#include <sys/resource.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

#include <btc/block.h>
#include <btc/net.h>
#include <btc/utils.h>
#include <btc/serialize.h>
#include <btc/tx.h>

btc_node_group* group = NULL;

/*
 * Fermer correctement le réseau en cas d'interuption
 */
static void sig_handler(int signo)
{
  if (signo == SIGINT) 
  {
    printf("\nSIGINT catched\n");
    btc_node_group_shutdown(group);
  }
}

static int default_write_log(const char *format, ...)
{
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
    return 1;
}

FILE *fp = NULL;
btc_bool log_flag = false;

static int file_write_log(const char *format, ...)
{
    if (log_flag)
    {
        if (fp == NULL) fp = fopen("log.txt","a+");
        va_list args;
        va_start(args, format);
        vfprintf(fp, format, args);
        va_end(args);
    } else {
        if (fp != NULL) 
        {
          fclose(fp);
          fp = NULL;
        }
    }
    return 1;
}

int nb_points = 0;
void init_nb_points()
{
    printf("\n");fflush(stdout);
    nb_points = 0;
}

void print_point(char c)
{
    printf("%c",c);fflush(stdout);
    nb_points += 1;
    if (nb_points == 80) init_nb_points();
}

btc_bool file_exist(const char *filename)
{
  struct stat   buffer;   
  return (stat (filename, &buffer) == 0);
}

/*
 * Les callbacks
 */
static btc_bool timer_cb(btc_node *node, uint64_t *now)
{
    /* return true = run internal timer logic (ping, disconnect-timeout, etc.) */
    return true;
}

btc_bool parse_cmd(struct btc_node_ *node, btc_p2p_msg_hdr *hdr, struct const_buffer *buf)
{
    (void)(node);
    (void)(hdr);
    (void)(buf);
    return true;
}

void post_cmd(struct btc_node_ *node, btc_p2p_msg_hdr *hdr, struct const_buffer *buf)
{
    char point = '.';
    struct const_buffer buf_copy = { buf->p, buf->len };
    if (strcmp(hdr->command, BTC_MSG_INV) == 0)
    {
        point = 'i';
        btc_bool contains_block = false;
        uint32_t vsize;
        // directly create a getdata message
        cstring *p2p_msg = btc_p2p_message_new(node->nodegroup->chainparams->netmagic, BTC_MSG_GETDATA, buf->p, buf->len);

        if (!deser_varlen(&vsize, &buf_copy)) return;

        for (unsigned int i = 0; i < vsize; i++)
        {
            uint8_t hash[36];
            uint32_t type;
            if (!deser_u32(&type, &buf_copy)) return;
            if (!deser_u256(hash, &buf_copy)) return;

            contains_block = (type == BTC_INV_TYPE_BLOCK);
        }

        /* send message */
        if (contains_block) btc_node_send(node, p2p_msg);

        /* cleanup */
        cstr_free(p2p_msg, true);
    }
    if (strcmp(hdr->command, BTC_MSG_BLOCK) == 0)
    {
        FILE*   block_fd;
        char    block_filename[85];

        const char* coinbase_hash = "0000000000000000000000000000000000000000000000000000000000000000";
        point = 'b';
        btc_block_header header;
        uint256 hash_bin_val;
        char hash_hex_str[65];
        uint32_t nb_tx;
        unsigned long total_outputs = 0;

        if (!btc_block_header_deserialize(&header, &buf_copy)) return;
        if (!deser_varlen(&nb_tx, &buf_copy)) return;

        btc_block_header_hash(&header, (uint8_t *)&hash_bin_val);
        memset(hash_hex_str, 0, sizeof(hash_hex_str));
        utils_bin_to_hex(hash_bin_val, sizeof(hash_bin_val), hash_hex_str);
        utils_reverse_hex(hash_hex_str, 64);

        sprintf(block_filename,"data/simpledb/block_%s",hash_hex_str);
        if (file_exist(block_filename)) return;

        // Save the block in a file
        block_fd = fopen(block_filename,"wb");
        if (block_fd == NULL) return;
        fwrite((const void*)&(buf->len), sizeof(size_t), 1, block_fd);
        fwrite((const void*)buf->p, sizeof(char), buf->len, block_fd);
        fclose(block_fd);

        printf("\nHash      : %s", hash_hex_str);
        printf("\nVersion   : %d", header.version);
        printf("\nTimestamp : %u", header.timestamp);
        printf("\nNonce     : %u", header.nonce);

        memset(hash_hex_str, 0, sizeof(hash_hex_str));
        utils_bin_to_hex(header.prev_block, sizeof(header.prev_block), hash_hex_str);
        utils_reverse_hex(hash_hex_str, 64);
        printf("\nPrev      : %s", hash_hex_str);
        printf("\nNb TX     : %u", nb_tx);

        memset(hash_hex_str, 0, sizeof(hash_hex_str));
        utils_bin_to_hex(header.merkle_root, sizeof(header.merkle_root), hash_hex_str);
        utils_reverse_hex(hash_hex_str, 64);
        printf("\nMerkle    : %s", hash_hex_str);

        for (unsigned int i = 0; i < nb_tx; i++)
        {
            unsigned long tx_total_outputs = 0;
            size_t consummed = 0;
            btc_tx* tx =btc_tx_new(); //needs to be on the heap

            btc_tx_deserialize(buf_copy.p, buf_copy.len, tx, &consummed, true);
            deser_skip(&buf_copy, consummed);
            btc_tx_hash(tx, hash_bin_val);
            utils_bin_to_hex(hash_bin_val, sizeof(hash_bin_val), hash_hex_str);
            utils_reverse_hex(hash_hex_str, 64);

            for (size_t victx = 0; victx < tx->vin->len; victx++) 
            {
                btc_tx_in* tx_in = vector_idx(tx->vin, victx);
                char* hex_txin = utils_uint8_to_hex(tx_in->prevout.hash, 32);

                utils_reverse_hex(hex_txin, strlen(hex_txin));
                if (!strncmp(hex_txin,coinbase_hash,strlen(coinbase_hash))) strcpy(hex_txin,"COINBASE");
            }

            for (size_t voctx = 0; voctx < tx->vout->len; voctx++) 
            {
                btc_tx_out* tx_out = vector_idx(tx->vout, voctx);
                
                tx_total_outputs += tx_out->value;
            }

            /*
            printf("\nTx   : %d", i);
            printf("\n\tVersion   : %d", tx->version);
            printf("\n\tLocktime  : %d", tx->locktime);            
            printf("\n\tHash      : %s", hash_hex_str);
            printf("\n\tTotal (%ld) outputs : %ld", tx->vout->len, tx_total_outputs);
            printf("\n");
            */

            fflush(stdout);
            total_outputs += tx_total_outputs;
            btc_tx_free(tx);
        }

        printf("\nTotal Block Outputs Amount : %ld", total_outputs);
        printf("\n");

        fflush(stdout);
    }
    // print_point(point);
}

