/*
 * Authors:
 * Dmitriy A. Gearasimov <kahovski@gmail.com>
 * DeM Labs Inc.   https://demlabs.net
 * DeM Labs Open source community https://github.com/demlabsinc
 * Copyright  (c) 2017-2018
 * All rights reserved.

 This file is part of DAP (Deus Applications Prototypes) the open source project

 DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 DAP is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with any DAP based project.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <string.h>
#include <pthread.h>
#include <malloc.h>

#include "uthash.h"
#include "dap_hash.h"
#include "dap_chain_datum_tx_cache.h"

typedef struct list_linked_item {
    dap_chain_hash_fast_t tx_hash_fast;
    dap_chain_datum_tx_t *tx;
    UT_hash_handle hh;
} list_cached_item_t;

// List of UTXO - unspent transactions cache
static list_cached_item_t *datum_list = NULL;

// for separate access to connect_list
static pthread_mutex_t hash_list_mutex = PTHREAD_MUTEX_INITIALIZER;


dap_chain_hash_fast_t* chain_node_datum_tx_calc_hash(dap_chain_datum_tx_t *tx)
{
    dap_chain_hash_fast_t *tx_hash = DAP_NEW_Z(dap_chain_hash_fast_t);
    dap_hash(tx, dap_chain_datum_tx_get_size(tx), tx_hash->raw, sizeof(tx_hash->raw), DAP_HASH_TYPE_KECCAK);

    return tx_hash;
}

/**
 * Add new transaction to the cache list
 *
 * return 0 OK, -1 error, -2 already present
 */
int chain_node_datum_tx_list_hash_add(dap_chain_hash_fast_t *tx_hash, dap_chain_datum_tx_t *tx)
{
    int ret = 0;
    if(!tx_hash || !tx)
        return -1;
    list_cached_item_t *item_tmp = NULL;
    pthread_mutex_lock(&hash_list_mutex);
    HASH_FIND(hh, datum_list, tx_hash, sizeof(dap_chain_hash_fast_t), item_tmp); // tx_hash already in the hash?
    if(item_tmp == NULL) {
        item_tmp = DAP_NEW(list_cached_item_t);
        memcpy(&item_tmp->tx_hash_fast, tx_hash, sizeof(dap_chain_hash_fast_t));
        item_tmp->tx = DAP_NEW_SIZE(dap_chain_datum_tx_t, dap_chain_datum_tx_get_size(tx));
        memcpy(&item_tmp->tx, tx, dap_chain_datum_tx_get_size(tx));
        HASH_ADD(hh, datum_list, tx_hash_fast, sizeof(dap_chain_hash_fast_t), item_tmp); // tx_hash_fast: name of key field
        ret = 0;
    }
    // transaction already present
    else
        ret = -2;
    pthread_mutex_unlock(&hash_list_mutex);
    return ret;
}

/**
 * Delete transaction from the cache
 *
 * return 0 OK, -1 error, -2 tx_hash not found
 */
int chain_node_datum_tx_list_hash_del(dap_chain_hash_fast_t *tx_hash)
{
    int ret = -1;
    if(!tx_hash)
        return -1;
    list_cached_item_t *item_tmp;
    pthread_mutex_lock(&hash_list_mutex);
    HASH_FIND(hh, datum_list, tx_hash, sizeof(dap_chain_hash_fast_t), item_tmp);
    if(item_tmp != NULL) {
        HASH_DEL(datum_list, item_tmp);
        ret = 0;
    }
    else
        // hash not found in the cache
        ret = -2;
    pthread_mutex_unlock(&hash_list_mutex);
    if(!ret) {
        // delete transaction
        DAP_DELETE(item_tmp->tx);
        // del struct for hash
        DAP_DELETE(item_tmp);
    }
    return ret;
}

/**
 * Delete all transactions from the cache
 */
void chain_node_datum_tx_list_hash_del_all(void)
{
    int ret = -1;
    list_cached_item_t *iter_current, *item_tmp;
    pthread_mutex_lock(&hash_list_mutex);
    HASH_ITER(hh, datum_list , iter_current, item_tmp)
    {
        // delete transaction
        DAP_DELETE(iter_current->tx);
        // del struct for hash
        HASH_DEL(datum_list, iter_current);
    }
    pthread_mutex_unlock(&hash_list_mutex);
}

/**
 * Get transaction by hash
 *
 * return transaction, or NULL if transaction not found in the cache
 */
const dap_chain_datum_tx_t* chain_node_datum_tx_list_hash_find(dap_chain_hash_fast_t *tx_hash)
{
    int ret = 0;
    if(!tx_hash)
        return NULL;
    dap_chain_datum_tx_t *tx_ret = NULL;
    list_cached_item_t *item_tmp;
    pthread_mutex_lock(&hash_list_mutex);
    HASH_FIND(hh, datum_list, tx_hash, sizeof(dap_chain_hash_fast_t), item_tmp); // tx_hash already in the hash?
    if(item_tmp != NULL) {
        tx_ret = item_tmp->tx;
    }
    pthread_mutex_unlock(&hash_list_mutex);
    return tx_ret;
}