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

static btc_bool timer_cb(btc_node *node, uint64_t *now)
{
/*
    if (node->time_started_con + 300 < *now)
        btc_node_disconnect(node);
*/
    /* return true = run internal timer logic (ping, disconnect-timeout, etc.) */
    return true;
}

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

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 postcmd(struct btc_node_ *node, btc_p2p_msg_hdr *hdr, struct const_buffer *buf)
{
    if (strcmp(hdr->command, "block") == 0)
    {
        btc_block_header header;
        if (!btc_block_header_deserialize(&header, buf)) return;

        uint32_t vsize;
        if (!deser_varlen(&vsize, buf)) return;

        for (unsigned int i = 0; i < vsize; i++)
        {
            btc_tx *tx = btc_tx_new(); //needs to be on the heep
            btc_tx_deserialize(buf->p, buf->len, tx, NULL, true);

            btc_tx_free(tx);
        }

        btc_node_disconnect(node);
    }

    if (strcmp(hdr->command, "inv") == 0)
    {
        // directly create a getdata message
        cstring *p2p_msg = btc_p2p_message_new(node->nodegroup->chainparams->netmagic, "getdata", buf->p, buf->len);

        uint32_t vsize;
        uint8_t hash[36];
        uint32_t type;
        if (!deser_varlen(&vsize, buf)) return;

        for (unsigned int i = 0; i < vsize; i++)
        {
            if (!deser_u32(&type, buf)) return;
            if (!deser_u256(hash, buf)) return;

        }

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

        /* cleanup */
        cstr_free(p2p_msg, true);
    }

    if (strcmp(hdr->command, "headers") == 0)
    {
        /* send getblock command */

        /* request some headers (from the genesis block) */
        vector *blocklocators = vector_new(1, NULL);
        uint256 from_hash;
        utils_uint256_sethex("000000000000000001e67f0781f5e31a62863e6d7a1a1f786c7f666a9954a648", from_hash); // height 428694
        uint256 stop_hash;
        utils_uint256_sethex("00000000000000000378be785f464ef19243baba187cb3791ac92a69ca46bb46", stop_hash); // height 428695

        vector_add(blocklocators, from_hash);

        cstring *getheader_msg = cstr_new_sz(256);
        btc_p2p_msg_getheaders(blocklocators, stop_hash, getheader_msg);

        /* create p2p message */
        cstring *p2p_msg = btc_p2p_message_new(node->nodegroup->chainparams->netmagic, "getblocks", getheader_msg->str, getheader_msg->len);
        cstr_free(getheader_msg, true);

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

        /* cleanup */
        vector_free(blocklocators, true);
        cstr_free(p2p_msg, true);
    }
}

void node_connection_state_changed(struct btc_node_ *node)
{
    (void)(node);
}

void handshake_done(struct btc_node_ *node)
{
    /* make sure only one node is used for header sync */
    for(size_t i =0;i< node->nodegroup->nodes->len; i++)
    {
        btc_node *check_node = vector_idx(node->nodegroup->nodes, i);
        if ((check_node->state & NODE_HEADERSYNC) == NODE_HEADERSYNC)
            return;
    }

    // request some headers (from the genesis block)
    vector *blocklocators = vector_new(1, NULL);
    vector_add(blocklocators, (void *)node->nodegroup->chainparams->genesisblockhash);

    cstring *getheader_msg = cstr_new_sz(256);
    btc_p2p_msg_getheaders(blocklocators, NULL, getheader_msg);

    /* create p2p message */
    cstring *p2p_msg = btc_p2p_message_new(node->nodegroup->chainparams->netmagic, "getheaders", getheader_msg->str, getheader_msg->len);
    cstr_free(getheader_msg, true);

    /* send message */
    node->state |= NODE_HEADERSYNC;
    btc_node_send(node, p2p_msg);

    /* cleanup */
    vector_free(blocklocators, true);
    cstr_free(p2p_msg, true);
}

void test_net_basics_plus_download_block()
{

    vector *ips = vector_new(10, free);
    const btc_dns_seed seed = btc_chainparams_main.dnsseeds[0];

    btc_get_peers_from_dns(seed.domain, ips, btc_chainparams_main.default_port, AF_INET);
    for (unsigned int i = 0; i<ips->len; i++)
    {
        char *ip = (char *)vector_idx(ips, i);
        printf("dns seed ip %d: %s\n", i, ip);
    }
    vector_free(ips, true);

    /* create a invalid node */
    btc_node *node_wrong = btc_node_new();
    btc_node_set_ipport(node_wrong, "0.0.0.1:1");

    /* create a invalid node to will run directly into a timeout */
    btc_node *node_timeout_direct = btc_node_new();
    btc_node_set_ipport(node_timeout_direct, "127.0.0.1:1234");

    /* create a invalid node to will run indirectly into a timeout */
    btc_node *node_timeout_indirect = btc_node_new();
    btc_node_set_ipport(node_timeout_indirect, "8.8.8.8:8333");

    /* create a node */
    btc_node *node = btc_node_new();
    btc_node_set_ipport(node, "192.99.8.123:8333");

    /* create a node group */
    btc_node_group* group = btc_node_group_new(NULL);
    group->desired_amount_connected_nodes = 1;

    /* add the node to the group */
    btc_node_group_add_node(group, node);

    /* set the timeout callback */
    group->periodic_timer_cb = timer_cb;

    /* set a individual log print function */
    group->log_write_cb = net_write_log_printf;
    group->parse_cmd_cb = parse_cmd;
    group->postcmd_cb = postcmd;
    group->node_connection_state_changed_cb = node_connection_state_changed;
    group->handshake_done_cb = handshake_done;
    
    /* 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)
{
  test_net_basics_plus_download_block();
  return 0;
}   
