/*
 *  Clixir Support and I/O functions.
 *
 *  The stuff that talks to BEAM lives here.
 */
#include "clixir_support.h"

// If you want full protocol hexdumps:
// #define CLIXIR_PROTOCOL_DUMP 1

static void handle_command(const char *command, unsigned short len);
static void clixir_read_loop();

int main() {
    clixir_read_loop();
}

static void clixir_read_loop() {
    // Protocol: 2 bytes with big endian length, then the actual command.
    // So maximum size we can read is 65535. We're not even gonna allocate
    // that with today's stack sizes and a single process. Once we start pushing
    // large graphics objects like bitmaps through the pipe, we'll regret the
    // decision but maybe by then we have converted this to a full C node.
    char buffer[BUF_SIZE];
    unsigned char size_buffer[2];

    while (1) {
        ssize_t bytes_read = read(STDIN_FILENO, size_buffer, 2);
        if (bytes_read == 0) {
            // We really only expect EOF here.
            fprintf(stderr, "Clixir executable ending normally on EOF\n");
            exit(0);
        }
        if (bytes_read != 2) {
            fprintf(stderr, "Unexpected size read: %ld, errno = %d, exiting.\n", bytes_read, errno);
            exit(errno);
        }

        unsigned short size = (size_buffer[0] << 8) + size_buffer[1];
        bytes_read = read(STDIN_FILENO, buffer, size);
        if (bytes_read <= 0) {
            fprintf(stderr, "Error, read %ld bytes, expected positive number\n", bytes_read);
            exit(-1);
        } else if (bytes_read < size) {
            fprintf(stderr, "Error, short read. Expected %d, got %ld\n", size, bytes_read);
            dump_hex('<', buffer, bytes_read);
            exit(-1);
        } else {
            dump_hex('<', buffer, size);
            handle_command(buffer, size);
        }
    }
}

// _dispatch_command will be generated by Clixir, this is its signature:
extern void _dispatch_command(const char *buf, unsigned short len, int *index);

static void _handle_command(const char *command, unsigned short len, int *index) {
    // For now, we parse the command, then echo it back.
    // Note that all we accept for now is
    //   {cast, <<function_name>>, args.... [, callback_pid]}
    // Or, preferably, an array of these.
    ei_term term;

    if (*index >= len) {
        return;
    }

    int result = ei_decode_ei_term(command, index, &term);
    assert(result == 1);
    switch (term.ei_type) {
    case ERL_SMALL_TUPLE_EXT:
        _dispatch_command(command, len, index);
        _handle_command(command, len, index);
        break;
    case ERL_LIST_EXT:
        // A list of commands; we can send this for efficiency. Loop and go.
        for (int i = 0; i < term.arity; i++) {
            _handle_command(command, len, index);
        }
        break;
    case ERL_NIL_EXT:
        // Skip nil.
        break;
    default:
        fprintf(stderr, "Unknown term type %c / %d\n", term.ei_type, term.ei_type);
        assert(1 == 0);
    }
}
static void handle_command(const char *command, unsigned short len) {
  int index = 1;
  _handle_command(command, len, &index); // Skip version number
}

void write_response_bytes(const char *bytes, unsigned short len) {
    struct iovec iov[2];
    unsigned char size_buffer[2];

    size_buffer[0] = len >> 8;
    size_buffer[1] = len & 0xff;

    iov[0].iov_base = size_buffer;
    iov[0].iov_len  = 2;
    iov[1].iov_base = (char *) bytes; // ok to drop the const here, read-only access
    iov[1].iov_len  = len;

    dump_hex('>', size_buffer, 2);
    dump_hex('>', bytes, len);
    assert (writev(STDOUT_FILENO, iov, 2) == len + 2);
}

// For debugging, shamely stolen from github
// https://gist.githubusercontent.com/ccbrown/9722406/raw/05202cd8f86159ff09edc879b70b5ac6be5d25d0/DumpHex.c

void dump_hex(const char dir, const void* data, size_t size) {
#ifdef CLIXIR_PROTOCOL_DUMP
    char ascii[17];
    size_t i, j;
    ascii[16] = '\0';
    fprintf(stderr, "%c ", dir);
    for (i = 0; i < size; ++i) {
        fprintf(stderr,"%02X ", ((unsigned char*)data)[i]);
        if (((unsigned char*)data)[i] >= ' ' && ((unsigned char*)data)[i] <= '~') {
            ascii[i % 16] = ((unsigned char*)data)[i];
        } else {
            ascii[i % 16] = '.';
        }
        if ((i+1) % 8 == 0 || i+1 == size) {
            fprintf(stderr," ");
            if ((i+1) % 16 == 0) {
                fprintf(stderr,"|  %s \n%c ", ascii, dir);
            } else if (i+1 == size) {
                ascii[(i+1) % 16] = '\0';
                if ((i+1) % 16 <= 8) {
                    fprintf(stderr," ");
                }
                for (j = (i+1) % 16; j < 16; ++j) {
                    fprintf(stderr,"   ");
                }
                fprintf(stderr,"|  %s \n%c ", ascii, dir);
            }
        }
    }
    fprintf(stderr, "\n");
    fflush(stderr);
#endif
}
