/*
 * 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/>.
*/


#pragma once
#include <stdbool.h>
#include <pthread.h>
#include "dap_config.h"
#include "dap_chain_common.h"
#include "dap_chain_datum.h"

#ifdef DAP_TPS_TEST
#define DAP_CHAIN_ATOM_MAX_SIZE (100 * 1024 * 1024)
#else
#define DAP_CHAIN_ATOM_MAX_SIZE (256 * 1024) // 256 KB
#endif

typedef struct dap_chain dap_chain_t;

typedef struct dap_chain_cell dap_chain_cell_t;

typedef struct dap_ledger dap_ledger_t;

// Atomic element
typedef const void * dap_chain_atom_ptr_t;

// Atomic element iterator
typedef struct dap_chain_atom_iter {
    dap_chain_t *chain;
    dap_chain_cell_id_t cell_id;
    void *cur_item;
    dap_chain_atom_ptr_t cur;
    size_t cur_size;
    dap_chain_hash_fast_t *cur_hash;
    uint64_t cur_num;
    dap_time_t cur_ts;
} dap_chain_atom_iter_t;

typedef struct dap_chain_datum_iter {
    dap_chain_t *chain;
    dap_chain_datum_t *cur;
    size_t cur_size;
    dap_chain_hash_fast_t *cur_hash;
    dap_chain_hash_fast_t *cur_atom_hash;
    uint32_t action;
    dap_chain_net_srv_uid_t uid;
    int ret_code;
    char *token_ticker;
    void *cur_item;
} dap_chain_datum_iter_t;

typedef enum dap_chain_atom_verify_res{
    ATOM_ACCEPT = 0, ATOM_PASS, ATOM_REJECT, ATOM_MOVE_TO_THRESHOLD, ATOM_FORK
} dap_chain_atom_verify_res_t;

static const char* const dap_chain_atom_verify_res_str[] = {
    [ATOM_ACCEPT]   = "accepted",
    [ATOM_PASS]     = "skipped",
    [ATOM_REJECT]   = "rejected",
    [ATOM_MOVE_TO_THRESHOLD] = "thresholded",
    [ATOM_FORK] = "forked"
};

typedef enum dap_chain_iter_op {
    DAP_CHAIN_ITER_OP_FIRST,
    DAP_CHAIN_ITER_OP_LAST,
    DAP_CHAIN_ITER_OP_NEXT,
    DAP_CHAIN_ITER_OP_PREV
} dap_chain_iter_op_t;

typedef dap_chain_t* (*dap_chain_callback_new_t)(void);

typedef void (*dap_chain_callback_t)(dap_chain_t *);
typedef int (*dap_chain_callback_new_cfg_t)(dap_chain_t *, dap_config_t *);
typedef void (*dap_chain_callback_ptr_t)(dap_chain_t *, void * );

typedef dap_chain_atom_verify_res_t (*dap_chain_callback_atom_t)(dap_chain_t *a_chain, dap_chain_atom_ptr_t a_atom, size_t a_atom_size, dap_hash_fast_t *a_atom_hash, bool a_atom_new);
typedef dap_chain_atom_ptr_t (*dap_chain_callback_atom_form_treshold_t)(dap_chain_t *, size_t *);
typedef json_object *(*dap_chain_callback_atom_to_json)(json_object **a_arr_out, dap_chain_t *a_chain, dap_chain_atom_ptr_t a_atom, size_t a_atom_size, const char *a_hex_out_type);
typedef dap_chain_atom_verify_res_t (*dap_chain_callback_atom_verify_t)(dap_chain_t *, dap_chain_atom_ptr_t , size_t, dap_hash_fast_t*);
typedef size_t (*dap_chain_callback_atom_get_hdr_size_t)(void);

typedef dap_chain_atom_iter_t * (*dap_chain_callback_atom_iter_create_t)(dap_chain_t *a_chain, dap_chain_cell_id_t a_cell_id, dap_hash_fast_t *a_hash_from);
typedef dap_chain_atom_ptr_t (*dap_chain_callback_atom_iter_get_t)(dap_chain_atom_iter_t *a_iter, dap_chain_iter_op_t a_operation, size_t *a_atom_size);
typedef dap_chain_atom_ptr_t (*dap_chain_callback_atom_iter_find_by_hash_t)(dap_chain_atom_iter_t *a_iter, dap_hash_fast_t *a_atom_hash, size_t *a_atom_size);
typedef dap_chain_atom_ptr_t (*dap_chain_callback_atom_iter_get_by_num_t)(dap_chain_atom_iter_t *a_iter, uint64_t a_atom_num);
typedef void (*dap_chain_callback_atom_iter_delete_t)(dap_chain_atom_iter_t *);

