/*
 * Authors:
 * Dmitriy A. Gearasimov <gerasimov.dmitriy@demlabs.net>
 * Alexander Lysikov <alexander.lysikov@demlabs.net>
 * DeM Labs Inc.   https://demlabs.net
 * DeM Labs Open source community https://github.com/demlabsinc
 * Copyright  (c) 2017-2019
 * 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 <stdint.h>
#include <stdbool.h>
#include "dap_common.h"
#include "dap_hash.h"
#include "dap_list.h"
#include "dap_math_ops.h"
#include "dap_chain_common.h"
#include "dap_chain_datum_token.h"
#include "dap_chain_datum_tx.h"
#include "dap_chain_datum_tx_in_ems.h"
#include "dap_chain_datum_tx_items.h"
#include "dap_chain_net.h"

#define DAP_CHAIN_NET_SRV_TRANSFER_ID 0x07
#define DAP_CHAIN_NET_SRV_BLOCK_REWARD_ID 0x08

typedef struct dap_ledger {
    dap_chain_net_t *net;
    void *_internal;
} dap_ledger_t;

/**
 * @brief Error codes for accepting a transaction to the ledger.
 */
typedef enum dap_ledger_check_error {
    DAP_LEDGER_CHECK_OK = 0,
    DAP_LEDGER_CHECK_INVALID_ARGS,
    DAP_LEDGER_CHECK_INVALID_SIZE,
    DAP_LEDGER_CHECK_ALREADY_CACHED,
    DAP_LEDGER_CHECK_PARSE_ERROR,
    DAP_LEDGER_CHECK_APPLY_ERROR,
    DAP_LEDGER_CHECK_NOT_ENOUGH_MEMORY,
    DAP_LEDGER_CHECK_INTEGER_OVERFLOW,
    DAP_LEDGER_CHECK_NOT_ENOUGH_VALID_SIGNS,
    DAP_LEDGER_CHECK_TICKER_NOT_FOUND,
    DAP_LEDGER_CHECK_ZERO_VALUE,
    DAP_LEDGER_CHECK_ADDR_FORBIDDEN,
    DAP_LEDGER_CHECK_WHITELISTED,
    /* TX check return codes */
    DAP_LEDGER_TX_CHECK_IN_EMS_ALREADY_USED,
    DAP_LEDGER_TX_CHECK_STAKE_LOCK_IN_EMS_ALREADY_USED,
    DAP_LEDGER_TX_CHECK_EMISSION_NOT_FOUND,
    DAP_LEDGER_TX_CHECK_TX_NO_VALID_INPUTS,
    DAP_LEDGER_TX_CHECK_STAKE_LOCK_INVALID_TOKEN,
    DAP_LEDGER_TX_CHECK_STAKE_LOCK_NO_OUT_COND_FOR_IN_EMS,
    DAP_LEDGER_TX_CHECK_NO_OUT_EXT_FOR_GIRDLED_IN_EMS,
    DAP_LEDGER_TX_CHECK_NO_OUT_ITEMS_FOR_BASE_TX,
    DAP_LEDGER_TX_CHECK_STAKE_LOCK_UNEXPECTED_VALUE,
    DAP_LEDGER_TX_CHECK_STAKE_LOCK_OTHER_TICKER_EXPECTED,
    DAP_LEDGER_TX_CHECK_OUT_ITEM_ALREADY_USED,
    DAP_LEDGER_TX_CHECK_PREV_TX_NOT_FOUND,
    DAP_LEDGER_TX_CHECK_PREV_OUT_ITEM_NOT_FOUND,
    DAP_LEDGER_TX_CHECK_PREV_OUT_ITEM_MISSTYPED,
    DAP_LEDGER_TX_CHECK_PKEY_HASHES_DONT_MATCH,
    DAP_LEDGER_TX_CHECK_PREV_OUT_ALREADY_USED_IN_CURRENT_TX,
    DAP_LEDGER_TX_CHECK_NO_VERIFICATOR_SET,
    DAP_LEDGER_TX_CHECK_VERIFICATOR_CHECK_FAILURE,
    DAP_LEDGER_TX_CHECK_SUM_INS_NOT_EQUAL_SUM_OUTS,
    DAP_LEDGER_TX_CHECK_REWARD_ITEM_ALREADY_USED,
    DAP_LEDGER_TX_CHECK_REWARD_ITEM_ILLEGAL,
    DAP_LEDGER_TX_CHECK_NO_MAIN_TICKER,
    DAP_LEDGER_TX_CHECK_UNEXPECTED_TOKENIZED_OUT,
    DAP_LEDGER_TX_CHECK_NOT_ENOUGH_FEE,
    DAP_LEDGER_TX_CHECK_NOT_ENOUGH_TAX,
    /* Emisssion check return codes */
    DAP_LEDGER_EMISSION_CHECK_VALUE_EXCEEDS_CURRENT_SUPPLY,
    DAP_LEDGER_EMISSION_CHECK_LEGACY_FORBIDDEN,
    /* Token declaration/update return codes */
    DAP_LEDGER_TOKEN_ADD_CHECK_NOT_ENOUGH_UNIQUE_SIGNS,
    DAP_LEDGER_TOKEN_ADD_CHECK_LEGACY_FORBIDDEN,
    DAP_LEDGER_TOKEN_ADD_CHECK_TSD_INVALID_SUPPLY,
    DAP_LEDGER_TOKEN_ADD_CHECK_TSD_INVALID_ADDR,
    DAP_LEDGER_TOKEN_ADD_CHECK_TSD_ADDR_MISMATCH,
    DAP_LEDGER_TOKEN_ADD_CHECK_TSD_PKEY_MISMATCH,
    DAP_LEDGER_TOKEN_ADD_CHECK_TSD_FORBIDDEN,
    DAP_LEDGER_TOKEN_ADD_CHECK_TSD_OTHER_TICKER_EXPECTED
} dap_ledger_check_error_t;

