#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>
#include <endian.h>

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

char *hexagram_arglist_pcap2can(void) {
    return "[infile.pcapng] [outfile.can]";
}

static ssize_t handle_option(hexagram_pcapng_stream *stream,
                             uint32_t type,
                             uint16_t code,
                             uint16_t len,
                             void *data) {
    if (lseek(stream->fd, (size_t)len, SEEK_CUR) < 0) {
        goto error_io;
    }

    stream->error = HEXAGRAM_PCAPNG_ERROR_OK;

    return (size_t)len;

error_io:
    stream->error = HEXAGRAM_PCAPNG_ERROR_IO;

    return -1;
}

static ssize_t handle_block_if(hexagram_pcapng_stream *stream,
                               uint32_t type,
                               size_t len,
                               void *data) {
    hexagram_pcapng_if iface;

    ssize_t readlen,
            total = 0;

    size_t remaining = len;

    /*
     * Read the interface block header.
     */
    if ((readlen = read(stream->fd, &iface, sizeof(iface))) < 0) {
        stream->error = HEXAGRAM_PCAPNG_ERROR_IO;

        goto error_io;
    } else {
        remaining -= readlen;
        total     += readlen;
    }

    if ((readlen = hexagram_pcapng_read_options(stream, handle_option, type, remaining, data)) < 0) {
        stream->error = HEXAGRAM_PCAPNG_ERROR_IO;

        goto error_io;
    } else {
        total += readlen;
    }

    return total;

error_io:
    return -1;
}

static ssize_t handle_block_packet(hexagram_pcapng_stream *stream,
                                   uint32_t type,
                                   size_t len,
                                   hexagram_capture *capture) {
    hexagram_pcapng_packet header;
    struct can_frame frame;

    ssize_t readlen,
            total = 0;

    size_t remaining = len;

    struct timeval timestamp;
    uint64_t usec;

    /*
     * Read the packet block header so that we may determine the size of the
     * payload to continue to read.
     */
    if ((readlen = read(stream->fd, &header, sizeof(header))) < 0) {
        stream->error = HEXAGRAM_PCAPNG_ERROR_IO;

        goto error_io;
    } else {
        remaining -= readlen;
        total     += readlen;
    }

    if (header.caplen > sizeof(frame)) {
        if (lseek(stream->fd, header.caplen, SEEK_CUR) < 0) {
            stream->error = HEXAGRAM_PCAPNG_ERROR_IO;

            goto error_io;
        }

        readlen = header.caplen;
    } else if ((readlen = read(stream->fd, &frame, header.caplen)) < 0) {
        stream->error = HEXAGRAM_PCAPNG_ERROR_IO;

        goto error_io;
    }

    remaining -= readlen;
    total     += readlen;

    if (header.caplen <= sizeof(frame)) {
        usec = ((uint64_t)header.timestamp[0] << 32)
              | (uint64_t)header.timestamp[1];

        timestamp.tv_sec  = usec / 1000000;
        timestamp.tv_usec = usec % 1000000;

        frame.can_id = be32toh(frame.can_id);

        if (hexagram_capture_write(capture, &timestamp, &frame) < 0) {
            goto error_io;
        }
    }

    if (header.caplen % sizeof(uint32_t)) {
        size_t padding = sizeof(uint32_t) - (header.caplen % sizeof(uint32_t));

        if (lseek(stream->fd, padding, SEEK_CUR) < 0) {
            stream->error = HEXAGRAM_PCAPNG_ERROR_IO;

            goto error_io;
        } else {
            remaining -= padding;
            total     += padding;
        }
    }

    /*
     * The remaining data here should be pcapng option values, and since we do
     * not presently require them, we can safely seek past them.
     */
    if (lseek(stream->fd, remaining, SEEK_CUR) < 0) {
        stream->error = HEXAGRAM_PCAPNG_ERROR_IO;

        goto error_io;
    } else {
        total += remaining;
    }

    stream->error = HEXAGRAM_PCAPNG_ERROR_OK;

    return total;

error_io:
    stream->error = HEXAGRAM_PCAPNG_ERROR_IO;

    return -1;
}

static ssize_t handle_block(hexagram_pcapng_stream *stream,
                            uint32_t type,
                            size_t len,
                            void *data) {
    switch (type) {
        case HEXAGRAM_PCAPNG_BLOCK_IF:
            return handle_block_if(stream, type, len, data);

        case HEXAGRAM_PCAPNG_BLOCK_PACKET:
            return handle_block_packet(stream, type, len, (hexagram_capture *)data);

        default: break;
    }

    if (lseek(stream->fd, len, SEEK_CUR) < 0) {
        stream->error = HEXAGRAM_PCAPNG_ERROR_IO;

        goto error_io;
    }

    return len;

error_io:
    return -1;
}

int hexagram_main_pcap2can(int argc, char **argv) {
    int fd;

    hexagram_pcapng_stream *stream;
    hexagram_capture *capture;

    fd = (argc < 2)?
        fileno(stdin):
        open(argv[1], O_RDONLY);

    if (fd < 0) {
        perror("open()");

        goto error_open_pcapng;
    }

    if (argc > 2) {
        if ((capture = hexagram_capture_open_file(argv[2], O_CREAT | O_WRONLY)) == NULL) {
            perror("hexagram_capture_open_file()");

            goto error_capture_open;
        }
    } else {
        if ((capture = hexagram_capture_open_fd(fileno(stdout), O_WRONLY)) == NULL) {
            perror("hexagram_capture_open_fd()");

            goto error_capture_open;
        }
    }

    if ((stream = hexagram_pcapng_stream_open_fd(fd)) < 0) {
        perror("hexagram_pcapng_stream_open_fd()");

        goto error_pcapng_stream_open_fd;
    }

    if (hexagram_pcapng_stream_read(stream, handle_block, capture) < 0) {
        perror("hexagram_pcapng_stream_read()");

        goto error_pcapng_stream_read;
    }

    hexagram_pcapng_stream_destroy(stream);

    hexagram_capture_destroy(capture);

    if (argc == 2) {
        close(fd);
    }

    return 0;

error_pcapng_stream_read:
error_pcapng_stream_open_fd:
    hexagram_pcapng_stream_destroy(stream);

error_capture_open:
    if (argc == 3 || argc == 2) {
        close(fd);
    }

error_open_pcapng:
    return 1;
}