#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <getopt.h>
#include <sysexits.h>
#include <inttypes.h>
#include <fcntl.h>

#include <hexagram/can.h>
#include <hexagram/capture.h>

#include "can2dump.h"

enum timestamp_type {
    TIMESTAMP_NONE,
    TIMESTAMP_ABSOLUTE,
    TIMESTAMP_DELTA,
    TIMESTAMP_ZERO,
    TIMESTAMP_DATE
};

struct optvalues {
    enum timestamp_type tstype;
};

char *hexagram_arglist_can2dump(void) {
    return "[--timestamp=|adzA|] ifname [file.can] [candump.txt]";
}

static int usage(int argc, char **argv, const char *message, ...) {
    if (message) {
        va_list args;

        va_start(args, message);
        vfprintf(stderr, message, args);
         fprintf(stderr, "\n");
        va_end(args);
    }

    fprintf(stderr, "usage: hexagram %s %s\n", argv[0], hexagram_arglist_can2dump());

    return EX_USAGE;
}

static int parseopts(struct optvalues *v,
                     struct option opts[],
                     int *argc,
                     char **argv[]) {
    int ch, index;

    while ((ch = getopt_long(*argc, *argv, "t:", opts, &index)) >= 0) {
        switch (ch) {
            case 't':
                switch (optarg[0]) {
                    case 'a': v->tstype = TIMESTAMP_ABSOLUTE; break;
                    case 'd': v->tstype = TIMESTAMP_DELTA;    break;
                    case 'z': v->tstype = TIMESTAMP_ZERO;     break;
                    case 'A': v->tstype = TIMESTAMP_DATE;     break;

                    default:
                        return usage(*argc, *argv, "Invalid timestamp type");
                }

                break;

            default:
                if (ch == '?') {
                    return usage(*argc, *argv, NULL);
                } else {
                    return usage(*argc, *argv, "Unknown flag '%c'", ch);
                }

                break;
        }
    }

    *argc -= optind;
    *argv += optind;

    return 0;
}

int hexagram_main_can2dump(int argc, char **argv) {
    static struct option opts[] = {
        { "timestamp", required_argument, NULL, 't' },
        { NULL,        0,                 NULL, 0   }
    };

    struct optvalues values = {
        .tstype = TIMESTAMP_NONE
    };

    int status;

    hexagram_capture *capture;
    const char *ifname;
    FILE *fh;

    struct timeval timestamp;
    struct can_frame frame;

    int argn    = argc;
    char **args = argv;

    if ((status = parseopts(&values, opts, &argn, &args)) != 0) {
        return status;
    }

    if (argn == 0) {
        return usage(argc, argv, "No CAN interface name provided");
    }

    ifname = args[0];

    if (argn == 1) {
        if ((capture = hexagram_capture_open_fd(fileno(stdin), O_RDONLY)) == NULL) {
            perror("hexagram_capture_open_fd()");

            goto error_capture_open;
        }

        fh = stdout;
    } else if (argn > 1) {
        if ((capture = hexagram_capture_open_file(args[1], O_RDONLY)) == NULL) {
            perror("hexagram_capture_open_file()");

            goto error_capture_open;
        }
    }

    if (argn == 3) {
        if ((fh = fopen(args[2], "w")) == NULL) {
            goto error_fopen;
        }
    } else if (argn == 2) {
        fh = stdout;
    } else {
        return usage(argc, argv, NULL);
    }

    while (hexagram_capture_read(capture, &timestamp, &frame) >= 0) {
        uint8_t i;

        switch (values.tstype) {
            case TIMESTAMP_NONE:
                fprintf(fh, " ");

                break;

            case TIMESTAMP_ABSOLUTE:
                fprintf(fh, " (%zu.%zu) ",
                    timestamp.tv_sec, timestamp.tv_usec);

                break;
        }

        fprintf(fh, "%6s  %03X   [%d] ",
            ifname, frame.can_id, frame.can_dlc);

        for (i=0; i<frame.can_dlc; i++) {
            fprintf(fh, " %02X", frame.data[i]);
        }

        fprintf(fh, "\n");
    }

    if (argn == 1) {
        hexagram_capture_destroy(capture);
    } else if (argn >= 2) {
        hexagram_capture_close(capture);
    }

    return 0;

error_fopen:
    if (argn == 1) {
        hexagram_capture_destroy(capture);
    } else if (argn >= 2) {
        hexagram_capture_close(capture);
    }

error_capture_open:
    return 1;
}