DAP_STATIC_INLINE const char *dap_ledger_check_error_str(dap_ledger_check_error_t a_error)
{
    switch (a_error) {
    case DAP_LEDGER_CHECK_OK: return "No error";
    case DAP_LEDGER_CHECK_INVALID_ARGS: return "Invalid arguments";
    case DAP_LEDGER_CHECK_INVALID_SIZE: return "Incorrect size of datum or datum's content";
    case DAP_LEDGER_CHECK_ALREADY_CACHED: return "Datum already cached in ledger";
    case DAP_LEDGER_CHECK_PARSE_ERROR: return "Incorrect datum interrnal structure, can't pasre it";
    case DAP_LEDGER_CHECK_APPLY_ERROR: return "Datum can't be applied";
    case DAP_LEDGER_CHECK_NOT_ENOUGH_MEMORY: return "Not enough memory";
    case DAP_LEDGER_CHECK_INTEGER_OVERFLOW: return "Incorrect datum values relationship lead to integer overflow, can't process";
    case DAP_LEDGER_CHECK_NOT_ENOUGH_VALID_SIGNS: return "No enough valid signatures in datum";
    case DAP_LEDGER_CHECK_TICKER_NOT_FOUND: return "Can't find specified token ticker";
    case DAP_LEDGER_CHECK_ZERO_VALUE: return "Unacceptable zero value";
    case DAP_LEDGER_CHECK_ADDR_FORBIDDEN: return "Specified address is forbidden";
    case DAP_LEDGER_CHECK_WHITELISTED: return "Datum is in hard accept list";
    /* TX check return codes */
    case DAP_LEDGER_TX_CHECK_IN_EMS_ALREADY_USED: return "Double spend attempt for emission";
    case DAP_LEDGER_TX_CHECK_STAKE_LOCK_IN_EMS_ALREADY_USED: return "Double spend attempt for stake-lock emission";
    case DAP_LEDGER_TX_CHECK_EMISSION_NOT_FOUND: return "Specified emission not found in ledger";
    case DAP_LEDGER_TX_CHECK_TX_NO_VALID_INPUTS: return "Transaction has no valid inputs, can't process";
    case DAP_LEDGER_TX_CHECK_STAKE_LOCK_INVALID_TOKEN: return "Incorrect deledated token specified in stake-lock transaction";
    case DAP_LEDGER_TX_CHECK_STAKE_LOCK_NO_OUT_COND_FOR_IN_EMS: return "Condtional output for stake-lock emission not found";
    case DAP_LEDGER_TX_CHECK_NO_OUT_EXT_FOR_GIRDLED_IN_EMS: return "Tokenized output for stake-lock girdled emission not found";
    case DAP_LEDGER_TX_CHECK_NO_OUT_ITEMS_FOR_BASE_TX: return "Output for basic transaction not found";
    case DAP_LEDGER_TX_CHECK_STAKE_LOCK_UNEXPECTED_VALUE: return "Incorrect value for stake-lock emission, should be stake * rate";
    case DAP_LEDGER_TX_CHECK_STAKE_LOCK_OTHER_TICKER_EXPECTED: return "Incorrect token ticker for stake-lock emission";
    case DAP_LEDGER_TX_CHECK_OUT_ITEM_ALREADY_USED: return "Double spend attempt for transaction output";
    case DAP_LEDGER_TX_CHECK_PREV_TX_NOT_FOUND: return "No previous transaction found";
    case DAP_LEDGER_TX_CHECK_PREV_OUT_ITEM_NOT_FOUND: return "Specified output number not found in previous transaction";
    case DAP_LEDGER_TX_CHECK_PREV_OUT_ITEM_MISSTYPED: return "Previuos transaction output has unknown type, possible ledger corruption";
    case DAP_LEDGER_TX_CHECK_PKEY_HASHES_DONT_MATCH: return "Trying to spend transaction output from wrongful wallet";
    case DAP_LEDGER_TX_CHECK_PREV_OUT_ALREADY_USED_IN_CURRENT_TX: return "Double spend attempt within single transaction";
    case DAP_LEDGER_TX_CHECK_NO_VERIFICATOR_SET: return "No verificator found for specified conditional ipnput";
    case DAP_LEDGER_TX_CHECK_VERIFICATOR_CHECK_FAILURE: return "Verificator check return error";
    case DAP_LEDGER_TX_CHECK_SUM_INS_NOT_EQUAL_SUM_OUTS: return "Sum of transaction outputs isn't equal to sum of its inputs";
    case DAP_LEDGER_TX_CHECK_REWARD_ITEM_ALREADY_USED: return "Double spend attempt for reward";
    case DAP_LEDGER_TX_CHECK_REWARD_ITEM_ILLEGAL: return "Wrongful reward item in transaction";
    case DAP_LEDGER_TX_CHECK_NO_MAIN_TICKER: return "Can't calculate main ticker found for transaction";
    case DAP_LEDGER_TX_CHECK_UNEXPECTED_TOKENIZED_OUT: return "Tokenized out is forbidden for single-channel trandactions";
    case DAP_LEDGER_TX_CHECK_NOT_ENOUGH_FEE: return "Not enough network fee for transaction processing";
    case DAP_LEDGER_TX_CHECK_NOT_ENOUGH_TAX: return "Not enough sovereign tax provided with current transaction";
    /* Emisssion check return codes */
    case DAP_LEDGER_EMISSION_CHECK_VALUE_EXCEEDS_CURRENT_SUPPLY: return "Value of emission execeeds current token supply";
    case DAP_LEDGER_EMISSION_CHECK_LEGACY_FORBIDDEN: return "Legacy type of emissions are present for old chains comliance only";
    /* Token declaration/update return codes */
    case DAP_LEDGER_TOKEN_ADD_CHECK_NOT_ENOUGH_UNIQUE_SIGNS: return "Not all token signs is unique";
    case DAP_LEDGER_TOKEN_ADD_CHECK_LEGACY_FORBIDDEN: return "Legacy type of tokens are present for old chains comliance only";
    case DAP_LEDGER_TOKEN_ADD_CHECK_TSD_INVALID_SUPPLY: return "Specified supply must be greater than current one";
    case DAP_LEDGER_TOKEN_ADD_CHECK_TSD_INVALID_ADDR: return "Specified address has invalid format";
    case DAP_LEDGER_TOKEN_ADD_CHECK_TSD_ADDR_MISMATCH: return "Specified address can't be processed cause double (for adding) or absent (for removing)";
    case DAP_LEDGER_TOKEN_ADD_CHECK_TSD_PKEY_MISMATCH: return "Specified public key or its hash can't be processed cause double (for adding) or absent (for removing)";
    case DAP_LEDGER_TOKEN_ADD_CHECK_TSD_FORBIDDEN: return "Specified TSD section type is not allowed in datum token of specified type";
    case DAP_LEDGER_TOKEN_ADD_CHECK_TSD_OTHER_TICKER_EXPECTED: return "Incorrect token ticker for delegated token";
    default: return "Unknown error";
    }
}