typedef dap_chain_datum_iter_t * (*dap_chain_datum_callback_iter_create_t)(dap_chain_t *);
typedef dap_chain_datum_t * (*dap_chain_datum_callback_iter_get_first_t)(dap_chain_datum_iter_t *);
typedef dap_chain_datum_t * (*dap_chain_datum_callback_iter_get_last_t)(dap_chain_datum_iter_t *);
typedef dap_chain_datum_t * (*dap_chain_datum_callback_iter_get_next_t)(dap_chain_datum_iter_t *);
typedef dap_chain_datum_t * (*dap_chain_datum_callback_iter_get_prev_t)(dap_chain_datum_iter_t *);
typedef dap_chain_datum_t * (*dap_chain_datum_callback_iters)(dap_chain_datum_iter_t *);
typedef void (*dap_chain_datum_callback_iter_delete_t)(dap_chain_datum_iter_t *);

typedef dap_chain_datum_t** (*dap_chain_callback_atom_get_datum_t)(dap_chain_atom_ptr_t, size_t, size_t * );
typedef dap_time_t (*dap_chain_callback_atom_get_timestamp_t)(dap_chain_atom_ptr_t);

typedef dap_chain_datum_t * (*dap_chain_callback_datum_find_by_hash_t)(dap_chain_t *, dap_chain_hash_fast_t *, dap_chain_hash_fast_t *, int *);

typedef dap_chain_atom_ptr_t (*dap_chain_callback_block_find_by_hash_t)(dap_chain_t * ,dap_chain_hash_fast_t *, size_t *);

typedef dap_chain_atom_ptr_t * (*dap_chain_callback_atom_iter_get_atoms_t)(dap_chain_atom_iter_t * ,size_t* ,size_t**);
typedef size_t (*dap_chain_callback_add_datums_t)(dap_chain_t * , dap_chain_datum_t **, size_t );

typedef void (*dap_chain_callback_notify_t)(void *a_arg, dap_chain_t *a_chain, dap_chain_cell_id_t a_id, dap_chain_hash_fast_t *a_atom_hash, void *a_atom, size_t a_atom_size); //change in chain happened
typedef void (*dap_chain_callback_datum_notify_t)(void *a_arg, dap_chain_hash_fast_t *a_datum_hash, void *a_datum, 
                                    size_t a_datum_size, int a_ret_code, uint32_t a_action, dap_chain_net_srv_uid_t a_uid); //change in chain happened
typedef void (*dap_chain_callback_datum_removed_notify_t)(void *a_arg, dap_chain_hash_fast_t *a_datum_hash); //change in chain happened

typedef uint64_t (*dap_chain_callback_get_count)(dap_chain_t *a_chain);
typedef dap_list_t *(*dap_chain_callback_get_list)(dap_chain_t *a_chain, size_t a_count, size_t a_page, bool a_reverse);
typedef dap_list_t *(*dap_chain_callback_get_poa_certs)(dap_chain_t *a_chain, size_t *a_auth_certs_count, uint16_t *count_verify);
typedef void (*dap_chain_callback_load_from_gdb)(dap_chain_t *a_chain);
typedef uint256_t (*dap_chain_callback_calc_reward)(dap_chain_t *a_chain, dap_hash_fast_t *a_block_hash, dap_pkey_t *a_block_sign_pkey);

typedef enum dap_chain_type {
    CHAIN_TYPE_INVALID = -1,
    CHAIN_TYPE_TOKEN = 1,
    CHAIN_TYPE_EMISSION = 2,
    CHAIN_TYPE_TX = 3,
    CHAIN_TYPE_CA = 4,
    CHAIN_TYPE_SIGNER = 5,
    CHAIN_TYPE_DECREE = 7,
    CHAIN_TYPE_ANCHOR = 8,
    CHAIN_TYPE_MAX
} dap_chain_type_t;

// not rotate, use in state machine
typedef enum dap_chain_sync_state {
    CHAIN_SYNC_STATE_SYNCED = -1,  // chain was synced
    CHAIN_SYNC_STATE_IDLE = 0,  // do nothink
    CHAIN_SYNC_STATE_WAITING = 1,  // wait packet in
    CHAIN_SYNC_STATE_ERROR = 2 // have a error
} dap_chain_sync_state_t;

