#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/netspv.h>
#include <btc/utils.h>

btc_spv_client* spv_global_client;

/*
 * Fermer correctement le réseau en cas d'interuption
 */
void sig_handler(int signo)
{
  if (signo == SIGINT) 
  {
    printf("\nSIGINT catched\n");
    fflush(stdout);
    btc_node_group_shutdown(spv_global_client->nodegroup);
    sleep(10);
  }
}

/*
 * Informer de la consommation mémoire du processus
 */
void print_mem_usage()
{
    struct rusage r_usage;
    getrusage(RUSAGE_SELF,&r_usage);
    printf("Memory usage: %ld kilobytes\n",r_usage.ru_maxrss);
    fflush(stdout);
}

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

btc_bool write_blockindex(btc_blockindex* blockindex)
{
    FILE* block_fd = NULL;
    char  block_filename[2000];
    char  hash_hex_str[65];

    utils_bin_to_hex(blockindex->hash, sizeof(uint256), hash_hex_str);
    utils_reverse_hex(hash_hex_str, 64);

    sprintf(block_filename,"data/simpledb/block_%u_%s",blockindex->height, hash_hex_str);
    if (file_exist(block_filename)) return false;

    // Save the block in a file
    block_fd = fopen(block_filename,"wb");
    if (block_fd == NULL) return false;

    fwrite((const void*)  &blockindex->height, sizeof(uint32_t), 1, block_fd);
    fwrite((const void*)  &blockindex->hash, sizeof(uint256), 1, block_fd);
    fwrite((const void*)  &blockindex->header.version, sizeof(int32_t), 1, block_fd);
    fwrite((const void*)  &blockindex->header.timestamp, sizeof(uint32_t), 1, block_fd);
    fwrite((const void*)  &blockindex->header.nonce, sizeof(uint32_t), 1, block_fd);
    fwrite((const void*)  &blockindex->header.prev_block, sizeof(uint256), 1, block_fd);
    fwrite((const void*)  &blockindex->header.merkle_root, sizeof(uint256), 1, block_fd);

    fclose(block_fd);

    return true;
}

btc_bool add_blockindex_tx(btc_blockindex* blockindex, btc_tx* tx)
{
    FILE*    block_fd = NULL;
    char     block_filename[2000];
    char     hash_hex_str[65];
    uint256  hash_bin_val;
    uint64_t tx_amount = 0;

    utils_bin_to_hex(blockindex->hash, sizeof(uint256), hash_hex_str);
    utils_reverse_hex(hash_hex_str, 64);

    btc_tx_hash(tx, hash_bin_val);

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

    sprintf(block_filename,"data/simpledb/block_%u_%s",blockindex->height, hash_hex_str);
    if (!file_exist(block_filename)) write_blockindex(blockindex);

    // Save the block in a file
    block_fd = fopen(block_filename,"ab");
    if (block_fd == NULL) return false;

    fwrite((const void*)  &hash_bin_val, sizeof(uint256), 1, block_fd);
    fwrite((const void*)  &tx->locktime, sizeof(uint32_t), 1, block_fd);
    fwrite((const void*)  &tx_amount, sizeof(uint64_t), 1, block_fd);

    fclose(block_fd);

    return true;
}

/*
 * Callback de fin de synchro
 */
void test_spv_sync_completed(btc_spv_client* client) 
{
    UNUSED(client);

    printf("\nSync completed, at height %d\n", client->headers_db->getchaintip(client->headers_db_ctx)->height);
    fflush(stdout);
}

/*
 * Callback de message "header"
 */
btc_bool test_spv_header_message_processed(struct btc_spv_client_ *client, btc_node *node, btc_blockindex *newtip) 
{
    UNUSED(client);
    UNUSED(node);

    printf("\nGet SPV Header message\n");
    if (newtip) {
        printf("\nNew headers tip height %d\n", newtip->height);
    }
    fflush(stdout);
    return true;
}

/*
 * Callback de nouveau dernier block connu
 */
void test_spv_header_connected(struct btc_spv_client_ *client)
{
    btc_blockindex* blockindex = client->headers_db->getchaintip(client->headers_db_ctx);
    write_blockindex(blockindex);
    printf("\nSPV Header Connected : %d\n", blockindex->height);
    fflush(stdout);
}

/*
 * Callback de nouvelle transaction dans le dernier block connu
 */
void test_spv_sync_transaction(void *ctx, btc_tx *tx, unsigned int pos, btc_blockindex *blockindex)
{
    UNUSED(ctx);
    UNUSED(pos);

    add_blockindex_tx(blockindex, tx);
}

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

    spv_global_client = btc_spv_client_new(&btc_chainparams_main, false, false);
    spv_global_client->header_message_processed = test_spv_header_message_processed;
    spv_global_client->sync_completed = test_spv_sync_completed;
    spv_global_client->header_connected = test_spv_header_connected;
    spv_global_client->sync_transaction = test_spv_sync_transaction;

    btc_spv_client_load(spv_global_client, "data/index.db");

    printf("Discover peers...");
    btc_spv_client_discover_peers(spv_global_client, NULL);
    printf("done\n");
    printf("Start interacting with the p2p network...\n");
    btc_spv_client_runloop(spv_global_client);
    btc_spv_client_free(spv_global_client);

    return 0;
}