typedef enum dap_chan_ledger_notify_opcodes{
    DAP_LEDGER_NOTIFY_OPCODE_ADDED = 'a', // 0x61
    DAP_LEDGER_NOTIFY_OPCODE_DELETED = 'd', // 0x64 
} dap_chan_ledger_notify_opcodes_t;
typedef enum dap_chain_tx_tag_action_type {    

    //subtags, till 32
    DAP_CHAIN_TX_TAG_ACTION_UNKNOWN  =              1 << 1,
    
    DAP_CHAIN_TX_TAG_ACTION_TRANSFER_REGULAR =      1 << 2,
    DAP_CHAIN_TX_TAG_ACTION_TRANSFER_COMISSION =    1 << 3,
    DAP_CHAIN_TX_TAG_ACTION_TRANSFER_CROSSCHAIN =   1 << 4,
    DAP_CHAIN_TX_TAG_ACTION_TRANSFER_REWARD =       1 << 5,

    DAP_CHAIN_TX_TAG_ACTION_OPEN =                  1 << 6,
    DAP_CHAIN_TX_TAG_ACTION_USE =                   1 << 7,
    DAP_CHAIN_TX_TAG_ACTION_EXTEND =                1 << 8,
    DAP_CHAIN_TX_TAG_ACTION_CHANGE =                1 << 9,
    DAP_CHAIN_TX_TAG_ACTION_CLOSE =                 1 << 10,

    DAP_CHAIN_TX_TAG_ACTION_VOTING =                1 << 11,
    DAP_CHAIN_TX_TAG_ACTION_VOTE =                  1 << 12,
    
    
    DAP_CHAIN_TX_TAG_ACTION_ALL =                          ~0,
} dap_chain_tx_tag_action_type_t;