typedef struct dap_chain {
    pthread_rwlock_t rwlock; // Common rwlock for the whole structure

    dap_chain_id_t id;
    dap_chain_net_id_t net_id;
    uint16_t load_priority;
    char *name;
    char *net_name;
    bool is_datum_pool_proc;
    bool is_mapped;
    atomic_int load_progress; 
    // Nested cells (hashtab by cell_id)
    dap_chain_cell_t *cells;
    dap_chain_cell_id_t active_cell_id;
    dap_chain_cell_id_t forking_cell_id;

    uint16_t datum_types_count;
    dap_chain_type_t *datum_types;
    uint16_t default_datum_types_count;
    dap_chain_type_t *default_datum_types;
    uint16_t autoproc_datum_types_count;
    uint16_t *autoproc_datum_types;
    uint64_t atom_num_last;

    dap_chain_sync_state_t  state;

    uint16_t authorized_nodes_count;
    dap_stream_node_addr_t *authorized_nodes_addrs;

    // To hold it in double-linked lists
    struct dap_chain * next;
    struct dap_chain * prev;

    pthread_rwlock_t cell_rwlock;

    dap_chain_callback_new_cfg_t callback_created;
    dap_chain_callback_t callback_delete;
    dap_chain_callback_t callback_purge;

    dap_chain_callback_atom_t callback_atom_add;
    dap_chain_callback_atom_form_treshold_t callback_atom_add_from_treshold;
    dap_chain_callback_atom_verify_t callback_atom_verify;

    dap_chain_callback_add_datums_t callback_add_datums;
    dap_chain_callback_atom_get_hdr_size_t callback_atom_get_hdr_static_size; // Get atom header's size

    dap_chain_callback_atom_get_datum_t callback_atom_get_datums;
    dap_chain_callback_atom_get_timestamp_t callback_atom_get_timestamp;

    dap_chain_callback_atom_iter_find_by_hash_t callback_atom_find_by_hash;
    dap_chain_callback_atom_iter_get_by_num_t callback_atom_get_by_num;
    dap_chain_callback_datum_find_by_hash_t callback_datum_find_by_hash;
    dap_chain_callback_atom_to_json callback_atom_dump_json;

    dap_chain_callback_block_find_by_hash_t callback_block_find_by_tx_hash;

    dap_chain_callback_atom_iter_create_t callback_atom_iter_create;
    dap_chain_callback_atom_iter_get_t callback_atom_iter_get;
    dap_chain_callback_atom_iter_delete_t callback_atom_iter_delete;
    // WRN: No iterator used or changed with it
    dap_chain_callback_atom_iter_get_atoms_t callback_atom_iter_get_links;

    dap_chain_callback_get_count callback_count_tx;
    dap_chain_callback_get_list callback_get_txs;
    dap_chain_callback_get_count callback_count_atom;
    dap_chain_callback_get_list callback_get_atoms;

    // Consensus specific callbacks
    dap_chain_callback_get_poa_certs callback_get_poa_certs;
    dap_chain_callback_calc_reward callback_calc_reward;
    dap_chain_callback_load_from_gdb callback_load_from_gdb;

    // Iterator callbacks
    dap_chain_datum_callback_iter_create_t callback_datum_iter_create;
    dap_chain_datum_callback_iter_get_first_t callback_datum_iter_get_first;
    dap_chain_datum_callback_iter_get_last_t callback_datum_iter_get_last;
    dap_chain_datum_callback_iter_get_next_t callback_datum_iter_get_next;
    dap_chain_datum_callback_iter_get_prev_t callback_datum_iter_get_prev;
    dap_chain_datum_callback_iter_delete_t callback_datum_iter_delete;

    dap_list_t *atom_notifiers;
    dap_list_t *datum_notifiers;
    dap_list_t *datum_removed_notifiers;

    dap_list_t *atom_confirmed_notifiers;

    dap_config_t *config;

    void * _pvt; // private data
    void * _inheritor; // inheritor object
} dap_chain_t;

typedef struct dap_proc_thread dap_proc_thread_t;

typedef struct dap_chain_atom_notifier {
    dap_chain_callback_notify_t callback;
    dap_proc_thread_t *proc_thread;
    void *arg;
} dap_chain_atom_notifier_t;

typedef struct dap_chain_datum_notifier {
    dap_chain_callback_datum_notify_t callback;
    dap_proc_thread_t *proc_thread;
    void *arg;
} dap_chain_datum_notifier_t;

typedef struct dap_chain_datum_removed_notifier {
    dap_chain_callback_datum_removed_notify_t callback;
    dap_proc_thread_t *proc_thread;
    void *arg;
} dap_chain_datum_removed_notifier_t;
typedef struct dap_chain_atom_confirmed_notifier {
    uint64_t block_notify_cnt;
    dap_chain_callback_notify_t callback;
    void *arg;
} dap_chain_atom_confirmed_notifier_t;

