/*
 *    Copyright 2004-2006 Intel Corporation
 * 
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 * 
 *        http://www.apache.org/licenses/LICENSE-2.0
 * 
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <strings.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "dtn_api.h"

#define BUFSIZE 16
#define BLOCKSIZE 8192
#define COUNTER_MAX_DIGITS 9

// Find the maximum commandline length
#ifdef __FreeBSD__
/* Needed for PATH_MAX, Linux doesn't need it */
#include <sys/syslimits.h>
#endif

#ifndef PATH_MAX
/* A conservative fallback */
#define PATH_MAX 1024
#endif

const char *progname;

int   verbose           = 0;    	// verbose output
int   quiet             = 0;    	// quiet output
char* endpoint		= NULL; 	// endpoint for registration
dtn_reg_id_t regid	= DTN_REGID_NONE;// registration id
int   expiration	= 30; 		// registration expiration time
int   count             = 0;            // exit after count bundles received
int   failure_action	= DTN_REG_DEFER;// registration delivery failure action
char* failure_script	= "";	 	// script to exec
int   register_only	= 0;    	// register and quit
int   change		= 0;    	// change existing registration 
int   unregister	= 0;    	// remove existing registration 
int   recv_timeout	= -1;    	// timeout to dtn_recv call
int   no_find_reg	= 0;    	// omit call to dtn_find_registration
char filename[PATH_MAX];		// Destination filename for file xfers
dtn_bundle_payload_location_t bundletype = DTN_PAYLOAD_MEM;

void
usage()
{
    fprintf(stderr, "usage: %s [opts] <endpoint> \n", progname);
    fprintf(stderr, "options:\n");
    fprintf(stderr, " -v verbose\n");
    fprintf(stderr, " -q quiet\n");
    fprintf(stderr, " -h help\n");
    fprintf(stderr, " -d <eid|demux_string> endpoint id\n");
    fprintf(stderr, " -r <regid> use existing registration regid\n");
    fprintf(stderr, " -n <count> exit after count bundles received\n");
    fprintf(stderr, " -e <time> registration expiration time in seconds "
            "(default: one hour)\n");
    fprintf(stderr, " -f <defer|drop|exec> failure action\n");
    fprintf(stderr, " -F <script> failure script for exec action\n");
    fprintf(stderr, " -x call dtn_register and immediately exit\n");
    fprintf(stderr, " -c call dtn_change_registration and immediately exit\n");
    fprintf(stderr, " -u call dtn_unregister and immediately exit\n");
    fprintf(stderr, " -N don't try to find an existing registration\n");
    fprintf(stderr, " -t <timeout> timeout value for call to dtn_recv\n");
    fprintf(stderr, " -o <template> Write out transfers to files using this template (# chars are\n"
            "replaced with a counter). Example: f##.bin goes to f00.bin, f01.bin, etc...\n");
}

void
parse_options(int argc, char**argv)
{
    int c, done = 0;

    progname = argv[0];

    memset(filename, 0, sizeof(char) * PATH_MAX);

    while (!done)
    {
        c = getopt(argc, argv, "vqhHd:r:e:f:F:xn:cuNt:o:");
        switch (c)
        {
        case 'v':
            verbose = 1;
            break;
        case 'q':
            quiet = 1;
            break;
        case 'h':
        case 'H':
            usage();
            exit(0);
            return;
        case 'r':
            regid = atoi(optarg);
            break;
        case 'e':
            expiration = atoi(optarg);
            break;
        case 'f':
            if (!strcasecmp(optarg, "defer")) {
                failure_action = DTN_REG_DEFER;

            } else if (!strcasecmp(optarg, "drop")) {
                failure_action = DTN_REG_DROP;

            } else if (!strcasecmp(optarg, "exec")) {
                failure_action = DTN_REG_EXEC;

            } else {
                fprintf(stderr, "invalid failure action '%s'\n", optarg);
                usage();
                exit(1);
            }
        case 'F':
            failure_script = optarg;
            break;
        case 'x':
            register_only = 1;
            break;
        case 'n':
            count = atoi(optarg);
            break;
        case 'c':
            change = 1;
            break;
        case 'u':
            unregister = 1;
            break;
        case 'N':
            no_find_reg = 1;
            break;
        case 't':
            recv_timeout = atoi(optarg);
            break;
        case 'o':
            strncpy(filename, optarg, PATH_MAX);
            break;
        case -1:
            done = 1;
            break;
        default:
            // getopt already prints an error message for unknown
            // option characters
            usage();
            exit(1);
        }
    }

    endpoint = argv[optind];
    if (!endpoint && (regid == DTN_REGID_NONE)) {
        fprintf(stderr, "must specify either an endpoint or a regid\n");
        usage();
        exit(1);
    }

    if ((change || unregister) && (regid == DTN_REGID_NONE)) {
        fprintf(stderr, "must specify regid when using -%c option\n",
                change ? 'c' : 'u');
        usage();
        exit(1);
    }

    if (failure_action == DTN_REG_EXEC && failure_script == NULL) {
        fprintf(stderr, "failure action EXEC must supply script\n");
        usage();
        exit(1);
    }

    // the default is to use memory transfer mode, but if someone specifies a
    // filename then we need to tell the API to expect a file
    if ( filename[0] != '\0' )
        bundletype = DTN_PAYLOAD_FILE;

}