typedef struct dap_ledger_datum_iter {
    dap_chain_net_t *net;
    dap_chain_datum_tx_t *cur;
    dap_chain_hash_fast_t cur_hash;
    bool is_unspent;
    int ret_code;
    void *cur_ledger_tx_item;
} dap_ledger_datum_iter_t;

typedef struct dap_ledger_datum_iter_data {
    char * token_ticker;
    dap_chain_tx_tag_action_type_t action_tag;
} dap_ledger_datum_iter_data_t;

typedef int (*dap_ledger_verificator_callback_t)(dap_ledger_t *a_ledger, dap_chain_tx_out_cond_t *a_tx_out_cond, dap_chain_datum_tx_t *a_tx_in, bool a_owner);
typedef void (*dap_ledger_updater_callback_t)(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx, dap_chain_tx_out_cond_t *a_prev_cond);
typedef void (* dap_ledger_tx_add_notify_t)(void *a_arg, dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx, dap_chan_ledger_notify_opcodes_t a_opcode);
typedef void (* dap_ledger_bridged_tx_notify_t)(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx, dap_hash_fast_t *a_tx_hash, void *a_arg, dap_chan_ledger_notify_opcodes_t a_opcode);
typedef bool (*dap_ledger_cache_tx_check_callback_t)(dap_ledger_t *a_ledger, dap_hash_fast_t *a_tx_hash);
typedef struct dap_chain_net dap_chain_net_t;
typedef bool (*dap_chain_ledger_voting_callback_t)(dap_ledger_t *a_ledger, dap_chain_tx_item_type_t a_type, dap_chain_datum_tx_t *a_tx, bool a_apply);
typedef bool (*dap_chain_ledger_voting_delete_callback_t)(dap_ledger_t *a_ledger, dap_chain_tx_item_type_t a_type, dap_chain_datum_tx_t *a_tx);
typedef bool (*dap_ledger_tag_check_callback_t)(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx, dap_chain_datum_tx_item_groups_t *a_items_grp, dap_chain_tx_tag_action_type_t *a_action);