typedef struct dap_chain_pvt {
    char *cs_name, *file_storage_dir;
    bool cs_started, need_reorder;
} dap_chain_pvt_t;

#define DAP_CHAIN_PVT(a) ((dap_chain_pvt_t *)a->_pvt)

#define DAP_CHAIN(a) ( (dap_chain_t *) (a)->_inheritor)

DAP_STATIC_INLINE int dap_chain_id_parse(const char *a_id_str, dap_chain_id_t *a_id)
{
    uint64_t l_id;
    int res = dap_id_uint64_parse(a_id_str, &l_id);
    if (!res)
        a_id->uint64 = l_id;
    return res;
}

int dap_chain_init(void);
void dap_chain_deinit(void);

dap_chain_t *dap_chain_create(const char *a_chain_net_name, const char *a_chain_name, dap_chain_net_id_t a_chain_net_id, dap_chain_id_t a_chain_id);

int dap_chain_load_all (dap_chain_t * a_chain);
int dap_chain_save_all (dap_chain_t * a_chain);
bool dap_chain_has_file_store(dap_chain_t * a_chain);

//dap_chain_t * dap_chain_open(const char * a_file_storage,const char * a_file_cache);
void dap_chain_info_dump_log(dap_chain_t * a_chain);

dap_chain_t * dap_chain_find_by_id(dap_chain_net_id_t a_chain_net_id,dap_chain_id_t a_chain_id);
dap_chain_t *dap_chain_load_from_cfg(const char *a_chain_net_name, dap_chain_net_id_t a_chain_net_id, dap_config_t *a_cfg);

void dap_chain_delete(dap_chain_t * a_chain);
void dap_chain_add_callback_notify(dap_chain_t *a_chain, dap_chain_callback_notify_t a_callback, dap_proc_thread_t *a_thread, void *a_arg);
void dap_chain_add_callback_datum_index_notify(dap_chain_t *a_chain, dap_chain_callback_datum_notify_t a_callback, dap_proc_thread_t *a_thread, void *a_callback_arg);
void dap_chain_add_callback_datum_removed_from_index_notify(dap_chain_t *a_chain, dap_chain_callback_datum_removed_notify_t a_callback, dap_proc_thread_t *a_thread, void *a_callback_arg);
void dap_chain_atom_confirmed_notify_add(dap_chain_t *a_chain, dap_chain_callback_notify_t a_callback, void *a_arg, uint64_t a_conf_cnt);
void dap_chain_atom_notify(dap_chain_cell_t *a_chain_cell,  dap_hash_fast_t *a_hash, const uint8_t *a_atom, size_t a_atom_size);
void dap_chain_datum_notify(dap_chain_cell_t *a_chain_cell,  dap_hash_fast_t *a_hash, const uint8_t *a_datum, size_t a_datum_size, int a_ret_code, uint32_t a_action, dap_chain_net_srv_uid_t a_uid);
void dap_chain_datum_removed_notify(dap_chain_cell_t *a_chain_cell,  dap_hash_fast_t *a_hash);
void dap_chain_atom_add_from_threshold(dap_chain_t *a_chain);
dap_chain_atom_ptr_t dap_chain_get_atom_by_hash(dap_chain_t * a_chain, dap_chain_hash_fast_t * a_atom_hash, size_t * a_atom_size);
bool dap_chain_get_atom_last_hash_num(dap_chain_t *a_chain, dap_chain_cell_id_t a_cell_id, dap_hash_fast_t *a_atom_hash, uint64_t *a_atom_num);
DAP_STATIC_INLINE bool dap_chain_get_atom_last_hash(dap_chain_t *a_chain, dap_chain_cell_id_t a_cell_id, dap_hash_fast_t *a_atom_hash)
{
    return dap_chain_get_atom_last_hash_num(a_chain, a_cell_id, a_atom_hash, NULL);
}
ssize_t dap_chain_atom_save(dap_chain_cell_t *a_chain_cell, const uint8_t *a_atom, size_t a_atom_size, dap_hash_fast_t *a_new_atom_hash);
int dap_cert_chain_file_save(dap_chain_datum_t *datum, char *net_name);

const char *dap_chain_type_to_str(dap_chain_type_t a_chain_type);
const char *dap_chain_get_path(dap_chain_t *a_chain);
const char *dap_chain_get_cs_type(dap_chain_t *l_chain);