void node_connection_state_changed(struct btc_node_ *node)
{
    int nb_connected = btc_node_group_amount_of_connected_nodes(node->nodegroup, NODE_CONNECTED);
    /*
    init_nb_points();
    printf("STATUS : connexion change status (%d node connected)\n", nb_connected);
    */
    (void)(node);
}

void handshake_done(struct btc_node_ *node)
{
    int nb_connected = btc_node_group_amount_of_connected_nodes(node->nodegroup, NODE_CONNECTED);
    init_nb_points();
    // printf("STATUS : connexion change status (%d node connected)\n", nb_connected);
}

void do_the_main_job()
{
    const btc_dns_seed seed = btc_chainparams_main.dnsseeds[0];
    vector* ips;

    /* create a node group */
    group = btc_node_group_new(NULL);
    group->desired_amount_connected_nodes = 1;
    group->periodic_timer_cb = timer_cb;
    group->log_write_cb = file_write_log;
    group->parse_cmd_cb = parse_cmd;
    group->postcmd_cb = post_cmd;
    group->node_connection_state_changed_cb = node_connection_state_changed;
    group->handshake_done_cb = handshake_done;

    /* collect and add some btc nodes to the group */
    ips = vector_new(group->desired_amount_connected_nodes + 5, free);
    btc_get_peers_from_dns(seed.domain, ips, btc_chainparams_main.default_port, AF_INET);
    for (unsigned int i = 0; i<ips->len; i++)
    {
        /* create a node */
        btc_node *node = btc_node_new();
        char *ip = (char *)vector_idx(ips, i);
        btc_node_set_ipport(node, ip);
        btc_node_group_add_node(group, node);
    }
    vector_free(ips, true);

    /* connect to the next node */
    btc_node_group_connect_next_nodes(group);

    /* start the event loop */
    btc_node_group_event_loop(group);

    /* cleanup */
    btc_node_group_free(group); //will also free the nodes structures from the heap
}

int main(int ac, char** av)
{
    if (signal(SIGINT, sig_handler) == SIG_ERR)
    {
        printf("\ncan't catch SIGINT\n");
        return 1;
    }
    init_nb_points();
    do_the_main_job();
    return 0;
}