//Change this UUID to automatically reload ledger cache on next node startup
#define DAP_LEDGER_CACHE_RELOAD_ONCE_UUID "0c92b759-a565-448f-b8bd-99103dacf7fc"

// Checks the emission of the token, usualy on zero chain
#define DAP_LEDGER_CHECK_TOKEN_EMISSION     0x0001

// Check double spending in local cell
#define DAP_LEDGER_CHECK_LOCAL_DS           0x0002

// Check the double spending in all cells
#define DAP_LEDGER_CHECK_CELLS_DS           0x0100

#define DAP_LEDGER_CACHE_ENABLED            0x0200

#define DAP_LEDGER_MAPPED                   0x0400

#define DAP_LEDGER_THRESHOLD_ENABLED        0x0800

// Error code for no previous transaction (for stay in mempool)
#define DAP_CHAIN_CS_VERIFY_CODE_TX_NO_PREVIOUS     DAP_LEDGER_TX_CHECK_PREV_TX_NOT_FOUND
// Error code for no emission for a transaction (for stay in mempool)
#define DAP_CHAIN_CS_VERIFY_CODE_TX_NO_EMISSION     DAP_LEDGER_TX_CHECK_EMISSION_NOT_FOUND
// Error code for not enough valid emission signs (for stay in mempool)
#define DAP_CHAIN_CS_VERIFY_CODE_NOT_ENOUGH_SIGNS   DAP_LEDGER_CHECK_NOT_ENOUGH_VALID_SIGNS
// Error code for no decree for anchor (for stay in mempool)
#define DAP_CHAIN_CS_VERIFY_CODE_NO_DECREE          -1113

#define DAP_LEDGER_TOKENS_STR              "tokens"
#define DAP_LEDGER_EMISSIONS_STR           "emissions"
#define DAP_LEDGER_STAKE_LOCK_STR          "stake_lock"
#define DAP_LEDGER_TXS_STR                 "txs"
#define DAP_LEDGER_SPENT_TXS_STR           "spent_txs"
#define DAP_LEDGER_BALANCES_STR            "balances"

int dap_ledger_init();
void dap_ledger_deinit();

dap_ledger_t *dap_ledger_create(dap_chain_net_t *a_net, uint16_t a_flags);

// Remove dap_ledger_t structure
void dap_ledger_handle_free(dap_ledger_t *a_ledger);

// Load ledger from mempool
//int dap_ledger_load(const char *a_net_name, const char *a_chain_name);

void dap_ledger_set_local_cell_id(dap_ledger_t *a_ledger, dap_chain_cell_id_t a_local_cell_id);

DAP_STATIC_INLINE char *dap_ledger_get_gdb_group(dap_ledger_t *a_ledger, const char *a_suffix)
{
    return a_ledger && a_ledger->net->pub.name && a_suffix
            ? dap_strdup_printf("local.ledger-cache.%s.%s", a_ledger->net->pub.name, a_suffix)
            : NULL;
}

/**
 * Add new transaction to the cache
 *
 * return 1 OK, -1 error
 */
int dap_ledger_tx_add(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx, dap_hash_fast_t *a_tx_hash, bool a_from_threshold, dap_ledger_datum_iter_data_t *a_datum_index_data);
int dap_ledger_tx_load(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx, dap_chain_hash_fast_t *a_tx_hash, dap_ledger_datum_iter_data_t *a_datum_index_data);
int dap_ledger_tx_remove(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx, dap_hash_fast_t *a_tx_hash);

int dap_ledger_tx_add_check(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx, size_t a_datum_size, dap_hash_fast_t *a_datum_hash);

/**
 * Print list transaction from ledger
 *
 */

json_object * dap_ledger_token_tx_item_list(dap_ledger_t * a_ledger, dap_chain_addr_t *a_addr, const char *a_hash_out_type, bool a_unspent_only);

