/*
 * Authors:
 * Dmitriy A. Gearasimov <gerasimov.dmitriy@demlabs.net>
 * Aleksandr Lysikov <alexander.lysikov@demlabs.net>
 * 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 <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>

#ifdef DAP_OS_LINUX
#include <dlfcn.h>
#endif
#include "json.h"
#include "json_object.h"
#include <pthread.h>
#include <dirent.h>
#include "uthash.h"
#include "utlist.h"

#include "dap_chain_net.h"
#include "dap_hash.h"
#include "dap_common.h"
#include "dap_enc_base58.h"
#include "dap_list.h"
#include "dap_string.h"
#include "dap_file_utils.h"
#include "dap_chain.h"
#include "dap_chain_common.h"
#include "dap_chain_net_srv.h"
#include "dap_chain_net_srv_order.h"
#include "dap_chain_net_srv_stream_session.h"
#include "dap_stream_ch_chain_net_srv.h"
#include "dap_chain_cs_blocks.h"
#ifdef DAP_MODULES_DYNAMIC
#include "dap_modules_dynamic_cdb.h"
#endif

#include "dap_chain_node_cli_cmd.h"

#define LOG_TAG "chain_net_srv"

typedef struct service_list {
    dap_chain_net_srv_uid_t uid;
    dap_chain_net_srv_t * srv;
    char name[32];
    UT_hash_handle hh;
} service_list_t;

// list of active services
static service_list_t *s_srv_list = NULL;
// for separate access to s_srv_list
static pthread_mutex_t s_srv_list_mutex = PTHREAD_MUTEX_INITIALIZER;
static int s_cli_net_srv(int argc, char **argv, char **a_str_reply);
static void s_load(const char * a_path);
static void s_load_all();

static bool s_pay_verificator_callback(dap_ledger_t * a_ledger, dap_chain_tx_out_cond_t *a_cond,
                                       dap_chain_datum_tx_t *a_tx_in, bool a_owner);
static bool s_fee_verificator_callback(dap_ledger_t * a_ledger, dap_chain_tx_out_cond_t *a_cond,
                                       dap_chain_datum_tx_t *a_tx_in, bool a_owner);
static int s_str_to_price_unit(char* a_price_unit_str, dap_chain_net_srv_price_unit_uid_t* a_price_unit);

/**
 * @brief dap_chain_net_srv_init
 * @return
 */
int dap_chain_net_srv_init()
{
    dap_chain_ledger_verificator_add(DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_PAY, s_pay_verificator_callback, NULL);
    dap_chain_ledger_verificator_add(DAP_CHAIN_TX_OUT_COND_SUBTYPE_FEE, s_fee_verificator_callback, NULL);
    dap_stream_ch_chain_net_srv_init();

    dap_cli_server_cmd_add ("net_srv", s_cli_net_srv, "Network services managment",
        "net_srv -net <net_name> order find [-direction {sell | buy}] [-srv_uid <Service UID>] [-price_unit <price unit>]\n"
        " [-price_token <Token ticker>] [-price_min <Price minimum>] [-price_max <Price maximum>]\n"
        "\tOrders list, all or by UID and/or class\n"
        "net_srv -net <net_name> order delete -hash <Order hash>\n"
        "\tOrder delete\n"
        "net_srv -net <net_name> order dump -hash <Order hash>\n"
        "\tOrder dump info\n"
        "net_srv -net <net_name> order create -direction {sell | buy} -srv_uid <Service UID> -price <Price>\n"
        " -price_unit <Price Unit> -price_token <token_ticker> -units <units> [-node_addr <Node Address>] [-tx_cond <TX Cond Hash>]\n"
        " [-expires <Unix time when expires>] [-cert <cert name to sign order>]\n"
        " [{-ext <Extension with params> | -region <Region name> -continent <Continent name>}]\n"
#ifdef DAP_MODULES_DYNAMIC
        "\tOrder create\n"
            "net_srv -net <net_name> order static [save | delete]\n"
            "\tStatic nodelist create/delete\n"
            "net_srv -net <net_name> order recheck\n"
            "\tCheck the availability of orders\n"
#endif
        );

    s_load_all();

    return 0;
}

/**
 * @brief s_load_all
 */
void s_load_all()
{
    char * l_net_dir_str = dap_strdup_printf("%s/service.d", dap_config_path());
    DIR * l_net_dir = opendir( l_net_dir_str);
    if ( l_net_dir ) {
        struct dirent * l_dir_entry = NULL;
        while ( (l_dir_entry = readdir(l_net_dir) )!= NULL ){
            if (l_dir_entry->d_name[0]=='\0' || l_dir_entry->d_name[0]=='.')
                continue;
            // don't search in directories
            char l_full_path[MAX_PATH + 1] = {0};
            dap_snprintf(l_full_path, sizeof(l_full_path), "%s/%s", l_net_dir_str, l_dir_entry->d_name);
            if(dap_dir_test(l_full_path)) {
                continue;
            }
            // search only ".cfg" files
            if(strlen(l_dir_entry->d_name) > 4) { // It has non zero name excluding file extension
                if(strncmp(l_dir_entry->d_name + strlen(l_dir_entry->d_name) - 4, ".cfg", 4) != 0) {
                    // its not .cfg file
                    continue;
                }
            }
            log_it(L_DEBUG,"Service config %s try to load", l_dir_entry->d_name);
            //char* l_dot_pos = rindex(l_dir_entry->d_name,'.');
            char* l_dot_pos = strchr(l_dir_entry->d_name,'.');
            if ( l_dot_pos )
                *l_dot_pos = '\0';
            s_load(l_full_path );
        }
        closedir(l_net_dir);
    }
    DAP_DELETE (l_net_dir_str);
}

/**
 * @brief s_load
 * @param a_name
 */
static void s_load(const char * a_path)
{
    log_it ( L_INFO, "Service config %s", a_path);
    // TODO open service
}


/**
 * @brief dap_chain_net_srv_deinit
 */
void dap_chain_net_srv_deinit(void)
{
    // TODO Stop all services

    dap_chain_net_srv_del_all();
}

/**
 * @brief s_cli_net_srv
 * @param argc
 * @param argv
 * @param a_str_reply
 * @return
 */
