#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include "servicepoint.h"

#define DEFAULT_LISTEN_IP "127.0.0.1"

void handle_error(const char *msg) {
    perror(msg);
    exit(EXIT_FAILURE);
}

bool log_command(struct Command command) {
    switch (command.tag) {
        case COMMAND_TAG_INVALID: {
            printf("-> this is an invalid command\n");
            break;
        }
        case COMMAND_TAG_HARD_RESET: {
            printf("-> HardReset command - exiting\n");
            return true;
        }
        case COMMAND_TAG_BITMAP: {
            BitmapCommand *bitmapCommand = command.data.bitmap;

            CompressionCode compression = sp_cmd_bitmap_get_compression(bitmapCommand);

            size_t x, y;
            sp_cmd_bitmap_get_origin(bitmapCommand, &x, &y);

            Bitmap *bitmap = sp_cmd_bitmap_get(bitmapCommand);
            size_t w = sp_bitmap_width(bitmap);
            size_t h = sp_bitmap_height(bitmap);

            printf("-> BitmapCommand with params: x=%zu, y=%zu, w=%zu, h=%zu, compression=%hu\n",
                    x, y, w, h, compression);
            break;
        }
        case COMMAND_TAG_BRIGHTNESS_GRID: {
            BrightnessGridCommand *gridCommand = command.data.brightness_grid;

            size_t x, y;
            sp_cmd_brightness_grid_get_origin(gridCommand, &x, &y);

            BrightnessGrid *grid = sp_cmd_brightness_grid_get(gridCommand);
            size_t w = sp_brightness_grid_width(grid);
            size_t h = sp_brightness_grid_height(grid);

            printf("-> BrightnessGridCommand with params: x=%zu, y=%zu, w=%zu, h=%zu\n",
                    x, y, w, h);
            break;
        }
        case COMMAND_TAG_CHAR_GRID: {
            CharGridCommand *gridCommand = command.data.char_grid;

            size_t x, y;
            sp_cmd_char_grid_get_origin(gridCommand, &x, &y);

            CharGrid *grid = sp_cmd_char_grid_get(gridCommand);
            size_t w = sp_char_grid_width(grid);
            size_t h = sp_char_grid_height(grid);

            printf("-> CharGridCommand with params: x=%zu, y=%zu, w=%zu, h=%zu\n",
                    x, y, w, h);
            break;
        }
        case COMMAND_TAG_CP437_GRID: {
            Cp437GridCommand *gridCommand = command.data.cp437_grid;

            size_t x, y;
            sp_cmd_cp437_grid_get_origin(gridCommand, &x, &y);

            Cp437Grid *grid = sp_cmd_cp437_grid_get(gridCommand);
            size_t w = sp_cp437_grid_width(grid);
            size_t h = sp_cp437_grid_height(grid);

            printf("-> Cp437GridCommand with params: x=%zu, y=%zu, w=%zu, h=%zu\n",
                    x, y, w, h);
            break;
        }
        case COMMAND_TAG_BIT_VEC: {
            BitVecCommand *bitvecCommand = command.data.bitvec;

            size_t offset = sp_cmd_bitvec_get_offset(bitvecCommand);
            CompressionCode compression = sp_cmd_bitvec_get_compression(bitvecCommand);

            BinaryOperation operation = sp_cmd_bitvec_get_operation(bitvecCommand);
            char *operationText;
            switch (operation) {
                case BINARY_OPERATION_AND:
                    operationText = "and";
                    break;
                case BINARY_OPERATION_OR:
                    operationText = "or";
                    break;
                case BINARY_OPERATION_XOR:
                    operationText = "xor";
                    break;
                case BINARY_OPERATION_OVERWRITE:
                    operationText ="overwrite";
                    break;
                default:
                    operationText ="unknown";
                    break;
            }

            BitVec *bitvec = sp_cmd_bitvec_get(bitvecCommand);
            size_t len = sp_bitvec_len(bitvec);

            printf("-> BitVecCommand with params: offset=%zu, length=%zu, compression=%hu, operation=%s\n",
                    offset, len, compression, operationText);
            break;
        }
        case COMMAND_TAG_CLEAR: {
            printf("-> ClearCommand\n");
            break;
        }
        case COMMAND_TAG_BITMAP_LEGACY: {
            printf("-> BitmapLinearLegacy\n");
            break;
        }
        case COMMAND_TAG_FADE_OUT:{
            printf("-> FadeOutCommand\n");
            break;
        }
        case COMMAND_TAG_GLOBAL_BRIGHTNESS: {
            Brightness brightness = sp_cmd_brightness_global_get(command.data.global_brightness);
            printf("-> GlobalBrightnessCommand with params: brightness=%hu\n", brightness);
            break;
        }
        default: {
            printf("-> unknown command tag %d\n", command.tag);
            break;
        }
    }

    return false;
}

int main(int argc, char **argv) {
    //init_env_logger();

    int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (udp_socket == -1)
        handle_error("socket could not be created\n");

    char *listen_addr_arg;
    if (argc > 1) {
        listen_addr_arg = argv[1];
    } else {
        listen_addr_arg = DEFAULT_LISTEN_IP;
    }

    struct in_addr addr;
    if (inet_aton(listen_addr_arg, &addr) == 0)
        handle_error("listen ip could not be parsed\n");

    struct sockaddr_in sockaddrIn = {
            .sin_addr = addr,
            .sin_family = AF_INET,
            .sin_port = htons(2342),
    };
    memset(sockaddrIn.sin_zero, 0, sizeof(sockaddrIn.sin_zero));

    if (bind(udp_socket, (struct sockaddr *) &sockaddrIn, sizeof(sockaddrIn)) == -1)
        handle_error("could not bind socket\n");

    printf("socket ready to receive on %s\n", listen_addr_arg);

    uint8_t buffer[10000];
    bool done = false;
    while (!done) {
        memset(buffer, 0, sizeof(buffer));
        printf("\n");

        ssize_t num_bytes = recv(udp_socket, (void *) buffer, sizeof(buffer), 0);
        if (num_bytes == -1)
            handle_error("could not read from client");

        Packet *packet = sp_packet_try_load((ByteSlice) {
                .start = buffer,
                .length = num_bytes,
        });
        if (packet == NULL) {
            printf("received invalid packet\n");
            continue;
        }

        struct Header *header = sp_packet_get_header(packet);

        ByteSlice payload = sp_packet_get_payload(packet);
        printf("Received packet: cc=%d, a=%d, b=%d, c=%d, d=%d, payload=%p (len %zu)\n",
               header->command_code, header->a, header->b, header->c, header->d,
               payload.start, payload.length);

        struct Command command = sp_cmd_generic_try_from_packet(packet);
        done = log_command(command);

        sp_cmd_generic_free(command);
    }

    close(udp_socket);
    exit(EXIT_SUCCESS);
}