/**
 * Check token ticker existance
 *
 */

dap_chain_datum_token_t *dap_ledger_token_ticker_check(dap_ledger_t * a_ledger, const char *a_token_ticker);

/**
 * Add new token datum
 *
 */

int dap_ledger_token_add(dap_ledger_t *a_ledger, byte_t *a_token, size_t a_token_size);
int dap_ledger_token_load(dap_ledger_t *a_ledger, byte_t *a_token, size_t a_token_size);
int dap_ledger_token_add_check(dap_ledger_t *a_ledger, byte_t *a_token, size_t a_token_size);
json_object *dap_ledger_token_info(dap_ledger_t *a_ledger, size_t a_limit, size_t a_offset);
json_object *dap_ledger_token_info_by_name(dap_ledger_t *a_ledger, const char *a_token_ticker);

// Get all token-declarations
dap_list_t* dap_ledger_token_decl_all(dap_ledger_t *a_ledger);

json_object *dap_ledger_threshold_info(dap_ledger_t *a_ledger, size_t a_limit, size_t a_offset, dap_hash_fast_t *a_threshold_hash, bool a_head);
json_object *dap_ledger_balance_info(dap_ledger_t *a_ledger, size_t a_limit, size_t a_offset, bool a_head);

size_t dap_ledger_token_get_auth_signs_valid(dap_ledger_t *a_ledger, const char *a_token_ticker);
size_t dap_ledger_token_get_auth_signs_total(dap_ledger_t *a_ledger, const char *a_token_ticker);
dap_list_t *dap_ledger_token_get_auth_pkeys_hashes(dap_ledger_t *a_ledger, const char *a_token_ticker);
uint256_t dap_ledger_token_get_emission_rate(dap_ledger_t *a_ledger, const char *a_token_ticker);

/**
 * Add token emission datum
 */
int dap_ledger_token_emission_add(dap_ledger_t *a_ledger, byte_t *a_token_emission, size_t a_token_emission_size, dap_hash_fast_t *a_emission_hash);
int dap_ledger_token_emission_load(dap_ledger_t *a_ledger, byte_t *a_token_emission, size_t a_token_emission_size, dap_hash_fast_t *a_token_emission_hash);

// Checking a new transaction before adding to the cache
int dap_ledger_token_emission_add_check(dap_ledger_t *a_ledger, byte_t *a_token_emission, size_t a_token_emission_size, dap_chain_hash_fast_t *a_emission_hash);

/* Add stake-lock item */
int dap_ledger_emission_for_stake_lock_item_add(dap_ledger_t *a_ledger, const dap_chain_hash_fast_t *a_tx_hash);

dap_chain_datum_token_emission_t *dap_ledger_token_emission_find(dap_ledger_t *a_ledger, const dap_chain_hash_fast_t *a_token_emission_hash);

const char* dap_ledger_tx_get_token_ticker_by_hash(dap_ledger_t *a_ledger,dap_chain_hash_fast_t *a_tx_hash);

void dap_ledger_addr_get_token_ticker_all_depricated(dap_ledger_t *a_ledger, dap_chain_addr_t * a_addr,
        char *** a_tickers, size_t * a_tickers_size);

void dap_ledger_addr_get_token_ticker_all(dap_ledger_t *a_ledger, dap_chain_addr_t * a_addr,
        char *** a_tickers, size_t * a_tickers_size);

const char *dap_ledger_get_description_by_ticker(dap_ledger_t *a_ledger, const char *a_token_ticker);

bool dap_ledger_tx_poa_signed(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx);

//TX service-tags
bool dap_ledger_deduct_tx_tag(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx, char **a_service_name, dap_chain_net_srv_uid_t *uid, dap_chain_tx_tag_action_type_t *action);
const char *dap_ledger_tx_action_str(dap_chain_tx_tag_action_type_t a_tag);
dap_chain_tx_tag_action_type_t dap_ledger_tx_action_str_to_action_t(const char *a_str);

bool dap_ledger_tx_service_info(dap_ledger_t *a_ledger, dap_hash_fast_t *a_tx_hash, 
                                dap_chain_net_srv_uid_t *a_uid, char **a_service_name,  dap_chain_tx_tag_action_type_t *a_action);