static int s_cli_net_srv( int argc, char **argv, char **a_str_reply)
{
    int arg_index = 1;
    dap_chain_net_t * l_net = NULL;

    const char * l_hash_out_type = NULL;
    dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-H", &l_hash_out_type);
    if(!l_hash_out_type)
        l_hash_out_type = "hex";
    if(dap_strcmp(l_hash_out_type, "hex") && dap_strcmp(l_hash_out_type, "base58")) {
        dap_cli_server_cmd_set_reply_text(a_str_reply, "invalid parameter -H, valid values: -H <hex | base58>");
        return -1;
    }

    int l_ret = dap_chain_node_cli_cmd_values_parse_net_chain( &arg_index, argc, argv, a_str_reply, NULL, &l_net );
    if ( l_net ) {
        //char * l_orders_group = dap_chain_net_srv_order_get_gdb_group( l_net );

        dap_string_t *l_string_ret = dap_string_new("");
        const char *l_order_str = NULL;
        int l_order_arg_pos = dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "order", &l_order_str);

        // Order direction
        const char *l_direction_str = NULL;
        dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-direction", &l_direction_str);

        const char* l_srv_uid_str = NULL;
        dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-srv_uid", &l_srv_uid_str);

        const char* l_srv_class_str = NULL;
        dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-srv_class", &l_srv_class_str);

        const char* l_node_addr_str = NULL;
        dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-node_addr", &l_node_addr_str);

        const char* l_tx_cond_hash_str = NULL;
        dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-tx_cond", &l_tx_cond_hash_str);

        const char* l_price_str = NULL;
        dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-price", &l_price_str);

        const char* l_expires_str = NULL;
        dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-expires", &l_expires_str);

        const char* l_price_unit_str = NULL;
        dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-price_unit", &l_price_unit_str);

        const char* l_price_token_str = NULL;
        dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-price_token", &l_price_token_str);

        const char* l_ext = NULL;
        dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-ext", &l_ext);

        const char *l_order_hash_str = NULL;
        dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-hash", &l_order_hash_str);

        const char* l_region_str = NULL;
        dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-region", &l_region_str);
        const char* l_continent_str = NULL;
        dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-continent", &l_continent_str);

        int8_t l_continent_num = dap_chain_net_srv_order_continent_to_num(l_continent_str);

        const char *l_units_str = NULL;
        dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-units", &l_units_str);

        char *l_order_hash_hex_str = NULL;
        char *l_order_hash_base58_str = NULL;
        // datum hash may be in hex or base58 format
        if (l_order_hash_str) {
            if(!dap_strncmp(l_order_hash_str, "0x", 2) || !dap_strncmp(l_order_hash_str, "0X", 2)) {
                l_order_hash_hex_str = dap_strdup(l_order_hash_str);
                l_order_hash_base58_str = dap_enc_base58_from_hex_str_to_str(l_order_hash_str);
            } else {
                l_order_hash_hex_str = dap_enc_base58_to_hex_str_from_str(l_order_hash_str);
                l_order_hash_base58_str = dap_strdup(l_order_hash_str);
            }
        }
        if(l_continent_str && l_continent_num <= 0) {
            dap_string_t *l_string_err = dap_string_new("Unrecognized \"-continent\" option=");
            dap_string_append_printf(l_string_err, "\"%s\". Variants: ", l_continent_str);
            int i = 0;
            while(1) {
                const char *l_continent = dap_chain_net_srv_order_continent_to_str(i);
                if(!l_continent)
                    break;
                if(!i)
                    dap_string_append_printf(l_string_err, "\"%s\"", l_continent);
                else
                    dap_string_append_printf(l_string_err, ", \"%s\"", l_continent);
                i++;
            }
            dap_string_append_printf(l_string_ret, "%s\n", l_string_err->str);
            dap_string_free(l_string_err, true);
            l_ret = -1;
        }
        // Update order
        else if(!dap_strcmp(l_order_str, "update")) {

            if(!l_order_hash_str) {
                l_ret = -1;
                dap_string_append(l_string_ret, "Can't find option '-hash'\n");
            }
            else {
                dap_chain_net_srv_order_t * l_order = dap_chain_net_srv_order_find_by_hash_str(l_net, l_order_hash_hex_str);
                if(!l_order) {
                    l_ret = -2;
                    if(!dap_strcmp(l_hash_out_type,"hex"))
                        dap_string_append_printf(l_string_ret, "Can't find order with hash %s\n", l_order_hash_hex_str);
                    else
                        dap_string_append_printf(l_string_ret, "Can't find order with hash %s\n", l_order_hash_base58_str);
                }
                else {
                    if(l_ext) {
                        l_order->ext_size = strlen(l_ext) + 1;
                        l_order = DAP_REALLOC(l_order, sizeof(dap_chain_net_srv_order_t) + l_order->ext_size);
                        memcpy(l_order->ext_n_sign, l_ext, l_order->ext_size);
                    }
                    else
                        dap_chain_net_srv_order_set_continent_region(&l_order, l_continent_num, l_region_str);
                    /*if(l_region_str) {
                        strncpy(l_order->region, l_region_str, MIN(sizeof(l_order->region) - 1, strlen(l_region_str) + 1));
                    }
                    if(l_continent_num>=0)
                        l_order->continent = l_continent_num;*/
                    char *l_new_order_hash_str = dap_chain_net_srv_order_save(l_net, l_order);
                    if (l_new_order_hash_str) {
                        // delete prev order
                        if(dap_strcmp(l_new_order_hash_str, l_order_hash_hex_str))
                            dap_chain_net_srv_order_delete_by_hash_str_sync(l_net, l_order_hash_hex_str);
                        DAP_DELETE(l_new_order_hash_str);
                        dap_string_append_printf(l_string_ret, "order updated\n");
                    } else
                        dap_string_append_printf(l_string_ret, "Order did not updated\n");
                    DAP_DELETE(l_order);
                }
            }

        }
        else if (!dap_strcmp( l_order_str, "find" )) {

            // Order direction
            const char *l_direction_str = NULL;
            dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-direction", &l_direction_str);

            // Select with specified service uid
            const char *l_srv_uid_str = NULL;
            dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-srv_uid", &l_srv_uid_str);


            // Select with specified price units
            const char*  l_price_unit_str = NULL;
            dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-price_unit", &l_price_unit_str);

            // Token ticker
            const char*  l_price_token_str = NULL;
            dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-price_token", &l_price_token_str);

            // Select with price not more than price_min
            const char*  l_price_min_str = NULL;
            dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-price_min", &l_price_min_str);

            // Select with price not more than price_max
            const char*  l_price_max_str = NULL;
            dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-price_max", &l_price_max_str);

            dap_chain_net_srv_order_direction_t l_direction = SERV_DIR_UNDEFINED;
            dap_chain_net_srv_uid_t l_srv_uid={{0}};
            uint256_t l_price_min = {};
            uint256_t l_price_max = {};
            dap_chain_net_srv_price_unit_uid_t l_price_unit={{0}};

            if ( l_direction_str ){
                if (!strcmp(l_direction_str, "sell"))
                    l_direction = SERV_DIR_SELL;
                else if (!strcmp(l_direction_str, "buy"))
                    l_direction = SERV_DIR_BUY;
                else {
                    dap_string_free(l_string_ret, true);
                    dap_cli_server_cmd_set_reply_text(a_str_reply, "Wrong direction of the token was "
                                                                   "specified, possible directions: buy, sell.");
                    return -18;
                }
            }

            if (l_srv_uid_str && dap_id_uint64_parse(l_srv_uid_str ,&l_srv_uid.uint64)) {
                dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't recognize '%s' string as 64-bit id, hex or dec.", l_srv_uid_str);
                return -21;
            }

            if ( l_price_min_str )
                l_price_min = dap_chain_balance_scan(l_price_min_str);

            if ( l_price_max_str )
                l_price_max = dap_chain_balance_scan(l_price_max_str);
            if ( l_price_unit_str)
                l_price_unit.uint32 = (uint32_t) atol ( l_price_unit_str );

            dap_chain_net_srv_order_t * l_orders;
            size_t l_orders_num = 0;
            if( dap_chain_net_srv_order_find_all_by( l_net, l_direction,l_srv_uid,l_price_unit,l_price_token_str,l_price_min, l_price_max,&l_orders,&l_orders_num) == 0 ){
                dap_string_append_printf(l_string_ret, "Found %zu orders:\n", l_orders_num);
                size_t l_orders_size = 0;
                for (size_t i = 0; i< l_orders_num; i++){
                    dap_chain_net_srv_order_t *l_order =(dap_chain_net_srv_order_t *) (((byte_t*) l_orders) + l_orders_size);
                    dap_chain_net_srv_order_dump_to_string(l_order, l_string_ret, l_hash_out_type);
                    l_orders_size += dap_chain_net_srv_order_get_size(l_order);
                    dap_string_append(l_string_ret,"\n");
                }
                l_ret = 0;
                if (l_orders_num)
                    DAP_DELETE(l_orders);
             }else{
                l_ret = -5 ;
                dap_string_append(l_string_ret,"Can't get orders: some internal error or wrong params\n");
            }
        } else if(!dap_strcmp( l_order_str, "dump" )) {
            // Select with specified service uid
            if ( l_order_hash_str ){
                dap_chain_net_srv_order_t * l_order = dap_chain_net_srv_order_find_by_hash_str( l_net, l_order_hash_hex_str );
                if (l_order) {
                    dap_chain_net_srv_order_dump_to_string(l_order,l_string_ret, l_hash_out_type);
                    l_ret = 0;
                }else{
                    l_ret = -7 ;
                    if(!dap_strcmp(l_hash_out_type,"hex"))
                        dap_string_append_printf(l_string_ret,"Can't find order with hash %s\n", l_order_hash_hex_str );
                    else
                        dap_string_append_printf(l_string_ret,"Can't find order with hash %s\n", l_order_hash_base58_str );
                }
            } else {

                dap_chain_net_srv_order_t * l_orders = NULL;
                size_t l_orders_num = 0;
                dap_chain_net_srv_uid_t l_srv_uid={{0}};
                uint256_t l_price_min = {};
                uint256_t l_price_max = {};
                dap_chain_net_srv_price_unit_uid_t l_price_unit={{0}};
                dap_chain_net_srv_order_direction_t l_direction = SERV_DIR_UNDEFINED;

                if( !dap_chain_net_srv_order_find_all_by( l_net,l_direction,l_srv_uid,l_price_unit, NULL, l_price_min, l_price_max,&l_orders,&l_orders_num) ){
                    dap_string_append_printf(l_string_ret,"Found %zd orders:\n",l_orders_num);
                    size_t l_orders_size = 0;
                    for(size_t i = 0; i < l_orders_num; i++) {
                        dap_chain_net_srv_order_t *l_order =(dap_chain_net_srv_order_t *) (((byte_t*) l_orders) + l_orders_size);
                        dap_chain_net_srv_order_dump_to_string(l_order, l_string_ret, l_hash_out_type);
                        l_orders_size += dap_chain_net_srv_order_get_size(l_order);
                        dap_string_append(l_string_ret, "\n");
                    }
                    l_ret = 0;
                }else{
                    l_ret = -5 ;
                    dap_string_append(l_string_ret,"Can't get orders: some internal error or wrong params\n");
                }
                DAP_DELETE(l_orders);
            }
        } else if(!dap_strcmp( l_order_str, "delete" )) {
            // Select with specified service uid
            //const char *l_order_hash_str = NULL;
            //dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-hash", &l_order_hash_str);
            if ( l_order_hash_str ){
                dap_chain_net_srv_order_t * l_order = dap_chain_net_srv_order_find_by_hash_str( l_net, l_order_hash_hex_str );
                if (l_order && dap_chain_net_srv_order_delete_by_hash_str_sync(l_net,l_order_hash_hex_str) == 0){
                    l_ret = 0 ;
                    if(!dap_strcmp(l_hash_out_type,"hex"))
                        dap_string_append_printf(l_string_ret, "Deleted order %s\n", l_order_hash_hex_str);
                    else
                        dap_string_append_printf(l_string_ret, "Deleted order %s\n", l_order_hash_base58_str);
                }else{
                    l_ret = -8 ;
                    if(!dap_strcmp(l_hash_out_type,"hex"))
                        dap_string_append_printf(l_string_ret, "Can't find order with hash %s\n", l_order_hash_hex_str);
                    else
                        dap_string_append_printf(l_string_ret, "Can't find order with hash %s\n", l_order_hash_base58_str);
                }
                DAP_DELETE(l_order);
            } else{
                l_ret = -9 ;
                dap_string_append(l_string_ret,"need -hash param to obtain what the order we need to dump\n");
            }
        } else if(!dap_strcmp( l_order_str, "create" )) {
            if (dap_chain_net_get_role(l_net).enums < NODE_ROLE_MASTER) {
                dap_cli_server_cmd_set_reply_text(a_str_reply, "Node role should be not lower than master\n");
                return -4;
            }
            const char *l_order_cert_name = NULL;
            dap_cli_server_cmd_find_option_val(argv, arg_index, argc, "-cert", &l_order_cert_name);
            if ( l_srv_uid_str && l_price_str && l_price_token_str && l_price_unit_str && l_units_str) {
                dap_chain_net_srv_uid_t l_srv_uid={{0}};
                dap_chain_node_addr_t l_node_addr={0};
                dap_chain_hash_fast_t l_tx_cond_hash={{0}};
                dap_time_t l_expires = 0; // TS when the service expires
                uint256_t l_price = {0};
                char l_price_token[DAP_CHAIN_TICKER_SIZE_MAX]={0};
                dap_chain_net_srv_price_unit_uid_t l_price_unit={{0}};
                dap_chain_net_srv_order_direction_t l_direction = SERV_DIR_UNDEFINED;
                if ( l_direction_str ){
                    if (!strcmp(l_direction_str, "sell")) {
                        l_direction = SERV_DIR_SELL;
                        log_it(L_DEBUG, "Created order to sell");
                    } else if (!strcmp(l_direction_str, "buy")) {
                        l_direction = SERV_DIR_BUY;
                        log_it(L_DEBUG, "Created order to buy");
                    } else {
                        log_it(L_WARNING, "Undefined order direction");
                        dap_string_free(l_string_ret, true);
                        dap_cli_server_cmd_set_reply_text(a_str_reply, "Wrong direction of the token was "
                                                                       "specified, possible directions: buy, sell.");
                        return -18;
                    }
                }

                if (l_expires_str)
                    l_expires = (dap_time_t ) atoll( l_expires_str);
                if (l_srv_uid_str && dap_id_uint64_parse(l_srv_uid_str ,&l_srv_uid.uint64)) {
                    dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't recognize '%s' string as 64-bit id, hex or dec.", l_srv_uid_str);
                    dap_string_free(l_string_ret, true);
                    return -21;
                }
                if (l_node_addr_str){
                    if (dap_chain_node_addr_str_check(l_node_addr_str)) {
                        dap_chain_node_addr_from_str( &l_node_addr, l_node_addr_str );
                    } else {
                        log_it(L_ERROR, "Can't parse \"%s\" as node addr", l_node_addr_str);
                        dap_cli_server_cmd_set_reply_text(a_str_reply, "The order has not been created. "
                                                                       "Failed to convert string representation of '%s' "
                                                                       "to node address.", l_node_addr_str);
                        dap_string_free(l_string_ret, true);
                        return -17;
                    }
                } else {
                    l_node_addr.uint64 = dap_chain_net_get_cur_addr_int(l_net);
                }
                if (l_tx_cond_hash_str)
                    dap_chain_hash_fast_from_str (l_tx_cond_hash_str, &l_tx_cond_hash);
                l_price = dap_chain_balance_scan(l_price_str);

                if (s_str_to_price_unit(l_price_unit_str, &l_price_unit)){
                    log_it(L_ERROR, "Undefined price unit");
                    dap_string_free(l_string_ret, true);
                    dap_cli_server_cmd_set_reply_text(a_str_reply, "Wrong unit type sepcified, possible values: B, KB, MB, SEC, DAY, PCS");
                    return -18;
                }

                uint64_t l_units = atoi(l_units_str);
                strncpy(l_price_token, l_price_token_str, DAP_CHAIN_TICKER_SIZE_MAX - 1);
                size_t l_ext_len = l_ext? strlen(l_ext) + 1 : 0;
                // get cert to order sign
                dap_cert_t *l_cert = NULL;
                dap_enc_key_t *l_key = NULL;
                if(l_order_cert_name) {
                    l_cert = dap_cert_find_by_name(l_order_cert_name);
                    if(l_cert) {
                        l_key = l_cert->enc_key;
                    } else {
                        log_it(L_ERROR, "Can't load cert '%s' for sign order", l_order_cert_name);
                        dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't load cert '%s' for sign "
                                                                       "order", l_order_cert_name);
                        dap_string_free(l_string_ret, true);
                        return -19;
                    }
                } else {
                    dap_cli_server_cmd_set_reply_text(a_str_reply, "The certificate name was not "
                                                                   "specified. Since version 5.2 it is not possible to "
                                                                   "create unsigned orders.");
                    dap_string_free(l_string_ret, true);
                    return -20;
                }
                // create order
                char * l_order_new_hash_str = dap_chain_net_srv_order_create(
                            l_net,l_direction, l_srv_uid, l_node_addr,l_tx_cond_hash, &l_price, l_price_unit,
                            l_price_token, l_expires, (uint8_t *)l_ext, l_ext_len, l_units, l_region_str, l_continent_num, l_key);
                if(l_cert)
                    dap_cert_delete(l_cert);
                if (l_order_new_hash_str)
                    dap_string_append_printf( l_string_ret, "Created order %s\n", l_order_new_hash_str);
                else{
                    dap_string_append_printf( l_string_ret, "Error! Can't created order\n");
                    l_ret = -4;
                }
            } else {
                dap_string_append_printf( l_string_ret, "Missed some required params\n");
                l_ret=-5;
            }
        }
