#include <curl/curl.h>
#include <stdio.h>
#include <string.h>

#include "../../hb_core_ademco.h"
#include "cJSON/cJSON.h"
#include "uvlib/uv_log.h"
#include "uvlib/uv_tcpserver.h"

typedef struct machine_info_s {
    char acct[ADEMCO_PACKET_ACCT_MAX_LEN + 1];
    ademco_id_t ademco_id;
    hb_machine_type_t type;
    hb_machine_status_t status;
} machine_info_t;

struct {
    uv_loop_t* loop;
    uv_tcpserver_t* tcpd;
    const char* uri;
} context;

size_t on_write(void* buffer, size_t size, size_t nmemb, void* user) {
    mybuf_t* buf = user;
    mybuf_append(buf, buffer, size * nmemb);
    return size * nmemb;
}

int post(const char* json) {
    CURLcode res;
    CURL* curl = curl_easy_init();
    struct curl_slist* headers = NULL;
    mybuf_t resp;
    long http_status, header_size;

    mybuf_init(&resp);

    do {
        if (!curl) {
            res = CURLE_FAILED_INIT;
            uvlog_error("curl_easy_init failed");
            break;
        }

        headers = curl_slist_append(headers, "Content-Type: application/json");
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
        curl_easy_setopt(curl, CURLOPT_HEADER, 1);
        curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
        curl_easy_setopt(curl, CURLOPT_POST, 1);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, on_write);
        curl_easy_setopt(curl, CURLOPT_URL, context.uri);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(json));
        curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3);

        res = curl_easy_perform(curl);
        if (res == CURLE_OK || res == CURLE_GOT_NOTHING) {
            curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status);
            curl_easy_getinfo(curl, CURLINFO_HEADER_SIZE, &header_size);
        }
        mybuf_reserve(&resp, resp.size + 1);
        resp.buf[resp.size] = '\0';

        uvlog_debug(
            "RESP: %d %s\n"
            "%s\n",
            res, curl_easy_strerror(res),
            resp.buf);

    } while (0);

    if (curl)
        curl_easy_cleanup(curl);

    if (headers) {
        curl_slist_free_all(headers);
        headers = NULL;
    }
    return 0;
}

int relay(const char* acct, ademco_event_t ademco_event, ademco_zone_t zone, ademco_gg_t gg) {
    cJSON* json = NULL;
    char* string = NULL;
    int r = 0;

    json = cJSON_CreateObject();
    fatal_if_null(json);
    if (cJSON_AddStringToObject(json, "acct", acct) == NULL) goto end;
    if (cJSON_AddNumberToObject(json, "ademco_event", ademco_event) == NULL) goto end;
    if (cJSON_AddNumberToObject(json, "zone", zone) == NULL) goto end;
    if (cJSON_AddNumberToObject(json, "gg", gg) == NULL) goto end;
    string = cJSON_Print(json);
    if (string == NULL) goto end;

    uvlog_debug("OUT:\n%s\n", string);
    r = post(string);

end:
    if (json)
        cJSON_Delete(json);
    if (string)
        free(string);
    return r;
}

void on_tcp_connection(uv_tcpserver_client_context_t* client, int connected) {
    if (connected) {
        machine_info_t* machine = malloc(sizeof(machine_info_t));
        fatal_if_null(machine);
        memset(machine->acct, '\0', sizeof(machine->acct));
        machine->ademco_id = 0;
        machine->type = HMT_INVALID;
        machine->status = HMS_INVALID;
        client->data = machine;
    } else if (client->data) {
        if (((machine_info_t*)(client->data))->acct[0] != '\0') {
            relay(((machine_info_t*)(client->data))->acct, EVENT_OFFLINE, 0, 0);
        }
        free(client->data);
    }
}

uv_tcp_parse_result_t on_tcp_parse(uv_tcpserver_client_context_t* client, const char* buf, size_t len, size_t* ate) {
    ademco_packet_t pkt;
    ademco_parse_result_t res = ademco_parse_packet(buf, len, &pkt, ate, NULL);
    switch (res) {
        case ADEMCO_PARSE_RESULT_OK:
            switch (pkt.id) {
                case AID_NULL:
                case AID_HB:
                case AID_ADM_CID:
                    printf("C:");
                    ademco_print(pkt.raw, pkt.raw_len);

                    if (((machine_info_t*)(client->data))->acct[0] == '\0') {
                        strcpy(((machine_info_t*)(client->data))->acct, pkt.acct);
                        relay(pkt.acct, EVENT_ONLINE, 0, 0);
                    }

                    if (pkt.dat.ademco_id) {
                        ((machine_info_t*)(client->data))->ademco_id = pkt.dat.ademco_id;
                    }

                    if (ademco_is_machine_status_event(pkt.dat.ademco_event)) {
                        ((machine_info_t*)(client->data))->status = hb_machine_status_from_ademco_event(pkt.dat.ademco_event);
                    }

                    if (ademco_is_machine_type_event(pkt.dat.ademco_event)) {
                        ((machine_info_t*)(client->data))->type = hb_machine_type_from_ademco_event(pkt.dat.ademco_event);
                    }

                    if (pkt.dat.ademco_event != EVENT_INVALID_EVENT && ((machine_info_t*)(client->data))->acct[0] != '\0') {
                        relay(((machine_info_t*)(client->data))->acct, pkt.dat.ademco_event, pkt.dat.zone, pkt.dat.gg);
                    }

                    ademco_make_ack_packet2(&pkt, pkt.seq, pkt.acct, pkt.dat.ademco_id);
                    uv_tcpserver_send_to_cli(client, pkt.raw, pkt.raw_len);
                    printf("S:");
                    ademco_print(pkt.raw, pkt.raw_len);

                    break;
            }
            return uv_tcp_parse_ok;
            break;
        case ADEMCO_PARSE_RESULT_NOT_ENOUGH:
            return uv_tcp_parse_not_enough;
            break;
        case ADEMCO_PARSE_RESULT_ERROR:
            return uv_tcp_parse_error;
            break;
        default:
            abort();
            break;
    }
}

int init_tcpd(int port) {
    static uv_tcpserver_settings_t settings = {on_tcp_connection, on_tcp_parse, NULL};
    int r = uv_tcpserver_create(&context.tcpd, context.loop, &settings);
    fatal_on_uv_err(r, "uv_tcpserver_create");
    r = uv_tcpserver_start_listen(context.tcpd, "0.0.0.0", port);
    fatal_on_uv_err(r, "uv_tcpserver_start_listen");
    printf("tcp server listening on %s:%d\n", "0.0.0.0", port);
    return r;
}

int main(int argc, char** argv) {
    if (argc != 3) {
        fprintf(stderr,
                "Usage: %s tcp_server_listening_port http_client_relay_to_uri\n"
                "	Example: %s 12345 http://your-http-server.com:8080/ademco\n",
                argv[0], argv[0]);
        exit(1);
    }

    uv_log_set_level(uv_log_level_debug);
    memset(&context, 0, sizeof(context));
    context.uri = argv[2];

    context.loop = uv_default_loop();
    fatal_if_null(context.loop);

    curl_global_init(CURL_GLOBAL_ALL);

    if (init_tcpd(atoi(argv[1]))) {
        abort();
    }

    uv_run(context.loop, UV_RUN_DEFAULT);
}