int dap_ledger_service_add(dap_chain_net_srv_uid_t a_uid, char *tag_str, dap_ledger_tag_check_callback_t a_callback);

const char *dap_ledger_tx_calculate_main_ticker(dap_ledger_t *a_ledger, dap_chain_datum_tx_t *a_tx, int *a_ledger_rc);

/**
 * Delete all transactions from the cache
 */
void dap_ledger_purge(dap_ledger_t *a_ledger, bool a_preserve_db);

/**
 * Return number transactions from the cache
 */
unsigned dap_ledger_count(dap_ledger_t *a_ledger);
uint64_t dap_ledger_count_from_to(dap_ledger_t * a_ledger, dap_nanotime_t a_ts_from, dap_nanotime_t a_ts_to);

/**
 * Check whether used 'out' items
 */
bool dap_ledger_tx_hash_is_used_out_item(dap_ledger_t *a_ledger, dap_chain_hash_fast_t *a_tx_hash, int a_idx_out, dap_hash_fast_t *a_out_spender);

/**
 * Retun true if reward was collected before
 */
bool dap_ledger_is_used_reward(dap_ledger_t *a_ledger, dap_hash_fast_t *a_block_hash, dap_hash_fast_t *a_sign_pkey_hash);

/**
 * Calculate balance of addr
 *
 */
uint256_t dap_ledger_calc_balance(dap_ledger_t *a_ledger, const dap_chain_addr_t *a_addr,
        const char *a_token_ticker);

uint256_t dap_ledger_calc_balance_full(dap_ledger_t *a_ledger, const dap_chain_addr_t *a_addr,
            const char *a_token_ticker);

/**
 * Get transaction in the cache by hash
 *
 * return transaction, or NULL if transaction not found in the cache
 */
dap_chain_datum_tx_t *dap_ledger_tx_find_by_hash(dap_ledger_t *a_ledger, const dap_chain_hash_fast_t *a_tx_hash);
dap_chain_datum_tx_t *dap_ledger_tx_unspent_find_by_hash(dap_ledger_t *a_ledger, const dap_chain_hash_fast_t *a_tx_hash);
dap_hash_fast_t *dap_ledger_get_final_chain_tx_hash(dap_ledger_t *a_ledger, dap_chain_tx_item_type_t a_cond_type, dap_chain_hash_fast_t *a_tx_hash);
dap_hash_fast_t *dap_ledger_get_first_chain_tx_hash(dap_ledger_t *a_ledger, dap_chain_datum_tx_t * a_tx, dap_chain_tx_out_cond_t *a_cond_out);

 // Get the transaction in the cache by the addr in out item
dap_chain_datum_tx_t* dap_ledger_tx_find_by_addr(dap_ledger_t *a_ledger, const char * a_token,
         const dap_chain_addr_t *a_addr, dap_chain_hash_fast_t *a_tx_first_hash);

bool dap_ledger_tx_check_recipient(dap_ledger_t* a_ledger, dap_chain_hash_fast_t* a_tx_prev_hash, dap_chain_addr_t *a_addr);

// Get the transaction in the cache by the public key that signed the transaction, starting with a_tx_first_hash
const dap_chain_datum_tx_t* dap_ledger_tx_find_by_pkey(dap_ledger_t *a_ledger,
        char *a_public_key, size_t a_public_key_size, dap_chain_hash_fast_t *a_tx_first_hash);

// Get the transaction in the cache with the out_cond item
dap_chain_datum_tx_t* dap_ledger_tx_cache_find_out_cond(dap_ledger_t *a_ledger, dap_chain_tx_out_cond_subtype_t a_cond_type,
                                                              dap_chain_hash_fast_t *a_tx_first_hash, dap_chain_tx_out_cond_t **a_out_cond,
                                                              int *a_out_cond_idx, char *a_token_ticker);

// Get all transactions from the cache with the specified out_cond items
dap_list_t* dap_ledger_tx_cache_find_out_cond_all(dap_ledger_t *a_ledger, dap_chain_net_srv_uid_t a_srv_uid);