#ifdef DAP_MODULES_DYNAMIC
        else if(!dap_strcmp( l_order_str, "recheck" )) {
            int (*dap_chain_net_srv_vpn_cdb_server_list_check_orders)(dap_chain_net_t *a_net);
            dap_chain_net_srv_vpn_cdb_server_list_check_orders = dap_modules_dynamic_get_cdb_func("dap_chain_net_srv_vpn_cdb_server_list_check_orders");
            int l_init_res = dap_chain_net_srv_vpn_cdb_server_list_check_orders ? dap_chain_net_srv_vpn_cdb_server_list_check_orders(l_net) : -5;
            if (l_init_res >= 0) {
                dap_string_append_printf(l_string_ret, "Orders recheck started\n");
                l_ret = 0;
            } else {
                dap_string_append_printf(l_string_ret, "Orders recheck not started, code %d\n", l_init_res);
                l_ret = -10;
            }

        } else if(!dap_strcmp( l_order_str, "static" )) {
            // find the subcommand directly after the 'order' command
            int l_subcmd_save = dap_cli_server_cmd_find_option_val(argv, l_order_arg_pos + 1, l_order_arg_pos + 2, "save", NULL);
            int l_subcmd_del = dap_cli_server_cmd_find_option_val(argv, l_order_arg_pos + 1, l_order_arg_pos + 2, "delete", NULL) |
                               dap_cli_server_cmd_find_option_val(argv, l_order_arg_pos + 1, l_order_arg_pos + 2, "del", NULL);

            int (*dap_chain_net_srv_vpn_cdb_server_list_static_create)(dap_chain_net_t *a_net) = NULL;
            int (*dap_chain_net_srv_vpn_cdb_server_list_static_delete)(dap_chain_net_t *a_net) = NULL;
            //  find func from dinamic library
            if(l_subcmd_save || l_subcmd_del) {
                dap_chain_net_srv_vpn_cdb_server_list_static_create = dap_modules_dynamic_get_cdb_func("dap_chain_net_srv_vpn_cdb_server_list_static_create");
                dap_chain_net_srv_vpn_cdb_server_list_static_delete = dap_modules_dynamic_get_cdb_func("dap_chain_net_srv_vpn_cdb_server_list_static_delete");
            }
            if(l_subcmd_save) {
                int l_init_res = dap_chain_net_srv_vpn_cdb_server_list_static_create ? dap_chain_net_srv_vpn_cdb_server_list_static_create(l_net) : -5;
                if(l_init_res >= 0){
                    dap_string_append_printf(l_string_ret, "Static node list saved, %d orders in list\n", l_init_res);
                    l_ret = 0;
                }
                else{
                    dap_string_append_printf(l_string_ret, "Static node list not saved, error code %d\n", l_init_res);
                    l_ret = -11;
                }

            } else if(l_subcmd_del) {
                int l_init_res = dap_chain_net_srv_vpn_cdb_server_list_static_delete ? dap_chain_net_srv_vpn_cdb_server_list_static_delete(l_net) : -5;
                if(!l_init_res){
                    dap_string_append_printf(l_string_ret, "Static node list deleted\n");
                    l_ret = 0;
                }
                else if(l_init_res > 0){
                    dap_string_append_printf(l_string_ret, "Static node list already deleted\n");
                    l_ret = -12;
                }
                else
                    dap_string_append_printf(l_string_ret, "Static node list not deleted, error code %d\n", l_init_res);
            } else {
                dap_string_append(l_string_ret, "not found subcommand 'save' or 'delete'\n");
                l_ret = -13;
            }
        }