static void
print_data(char* buffer, u_int length)
{
    u_int k;
    char s_buffer[BUFSIZE];
    for (k=0; k < length; k++)
    {
        if (buffer[k] >= ' ' && buffer[k] <= '~')
            s_buffer[k%BUFSIZE] = buffer[k];
        else
            s_buffer[k%BUFSIZE] = '.';

        if (k%BUFSIZE == 0) // new line every 16 bytes
        {
            printf("%07x ", k);
        }
        else if (k%2 == 0)
        {
            printf(" "); // space every 2 bytes
        }
                    
        printf("%02x", buffer[k] & 0xff);
                    
            // print character summary (a la emacs hexl-mode)
        if (k%BUFSIZE == BUFSIZE-1)
        {
            printf(" |  %.*s\n", BUFSIZE, s_buffer);
        }
    }
    
    // handle leftovers
    if ((length % BUFSIZE) != 0) {
        while (k%BUFSIZE != BUFSIZE-1) {
            if (k%2 == 0) {
                printf(" ");
            }
            printf("  ");
            k++;
        }
        printf("   |  %.*s\n",
               (int)length%BUFSIZE, 
               s_buffer);
    }
    printf("\n");
}

/* ----------------------------------------------------------------------- */
/* Builds the temporary file based on the template given and an integer
 * counter value
 */
int buildfilename(char* template, char* newfilename, int counter )
{
    char counterasstring[COUNTER_MAX_DIGITS];
    char formatstring[10];
    char* startloc;
    char* endloc;
    int templatelen;

    strcpy(newfilename, template);

    endloc = rindex(newfilename, '#');
    /* No template in the filename, just copy it as is */
    if ( endloc == NULL )
        return 0;
        
    /* Search backwards for the start of the template */
    for ( startloc = endloc; *startloc == '#' && startloc != template; 
          startloc -= 1 );

    startloc += 1;

    templatelen = endloc - startloc + 1;
    if ( templatelen > COUNTER_MAX_DIGITS )
        templatelen = COUNTER_MAX_DIGITS;

    sprintf(formatstring, "%%0%dd", templatelen);
    sprintf(counterasstring, formatstring, counter);

    if ( strlen(counterasstring) > (unsigned int)templatelen )
        fprintf(stderr, "Warning: Filename template not large enough "
                "to support counter value %d\n", counter);

    memcpy(startloc, counterasstring, sizeof(char) * templatelen);

    return 0;
}

/* ----------------------------------------------------------------------- */
/* File transfers suffer considerably from the inability to safely send 
 * metadata on the same channel as the file transfer in DTN.  Perhaps we 
 * should work around this?
 */