// Get the value from all transactions in the cache with out_cond item
uint256_t dap_ledger_tx_cache_get_out_cond_value(dap_ledger_t *a_ledger, dap_chain_tx_out_cond_subtype_t a_cond_type, dap_chain_addr_t *a_addr,
                                                       dap_chain_tx_out_cond_t **tx_out_cond);

// Get the list of 'out' items from previous transactions with summary value >= than a_value_need
// Put this summary value to a_value_transfer
dap_list_t *dap_ledger_get_list_tx_outs_with_val(dap_ledger_t *a_ledger, const char *a_token_ticker, const dap_chain_addr_t *a_addr_from,
                                                       uint256_t a_value_need, uint256_t *a_value_transfer);
dap_list_t *dap_ledger_get_list_tx_outs(dap_ledger_t *a_ledger, const char *a_token_ticker, const dap_chain_addr_t *a_addr_from,
                                        uint256_t *a_value_transfer);
// Get the list of 'out_cond' items with summary value >= than a_value_need
dap_list_t *dap_ledger_get_list_tx_cond_outs_with_val(dap_ledger_t *a_ledger, const char *a_token_ticker,  const dap_chain_addr_t *a_addr_from,
        dap_chain_tx_out_cond_subtype_t a_subtype, uint256_t a_value_need, uint256_t *a_value_transfer);

dap_list_t *dap_ledger_get_list_tx_cond_outs(dap_ledger_t *a_ledger, const char *a_token_ticker,  const dap_chain_addr_t *a_addr_from,
                                             dap_chain_tx_out_cond_subtype_t a_subtype, uint256_t *a_value_transfer);
// Add new verificator callback with associated subtype. Returns 1 if callback replaced, overwise returns 0
int dap_ledger_verificator_add(dap_chain_tx_out_cond_subtype_t a_subtype, dap_ledger_verificator_callback_t a_callback,
                                     dap_ledger_updater_callback_t a_callback_added, dap_ledger_updater_callback_t a_callback_deleted);
// Add new verificator callback for voting. Returns 1 if callback replaced, overwise returns 0
int dap_chain_ledger_voting_verificator_add(dap_chain_ledger_voting_callback_t a_callback, dap_chain_ledger_voting_delete_callback_t a_callback_delete);
// Getting a list of transactions from the ledger.
dap_list_t * dap_ledger_get_txs(dap_ledger_t *a_ledger, size_t a_count, size_t a_page, bool a_reverse, bool a_unspent_only);

//bool dap_ledger_fee_verificator(dap_ledger_t* a_ledger, dap_chain_tx_out_cond_t* a_cond, dap_chain_datum_tx_t* a_tx, bool a_owner);

dap_ledger_datum_iter_t *dap_ledger_datum_iter_create(dap_chain_net_t *a_net);
void dap_ledger_datum_iter_delete(dap_ledger_datum_iter_t *a_iter);
dap_chain_datum_tx_t *dap_ledger_datum_iter_get_first(dap_ledger_datum_iter_t *a_iter);
dap_chain_datum_tx_t *dap_ledger_datum_iter_get_next(dap_ledger_datum_iter_t *a_iter);
dap_chain_datum_tx_t *dap_ledger_datum_iter_get_last(dap_ledger_datum_iter_t *a_iter);

void dap_ledger_tx_add_notify(dap_ledger_t *a_ledger, dap_ledger_tx_add_notify_t a_callback, void *a_arg);
void dap_ledger_bridged_tx_notify_add(dap_ledger_t *a_ledger, dap_ledger_bridged_tx_notify_t a_callback, void *a_arg);


bool dap_ledger_cache_enabled(dap_ledger_t *a_ledger);
void dap_ledger_set_cache_tx_check_callback(dap_ledger_t *a_ledger, dap_ledger_cache_tx_check_callback_t a_callback);
dap_chain_tx_out_cond_t* dap_chain_ledger_get_tx_out_cond_linked_to_tx_in_cond(dap_ledger_t *a_ledger, dap_chain_tx_in_cond_t *a_in_cond);
void dap_leger_load_end(dap_ledger_t *a_ledger);