#endif
        else if (l_order_str) {
            dap_string_append_printf(l_string_ret, "Unrecognized subcommand '%s'", l_order_str);
            l_ret = -14;
        } else {
            dap_string_append_printf(l_string_ret, "Command 'net_srv' requires subcommand 'order'");
            l_ret = -3;
        }
        dap_cli_server_cmd_set_reply_text(a_str_reply, "%s", l_string_ret->str);
        dap_string_free(l_string_ret, true);
    }

    return l_ret;
}

/**
 * @brief s_fee_verificator_callback
 * @param a_ledger
 * @param a_tx_out_hash
 * @param a_cond
 * @param a_tx_in
 * @param a_owner
 * @return
 */
static bool s_fee_verificator_callback(dap_ledger_t *a_ledger, UNUSED_ARG dap_chain_tx_out_cond_t *a_cond,
                                       dap_chain_datum_tx_t *a_tx_in, UNUSED_ARG bool a_owner)
{
    dap_chain_net_t *l_net = dap_chain_net_by_name(a_ledger->net_name);
    if (!l_net)
        return false;
    dap_chain_t *l_chain;
    DL_FOREACH(l_net->pub.chains, l_chain) {
        if (!l_chain->callback_block_find_by_tx_hash)
            continue;
        dap_chain_tx_in_cond_t *l_tx_in_cond = (dap_chain_tx_in_cond_t *)dap_chain_datum_tx_item_get(a_tx_in, 0, TX_ITEM_TYPE_IN_COND, 0);
        if (!l_tx_in_cond)
            return false;
        if (dap_hash_fast_is_blank(&l_tx_in_cond->header.tx_prev_hash))
            return false;
        size_t l_block_size = 0;
        dap_chain_block_t *l_block = (dap_chain_block_t *)l_chain->callback_block_find_by_tx_hash(
                                                    l_chain, &l_tx_in_cond->header.tx_prev_hash, &l_block_size);
        if (!l_block)
            continue;
        dap_sign_t *l_sign_block = dap_chain_block_sign_get(l_block, l_block_size, 0);
        if (!l_sign_block)
            return false;

        // TX sign is already verified, just compare pkeys
        dap_chain_tx_sig_t *l_tx_sig = (dap_chain_tx_sig_t *)dap_chain_datum_tx_item_get(a_tx_in, NULL, TX_ITEM_TYPE_SIG, NULL);
        dap_sign_t *l_sign_tx = dap_chain_datum_tx_item_sign_get_sig(l_tx_sig);
        return dap_sign_match_pkey_signs(l_sign_block, l_sign_tx);
    }
    return false;
}