int
handle_file_transfer(dtn_bundle_spec_t spec, dtn_bundle_payload_t payload,
                     int* total_bytes, int counter)
{
    int tempdes;
    int destdes;
    char block[BLOCKSIZE];
    ssize_t bytesread;
    struct stat fileinfo;
    char currentfile[PATH_MAX];

    // Copy the file into place
    tempdes = open(payload.filename.filename_val, O_RDONLY);

    if ( tempdes < 0 )
    {
        fprintf(stderr, "While opening the temporary file for reading '%s': %s\n",
                payload.filename.filename_val, strerror(errno));
        exit(1);
    }

    // Create the filename by searching for ### characters in the given
    // filename and replacing that with an incrementing counter.  
    buildfilename(filename, currentfile, counter);

    destdes = creat(currentfile, 0644);

    if ( destdes < 0 )
    {
        fprintf(stderr, "While opening output file for writing '%s': %s\n",
                filename, strerror(errno));
        exit(1);
    }

    // Duplicate the file
    while ( (bytesread = read(tempdes, block, sizeof(block))) > 0 )
        write(destdes, block, bytesread);

    // Remove the temp file
    unlink(payload.filename.filename_val);

    if ( stat(currentfile, &fileinfo) == -1 )
    {
        fprintf(stderr, "Unable to stat destination file '%s': %s\n",
                currentfile, strerror(errno));
        return 1;
    }

    printf("%d byte file from [%s]: transit time=%d ms, written to '%s'\n",
           (int)fileinfo.st_size, spec.source.uri, 0, currentfile);        

    *total_bytes += fileinfo.st_size;

    return 0;
}

