/*
 * Authors:
 * Dmitriy A. Gearasimov <gerasimov.dmitriy@demlabs.net>
 * DeM Labs Inc.   https://demlabs.net
 * Kelvin Project https://github.com/kelvinblockchain
 * Copyright  (c) 2017-2018
 * All rights reserved.

 This file is part of DAP (Distributed Applications Platform) the open source project

    DAP (Distributed Applications Platform) 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 "dap_common.h"
#include "uthash.h"
#include "utlist.h"
#include "dap_chain_cs.h"

#define LOG_TAG "dap_chain_cs"

typedef struct dap_chain_callback_new_cfg_item
{
    char name[32];
    dap_chain_callback_new_cfg_t callback_init;
    UT_hash_handle hh;
} dap_chain_callback_new_cfg_item_t;

static dap_chain_callback_new_cfg_item_t * s_cs_callbacks = NULL;
static dap_chain_callback_new_cfg_item_t * s_class_callbacks = NULL;

/**
 * @brief dap_chain_cs_init
 * @return
 */
int dap_chain_cs_init(void)
{
    return 0;
}

/**
 * @brief dap_chain_cs_deinit
 */
void dap_chain_cs_deinit(void)
{

}

/**
 * @brief dap_chain_class_add
 * @param a_cs_str consensus name
 * @param a_callback_init callback function
 */
void dap_chain_cs_type_add (const char * a_cs_str,  dap_chain_callback_new_cfg_t a_callback_init)
{
    dap_chain_callback_new_cfg_item_t *l_item = DAP_NEW_Z ( dap_chain_callback_new_cfg_item_t );
    if ( !l_item ) {
        log_it(L_CRITICAL, "%s", g_error_memory_alloc);
        return;
    }
    strncpy(l_item->name, a_cs_str, sizeof (l_item->name) - 1);
    l_item->name[sizeof (l_item->name) - 1] = '\0';
    l_item->callback_init = a_callback_init;
    HASH_ADD_STR( s_class_callbacks, name, l_item);
}

/**
 * @brief dap_chain_class_create
 * @param a_chain
 * @param a_chain_cfg
 * @return
 */
int dap_chain_cs_type_create(const char *a_type, dap_chain_t * a_chain, dap_config_t * a_chain_cfg)
{
    dap_chain_callback_new_cfg_item_t *l_item = NULL;
    HASH_FIND_STR(s_class_callbacks, a_type, l_item);
    if (l_item && l_item->callback_init)
        return l_item->callback_init(a_chain, a_chain_cfg);
    return -1;
}


/**
 * @brief dap_chain_cs_add
 * add consensus [dag_pos, dag_poa, block_poa, none] to s_cs_callbacks linked list
 * @param a_cs_str
 * @param a_callback_init
 */
void dap_chain_cs_add (const char * a_cs_str,  dap_chain_callback_new_cfg_t a_callback_init)
{
    dap_chain_callback_new_cfg_item_t *l_item = DAP_NEW_Z ( dap_chain_callback_new_cfg_item_t );
    if ( !l_item ) {
        log_it(L_CRITICAL, "%s", g_error_memory_alloc);
        return;
    }
    strncpy(l_item->name, a_cs_str, sizeof (l_item->name) - 1);
    l_item->name[sizeof (l_item->name) - 1] = '\0';
    l_item->callback_init = a_callback_init;
    HASH_ADD_STR( s_cs_callbacks, name, l_item);
}

/**
 * @brief dap_chain_cs_create
 * get consensus from chain cfg file [dag_pos, dag_poa, block_poa, none]
 * dap_config_get_item_str( a_chain_cfg, "chain", "consensus");
 * verify if consensus was created by dap_chain_cs_add function
 * @param a_chain dap_chain_t chain object
 * @param a_chain_cfg dap_config_t 
 * @return
 */
int dap_chain_cs_create(dap_chain_t * a_chain, dap_config_t * a_chain_cfg)
{
    dap_chain_callback_new_cfg_item_t *l_item = NULL;
#if defined(DAP_CHAIN_BLOCKS_TEST) || defined(DAP_LEDGER_TEST)
    const char *l_consensus = NULL;
    if (a_chain->id.uint64 == 0)
        l_consensus = dap_strdup("dag_poa");
    else
        l_consensus = dap_strdup("esbocs");
#else
    const char *l_consensus = dap_config_get_item_str( a_chain_cfg, "chain", "consensus");
#endif
    if(l_consensus)
        HASH_FIND_STR(s_cs_callbacks, l_consensus, l_item );
    if (l_item) {
        log_it(L_NOTICE,"Consensus \"%s\" found, prepare to parse config file",l_item->name );
        if (l_item->callback_init)
            l_item->callback_init(a_chain, a_chain_cfg);
        DAP_CHAIN_PVT(a_chain)->cs_name = l_item->name;
        return 0;
    } else {
        log_it(L_ERROR,"Can't find consensus \"%s\"",dap_config_get_item_str( a_chain_cfg, "chain", "consensus"));
        return -1;
    }
}