/**
 * @brief s_pay_verificator_callback
 * @param a_ledger
 * @param a_tx_out
 * @param a_cond
 * @param a_tx_in
 * @param a_owner
 * @return
 */
static bool s_pay_verificator_callback(dap_ledger_t * a_ledger, dap_chain_tx_out_cond_t *a_cond,
                                       dap_chain_datum_tx_t *a_tx_in, bool a_owner)
{
    if (a_owner)
        return true;
    dap_chain_datum_tx_receipt_t *l_receipt = (dap_chain_datum_tx_receipt_t *)
                                               dap_chain_datum_tx_item_get(a_tx_in, NULL, TX_ITEM_TYPE_RECEIPT, NULL);
    if (!l_receipt){
        log_it(L_ERROR, "Can't find receipt.");
        return false;
    }

    // Check provider sign
    dap_sign_t *l_sign = dap_chain_datum_tx_receipt_sign_get(l_receipt, l_receipt->size, 0);

    if (!l_sign){
        log_it(L_ERROR, "Can't get provider sign from receipt.");
        return false;
    }
    dap_sign_type_t l_provider_sign_type = l_sign->header.type;

    if (dap_sign_verify_all(l_sign, dap_sign_get_size(l_sign), &l_receipt->receipt_info, sizeof(l_receipt->receipt_info))){
        log_it(L_ERROR, "Provider sign in receipt not passed verification.");
        return false;
    }

    // Checking the signature matches the provider's signature
    dap_hash_fast_t l_tx_sign_pkey_hash = {};
    dap_hash_fast_t l_provider_pkey_hash = {};
    if (!dap_sign_get_pkey_hash(l_sign, &l_provider_pkey_hash)){
        log_it(L_ERROR, "Can't get pkey hash from provider sign.");
        return false;
    }

    int l_item_size = 0;
    uint8_t* l_sig = dap_chain_datum_tx_item_get(a_tx_in, 0, TX_ITEM_TYPE_SIG, &l_item_size);
    if(!l_sig){
        log_it(L_ERROR, "Can't get item with provider signature from tx");
        return false;
    }

    l_sign = dap_chain_datum_tx_item_sign_get_sig((dap_chain_tx_sig_t *)l_sig);
    if (!l_sign){
        log_it(L_ERROR, "Provider sign from tx sig_item");
        return false;
    }

    if(!dap_sign_get_pkey_hash(l_sign, &l_tx_sign_pkey_hash)){
        log_it(L_ERROR, "Can't get pkey hash from tx provider signature");
        return false;
    }

    if(!dap_hash_fast_compare(&l_tx_sign_pkey_hash, &l_provider_pkey_hash)){
        log_it(L_ERROR, "Provider signature in receipt and tx is different.");
        return false;
    }

    // Check client sign
    l_sign = dap_chain_datum_tx_receipt_sign_get(l_receipt, l_receipt->size, 1);
    if (!l_sign){
        log_it(L_ERROR, "Can't get client signature from receipt.");
        return false;
    }
    dap_hash_fast_t l_pkey_hash = {};
    if (!dap_sign_get_pkey_hash(l_sign, &l_pkey_hash)){
        log_it(L_ERROR, "Can't get pkey hash from receipt client signature");
        return false;
    }

    if(!dap_hash_fast_compare(&l_pkey_hash, &a_cond->subtype.srv_pay.pkey_hash)){
        log_it(L_ERROR, "Client signature in receipt is invalid!");
        return false;
    }

    // Check price is less than maximum
    dap_chain_tx_in_cond_t *l_tx_in_cond = (dap_chain_tx_in_cond_t *)dap_chain_datum_tx_item_get(a_tx_in, 0, TX_ITEM_TYPE_IN_COND, 0);
    dap_chain_datum_tx_t *l_tx_prev = dap_chain_ledger_tx_find_by_hash(a_ledger , &l_tx_in_cond->header.tx_prev_hash);
    dap_chain_tx_out_cond_t *l_prev_out_cond = dap_chain_datum_tx_out_cond_get(l_tx_prev, DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_PAY, NULL);

    uint256_t l_unit_price = {};
    if (l_receipt->receipt_info.units != 0){
        DIV_256(l_receipt->receipt_info.value_datoshi, GET_256_FROM_64(l_receipt->receipt_info.units), &l_unit_price);
    } else {
        return false;
    }

    if( compare256(uint256_0, l_prev_out_cond->subtype.srv_pay.unit_price_max_datoshi) &&
        compare256(l_unit_price, l_prev_out_cond->subtype.srv_pay.unit_price_max_datoshi) > 0){
        log_it(L_ERROR, "Value in receipt is exceed max allowable price.");
        return false;
    }

    // Check out value is equal to value in receipt
    int items_count = 0;
    dap_list_t * items_list = dap_chain_datum_tx_items_get(a_tx_in, TX_ITEM_TYPE_OUT, &items_count);
    dap_chain_addr_t l_provider_addr = {};
    dap_chain_addr_fill(&l_provider_addr, l_provider_sign_type, &l_provider_pkey_hash, dap_chain_net_id_by_name(a_ledger->net_name));

    dap_list_t * list_item = items_list;
    for (int i = 0; i < items_count; i++){
        dap_chain_tx_out_t *l_out = (dap_chain_tx_out_t*)list_item->data;
        if (dap_chain_addr_compare(&l_provider_addr, &l_out->addr))
        {
            if(!compare256(l_out->header.value, l_receipt->receipt_info.value_datoshi)){
                dap_list_free(items_list);
                return true;
            }else{
                dap_list_free(items_list);
                log_it(L_ERROR, "Value in tx out is not equal to value in receipt.");
                return false;

            }
        }
        items_list = items_list->next;
    }
    dap_list_free(items_list);
    log_it(L_ERROR, "Can't find OUT in tx matching provider.");
    return false;
}