int
main(int argc, char** argv)
{
    int i;
    u_int k;
    int ret;
    int total_bytes = 0;
    dtn_handle_t handle;
    dtn_endpoint_id_t local_eid;
    dtn_reg_info_t reginfo;
    dtn_bundle_spec_t spec;
    dtn_bundle_payload_t payload;
    int call_bind;

    // force stdout to always be line buffered, even if output is
    // redirected to a pipe or file
    setvbuf(stdout, (char *)NULL, _IOLBF, 0);
    
    progname = argv[0];

    parse_options(argc, argv);

    if (count == 0) { 
        printf("dtnrecv (pid %d) starting up\n", getpid());
    } else {
        printf("dtnrecv (pid %d) starting up -- count %u\n", getpid(), count);
    }

    // open the ipc handle
    if (verbose) printf("opening connection to dtn router...\n");
    int err = dtn_open(&handle);
    if (err != DTN_SUCCESS) {
        fprintf(stderr, "fatal error opening dtn handle: %s\n",
                dtn_strerror(err));
        exit(1);
    }
    if (verbose) printf("opened connection to dtn router...\n");

    // if we're not given a regid, or we're in change mode, we need to
    // build up the reginfo
    memset(&reginfo, 0, sizeof(reginfo));

    if ((regid == DTN_REGID_NONE) || change)
    {
        // if the specified eid starts with '/', then build a local
        // eid based on the configuration of our dtn router plus the
        // demux string. otherwise make sure it's a valid one
        if (endpoint[0] == '/') {
            if (verbose) printf("calling dtn_build_local_eid.\n");
            dtn_build_local_eid(handle, &local_eid, (char *) endpoint);
            if (verbose) printf("local_eid [%s]\n", local_eid.uri);
        } else {
            if (verbose) printf("calling parse_eid_string\n");
            if (dtn_parse_eid_string(&local_eid, endpoint)) {
                fprintf(stderr, "invalid destination endpoint '%s'\n",
                        endpoint);
                goto err;
            }
        }

        // create a new registration based on this eid
        dtn_copy_eid(&reginfo.endpoint, &local_eid);
        reginfo.regid             = regid;
        reginfo.expiration        = expiration;
        reginfo.failure_action    = failure_action;
        reginfo.script.script_val = failure_script;
        reginfo.script.script_len = strlen(failure_script) + 1;
    }

    if (change) {
        if ((ret = dtn_change_registration(handle, regid, &reginfo)) != 0) {
            fprintf(stderr, "error changing registration: %d (%s)\n",
                    ret, dtn_strerror(dtn_errno(handle)));
            goto err;
        }
        printf("change registration succeeded, regid %d\n", regid);
        goto done;
    }
    
    if (unregister) {
        if (dtn_unregister(handle, regid) != 0) {
            fprintf(stderr, "error in unregister regid %d: %s\n",
                    regid, dtn_strerror(dtn_errno(handle)));
            goto err;
        }
        
        printf("unregister succeeded, regid %d\n", regid);
        goto done;
    }
    
    // try to see if there is an existing registration that matches
    // the given endpoint, in which case we'll use that one.
    if (regid == DTN_REGID_NONE && ! no_find_reg) {
        if (dtn_find_registration(handle, &local_eid, &regid) != 0) {
            if (dtn_errno(handle) != DTN_ENOTFOUND) {
                fprintf(stderr, "error in find_registration: %s\n",
                        dtn_strerror(dtn_errno(handle)));
                goto err;
            }
        }
        printf("find registration succeeded, regid %d\n", regid);
        call_bind = 1;
    }
    
    // if the user didn't give us a registration to use, get a new one
    if (regid == DTN_REGID_NONE) {
        if ((ret = dtn_register(handle, &reginfo, &regid)) != 0) {
            fprintf(stderr, "error creating registration: %d (%s)\n",
                    ret, dtn_strerror(dtn_errno(handle)));
            goto err;
        }

        printf("register succeeded, regid %d\n", regid);
        call_bind = 0;
    } else {
        call_bind = 1;
    }
    
    if (register_only) {
        goto done;
    }

    if (call_bind) {
        // bind the current handle to the found registration
        printf("binding to regid %d\n", regid);
        if (dtn_bind(handle, regid) != 0) {
            fprintf(stderr, "error binding to registration: %s\n",
                    dtn_strerror(dtn_errno(handle)));
            goto err;
        }
    }

    // loop waiting for bundles
    if (count == 0) {
        printf("looping forever to receive bundles\n");
    } else {
        printf("looping to receive %d bundles\n", count);
    }
    for (i = 0; (count == 0) || (i < count); ++i) {
        memset(&spec, 0, sizeof(spec));
        memset(&payload, 0, sizeof(payload));

        if (!quiet) {
            printf("dtn_recv [%s]...\n", local_eid.uri);
        }
    
        if ((ret = dtn_recv(handle, &spec, bundletype, &payload, 
                                                        recv_timeout)) < 0)
        {
            fprintf(stderr, "error getting recv reply: %d (%s)\n",
                    ret, dtn_strerror(dtn_errno(handle)));
            goto err;
        }

        // Files need to be handled differently than memory transfers
        if ( bundletype == DTN_PAYLOAD_FILE )
        {
                handle_file_transfer(spec, payload, &total_bytes, i);
                dtn_free_payload(&payload);
                continue;
        }

        total_bytes += payload.buf.buf_len;

        if (quiet) {
            dtn_free_payload(&payload);
            if (spec.blocks.blocks_len > 0) {
                free(spec.blocks.blocks_val);
                spec.blocks.blocks_val = NULL;
                spec.blocks.blocks_len = 0;
            }
            if (spec.metadata.metadata_len > 0) {
                free(spec.metadata.metadata_val);
                spec.metadata.metadata_val = NULL;
                spec.metadata.metadata_len = 0;
            }
            continue;
        }

        printf("\n%d extension blocks from [%s]: transit time=%d ms\n",
               spec.blocks.blocks_len, spec.source.uri, 0);
        dtn_extension_block_t *block = spec.blocks.blocks_val;
        for (k = 0; k < spec.blocks.blocks_len; k++) {
            printf("Extension Block %i:\n", k);
            printf("\ttype = %i\n\tflags = %i\n",
                   block[k].type, block[k].flags);
            print_data(block[k].data.data_val, block[k].data.data_len);
        }

        printf("\n%d metadata blocks from [%s]: transit time=%d ms\n",
               spec.metadata.metadata_len, spec.source.uri, 0);
        dtn_extension_block_t *meta = spec.metadata.metadata_val;
        for (k = 0; k < spec.metadata.metadata_len; k++) {
            printf("Metadata Extension Block %i:\n", k);
            printf("\ttype = %i\n\tflags = %i\n",
                   meta[k].type, meta[k].flags);
            print_data(meta[k].data.data_val, meta[k].data.data_len);
        }

        printf("%d payload bytes from [%s]: transit time=%d ms\n",
               payload.buf.buf_len,
               spec.source.uri, 0);
        
        print_data(payload.buf.buf_val, payload.buf.buf_len);
        printf("\n");

        dtn_free_payload(&payload);
        if (spec.blocks.blocks_len > 0) {
            free(spec.blocks.blocks_val);
            spec.blocks.blocks_val = NULL;
            spec.blocks.blocks_len = 0;
        }
        if (spec.metadata.metadata_len > 0) {
            free(spec.metadata.metadata_val);
            spec.metadata.metadata_val = NULL;
            spec.metadata.metadata_len = 0;
        }
    }

    printf("dtnrecv (pid %d) exiting: %d bundles received, %d total bytes\n\n",
           getpid(), i, total_bytes);

done:
    dtn_close(handle);
    return 0;

err:
    dtn_close(handle);
    return 1;
}