int dap_chain_net_srv_price_apply_from_my_order(dap_chain_net_srv_t *a_srv, const char *a_config_section){
    const char *l_wallet_path = dap_config_get_item_str_default(g_config, "resources", "wallets_path", NULL);
    const char *l_wallet_name = dap_config_get_item_str_default(g_config, a_config_section, "wallet", NULL);
    const char *l_net_name = dap_config_get_item_str_default(g_config, a_config_section, "net", NULL);
    if (!l_wallet_path || !l_wallet_name || !l_net_name){
        return -2;
    }
    dap_chain_wallet_t *l_wallet = dap_chain_wallet_open(l_wallet_name, l_wallet_path);
    if (!l_wallet) {
        return -3;
    }
    dap_chain_net_t *l_net = dap_chain_net_by_name(l_net_name);
    if (!l_net) {
        return -4;
    }
    a_srv->grace_period = dap_config_get_item_uint32_default(g_config, a_config_section, "grace_period", 60);
    a_srv->allow_free_srv = dap_config_get_item_bool_default(g_config, a_config_section, "allow_free_srv", false);
    int l_err_code = 0;
    dap_chain_node_addr_t *l_node_addr = NULL;
    l_node_addr = dap_chain_net_get_cur_addr(l_net);
    if (!l_node_addr)
        return -1;
    size_t l_orders_count = 0;
    uint64_t l_max_price_cfg = dap_config_get_item_uint64_default(g_config, a_config_section, "max_price", 0xFFFFFFFFFFFFFFF);
    char *l_gdb_order_group = dap_chain_net_srv_order_get_gdb_group(l_net);
    dap_global_db_obj_t *l_orders = dap_global_db_get_all_sync(l_gdb_order_group, &l_orders_count);
    for (size_t i=0; i < l_orders_count; i++){
        l_err_code = -4;
        dap_chain_net_srv_order_t *l_order = dap_chain_net_srv_order_read(l_orders[i].value, l_orders[i].value_len);
        if (l_order->node_addr.uint64 == l_node_addr->uint64) {
            l_err_code = 0;
            dap_chain_net_srv_price_t *l_price = DAP_NEW_Z(dap_chain_net_srv_price_t);
            if (!l_price) {
                log_it(L_CRITICAL, "Memory allocation error");
                DAP_DEL_Z(l_order);
                dap_global_db_objs_delete(l_orders, l_orders_count);
                return -1;
            }
            l_price->net = l_net;
            l_price->net_name = dap_strdup(l_net->pub.name);
            uint256_t l_max_price = GET_256_FROM_64(l_max_price_cfg); // Change this value when max price wil be calculated
            if (!compare256(l_order->price, uint256_0) || l_order->units == 0 ){
                log_it(L_ERROR, "Invalid order: units count or price unspecified");
                DAP_DELETE(l_price);
                continue;
            }
            l_price->value_datoshi = l_order->price;
            dap_stpcpy(l_price->token, l_order->price_ticker);
            l_price->units = l_order->units;
            l_price->units_uid = l_order->price_unit;
            if (compare256(l_max_price, uint256_0)){
                uint256_t l_price_unit = uint256_0;
                DIV_256(l_price->value_datoshi,  GET_256_FROM_64(l_order->units), &l_price_unit);
                if (compare256(l_price_unit, l_max_price)>0){
                    char *l_price_unit_str = dap_chain_balance_print(l_price_unit), *l_max_price_str = dap_chain_balance_print(l_max_price);
                    log_it(L_ERROR, "Unit price exeeds max permitted value: %s > %s", l_price_unit_str, l_max_price_str);
                    DAP_DELETE(l_price_unit_str);
                    DAP_DELETE(l_max_price_str);
                    DAP_DELETE(l_price);
                    continue;
                }
            }
            l_price->wallet = l_wallet;
            DL_APPEND(a_srv->pricelist, l_price);
            break;
        }
        DAP_DELETE(l_order);
    }
    dap_global_db_objs_delete(l_orders, l_orders_count);
    return l_err_code;
}

int dap_chain_net_srv_parse_pricelist(dap_chain_net_srv_t *a_srv, const char *a_config_section)
{
    int ret = 0;
    if (!a_config_section)
        return ret;
    a_srv->grace_period = dap_config_get_item_uint32_default(g_config, a_config_section, "grace_period", 60);
    uint16_t l_pricelist_count = 0;
    char **l_pricelist = dap_config_get_array_str(g_config, a_config_section, "pricelist", &l_pricelist_count);
    for (uint16_t i = 0; i < l_pricelist_count; i++) {
        dap_chain_net_srv_price_t *l_price = DAP_NEW_Z(dap_chain_net_srv_price_t);
        if (!l_price) {
            log_it(L_CRITICAL, "Memory allocation error");
            return ret;
        }
        short l_iter = 0;
        char *l_price_str = dap_strdup(l_pricelist[i]), *l_ctx;
        for (char *l_price_token = strtok_r(l_price_str, ":", &l_ctx); l_price_token || l_iter == 6; l_price_token = strtok_r(NULL, ":", &l_ctx), ++l_iter) {
            //log_it(L_DEBUG, "Tokenizer: %s", l_price_token);
            switch (l_iter) {
            case 0:
                l_price->net_name = l_price_token;
                if (!(l_price->net = dap_chain_net_by_name(l_price->net_name))) {
                    log_it(L_ERROR, "Error parsing pricelist: can't find network \"%s\"", l_price_token);
                    break;
                }
                continue;
            case 1:
                l_price->value_datoshi = dap_chain_coins_to_balance(l_price_token);
                if (IS_ZERO_256(l_price->value_datoshi)) {
                    log_it(L_ERROR, "Error parsing pricelist: text on 2nd position \"%s\" is not floating number", l_price_token);
                    l_iter = 0;
                    break;
                }
                continue;
            case 2:
                dap_stpcpy(l_price->token, l_price_token);
                continue;
            case 3:
                l_price->units = strtoul(l_price_token, NULL, 10);
                if (!l_price->units) {
                    log_it(L_ERROR, "Error parsing pricelist: text on 4th position \"%s\" is not unsigned integer", l_price_token);
                    l_iter = 0;
                    break;
                }
                continue;
            case 4:
                if (s_str_to_price_unit(l_price_token, &(l_price->units_uid))) {
                    log_it(L_ERROR, "Error parsing pricelist: wrong unit type \"%s\"", l_price_token);
                    l_iter = 0;
                    break;
                }
                continue;
            case 5:
                if (!(l_price->wallet = dap_chain_wallet_open(l_price_token, dap_config_get_item_str_default(g_config, "resources", "wallets_path", NULL)))) {
                    log_it(L_ERROR, "Error parsing pricelist: can't open wallet \"%s\"", l_price_token);
                    l_iter = 0;
                    break;
                }
                continue;
            case 6:
                log_it(L_INFO, "Price item correct, added to service");
                ret++;
                break;
            default:
                break;
            }
            log_it(L_DEBUG, "Done with price item %d", i);
            if (l_iter == 6)
                DL_APPEND(a_srv->pricelist, l_price);
            break; // double break exits tokenizer loop and steps to next price item
        }
        if (l_iter != 6)
            DAP_DELETE(l_price);
        DAP_DELETE(l_price_str);
    }
    return ret;
}

/**
 * @brief dap_chain_net_srv_add
 * @param a_uid
 * @param a_callback_request
 * @param a_callback_response_success
 * @param a_callback_response_error
 * @return
 */
dap_chain_net_srv_t* dap_chain_net_srv_add(dap_chain_net_srv_uid_t a_uid,
                                           const char *a_config_section,
                                           dap_chain_net_srv_callbacks_t* a_callbacks)

{
    service_list_t *l_sdata = NULL;
    dap_chain_net_srv_t * l_srv = NULL;
    dap_chain_net_srv_uid_t l_uid = {.uint64 = a_uid.uint64 }; // Copy to let then compiler to pass args via registers not stack
    pthread_mutex_lock(&s_srv_list_mutex);
    HASH_FIND(hh, s_srv_list, &l_uid, sizeof(l_uid), l_sdata);
    if(!l_sdata) {
        l_srv = DAP_NEW_Z(dap_chain_net_srv_t);
        if (!l_srv) {
            log_it(L_CRITICAL, "Memory allocation error");
            pthread_mutex_unlock(&s_srv_list_mutex);
            return NULL;
        }
        l_srv->uid.uint64 = a_uid.uint64;
        if (a_callbacks)
            l_srv->callbacks = *a_callbacks;
        pthread_mutex_init(&l_srv->banlist_mutex, NULL);
        l_sdata = DAP_NEW_Z(service_list_t);
        if (!l_sdata) {
            log_it(L_CRITICAL, "Memory allocation error");
            DAP_DEL_Z(l_srv);
            pthread_mutex_unlock(&s_srv_list_mutex);
            return NULL;
        }
        l_sdata->uid = l_uid;
        strncpy(l_sdata->name, a_config_section, sizeof(l_sdata->name) - 1);
        l_sdata->srv = l_srv;
        dap_chain_net_srv_price_apply_from_my_order(l_srv, a_config_section);
//        dap_chain_net_srv_parse_pricelist(l_srv, a_config_section);
        HASH_ADD(hh, s_srv_list, uid, sizeof(l_srv->uid), l_sdata);
        if (l_srv->pricelist)
            dap_chain_ledger_tx_add_notify(l_srv->pricelist->net->pub.ledger, dap_stream_ch_chain_net_srv_tx_cond_added_cb, NULL);
    }else{
        log_it(L_ERROR, "Already present service with 0x%016"DAP_UINT64_FORMAT_X, a_uid.uint64);
    }
    pthread_mutex_unlock(&s_srv_list_mutex);
    return l_srv;
}

/**
 * @brief dap_chain_net_srv_del
 * @param a_srv
 */
void dap_chain_net_srv_del(dap_chain_net_srv_t * a_srv)
{
    service_list_t *l_sdata;
    if(!a_srv)
        return;
    pthread_mutex_lock(&s_srv_list_mutex);
    HASH_FIND(hh, s_srv_list, a_srv, sizeof(dap_chain_net_srv_uid_t), l_sdata);
    if(l_sdata) {
        HASH_DEL(s_srv_list, l_sdata);
        pthread_mutex_destroy(&a_srv->banlist_mutex);
        DAP_DELETE(a_srv);
        DAP_DELETE(l_sdata);
    }
    pthread_mutex_unlock(&s_srv_list_mutex);
}

/**
 * @brief dap_chain_net_srv_call_write_all
 * @param a_client
 */
void dap_chain_net_srv_call_write_all(dap_stream_ch_t * a_client)
{
    service_list_t *l_sdata, *l_sdata_tmp;
    pthread_mutex_lock(&s_srv_list_mutex);
    HASH_ITER(hh, s_srv_list , l_sdata, l_sdata_tmp)
    {
        if (l_sdata->srv->callbacks.stream_ch_write)
            l_sdata->srv->callbacks.stream_ch_write(l_sdata->srv, a_client);
    }
    pthread_mutex_unlock(&s_srv_list_mutex);
}

/**
 * @brief dap_chain_net_srv_call_opened_all
 * @param a_client
 */
void dap_chain_net_srv_call_opened_all(dap_stream_ch_t * a_client)
{
    service_list_t *l_sdata, *l_sdata_tmp;
    pthread_mutex_lock(&s_srv_list_mutex);
    HASH_ITER(hh, s_srv_list , l_sdata, l_sdata_tmp)
    {
        if (l_sdata->srv->callbacks.stream_ch_opened)
            l_sdata->srv->callbacks.stream_ch_opened(l_sdata->srv, a_client);
    }
    pthread_mutex_unlock(&s_srv_list_mutex);
}

void dap_chain_net_srv_call_closed_all(dap_stream_ch_t * a_client)
{
    service_list_t *l_sdata, *l_sdata_tmp;
    pthread_mutex_lock(&s_srv_list_mutex);
    HASH_ITER(hh, s_srv_list , l_sdata, l_sdata_tmp)
    {
        if (l_sdata->srv->callbacks.stream_ch_closed)
            l_sdata->srv->callbacks.stream_ch_closed(l_sdata->srv, a_client);
    }
    pthread_mutex_unlock(&s_srv_list_mutex);
}



/**
 * @brief dap_chain_net_srv_del_all
 * @param a_srv
 */
void dap_chain_net_srv_del_all(void)
{
    service_list_t *l_sdata, *l_sdata_tmp;
    pthread_mutex_lock(&s_srv_list_mutex);
    HASH_ITER(hh, s_srv_list , l_sdata, l_sdata_tmp)
    {
        // Clang bug at this, l_sdata should change at every loop cycle
        HASH_DEL(s_srv_list, l_sdata);
        pthread_mutex_destroy(&l_sdata->srv->banlist_mutex);
        DAP_DELETE(l_sdata->srv);
        DAP_DELETE(l_sdata);
    }
    pthread_mutex_unlock(&s_srv_list_mutex);
}

/**
 * @brief dap_chain_net_srv_get
 * @param a_uid
 * @return
 */
dap_chain_net_srv_t * dap_chain_net_srv_get(dap_chain_net_srv_uid_t a_uid)
{
    service_list_t *l_sdata = NULL;
    pthread_mutex_lock(&s_srv_list_mutex);
    HASH_FIND(hh, s_srv_list, &a_uid, sizeof(dap_chain_net_srv_uid_t), l_sdata);
    pthread_mutex_unlock(&s_srv_list_mutex);
    return (l_sdata) ? l_sdata->srv : NULL;
}

/**
 * @brief dap_chain_net_srv_get_by_name
 * @param a_client
 */
dap_chain_net_srv_t* dap_chain_net_srv_get_by_name(const char *a_name)
{
    if(!a_name)
        return NULL;
    dap_chain_net_srv_t *l_srv = NULL;
    service_list_t *l_sdata, *l_sdata_tmp;
    pthread_mutex_lock(&s_srv_list_mutex);
    HASH_ITER(hh, s_srv_list , l_sdata, l_sdata_tmp)
    {
        if(!dap_strcmp(l_sdata->name, a_name))
            l_srv = l_sdata->srv;
    }
    pthread_mutex_unlock(&s_srv_list_mutex);
    return l_srv;
}

/**
 * @brief dap_chain_net_srv_count
 * @return
 */
 size_t dap_chain_net_srv_count(void)
{
    size_t l_count = 0;
    service_list_t *l_sdata, *l_sdata_tmp;
    pthread_mutex_lock(&s_srv_list_mutex);
    HASH_ITER(hh, s_srv_list , l_sdata, l_sdata_tmp)
    {
        l_count++;
    }
    pthread_mutex_unlock(&s_srv_list_mutex);
    return l_count;
}

/**
 * @brief dap_chain_net_srv_list
 * @return
 */
const dap_chain_net_srv_uid_t * dap_chain_net_srv_list(void)
{
    static dap_chain_net_srv_uid_t *l_srv_uids = NULL;
    static size_t l_count_last = 0;
    size_t l_count_cur = 0;
    dap_list_t *l_list = NULL;
    service_list_t *l_sdata, *l_sdata_tmp;
    pthread_mutex_lock(&s_srv_list_mutex);
    // count the number of services and save them in list
    HASH_ITER(hh, s_srv_list , l_sdata, l_sdata_tmp)
    {
        l_list = dap_list_append(l_list, l_sdata);
        l_count_cur++;
    }
    // fill the output array
    if(l_count_cur > 0) {
        if(l_count_cur != l_count_last) {
            DAP_DELETE(l_srv_uids);
            l_srv_uids = DAP_NEW_SIZE(dap_chain_net_srv_uid_t, sizeof(dap_chain_net_srv_uid_t) * l_count_cur);
        }
        for(size_t i = 0; i < l_count_cur; i++) {
            service_list_t *l_sdata = l_list->data;
            memcpy(l_srv_uids + i, &l_sdata->uid, sizeof(dap_chain_net_srv_uid_t));
        }
    }
    // save new number of services
    l_count_last = l_count_cur;
    pthread_mutex_unlock(&s_srv_list_mutex);
    dap_list_free(l_list);
    return l_srv_uids;
}

/**
 * @brief dap_chain_net_srv_issue_receipt
 * @param a_srv
 * @param a_usage
 * @param a_price
 * @return
 */
dap_chain_datum_tx_receipt_t * dap_chain_net_srv_issue_receipt(dap_chain_net_srv_t *a_srv,
                                                               dap_chain_net_srv_price_t * a_price,
                                                               const void * a_ext, size_t a_ext_size)
{
    dap_chain_datum_tx_receipt_t * l_receipt = dap_chain_datum_tx_receipt_create(
                    a_srv->uid, a_price->units_uid, a_price->units, a_price->value_datoshi, a_ext, a_ext_size);
    // Sign with our wallet
    return dap_chain_datum_tx_receipt_sign_add(l_receipt, dap_chain_wallet_get_key(a_price->wallet, 0));
}

/**
 * @brief dap_chain_net_srv_issue_receipt
 * @param a_str_price_unit
 * @param a_price_unit
 * @return 0 if OK, other if error
 */
int s_str_to_price_unit(char* a_price_unit_str, dap_chain_net_srv_price_unit_uid_t* a_price_unit) {
    if (!a_price_unit_str || !a_price_unit)
        return -1;
    a_price_unit->enm = dap_chain_srv_str_to_unit_enum(a_price_unit_str);
    return a_price_unit->enm != SERV_UNIT_UNDEFINED ? 0 : -1;
}