/*
 * 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 WIN32
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0600
#include <winsock2.h>
#include <windows.h>
#include <mswsock.h>
#include <ws2tcpip.h>
#include <io.h>
#include <wepoll.h>
#endif

#include <pthread.h>


#include "uthash.h"
#include "utlist.h"
#include "dap_list.h"
#include "dap_string.h"

#include "dap_chain.h"
#include "dap_chain_net.h"
#include "dap_chain_net_srv.h"
#include "dap_chain_net_srv_order.h"

#include "dap_chain_node_cli_cmd.h"

#define LOG_TAG "chain_net_srv"

static size_t m_uid_count;
static dap_chain_net_srv_uid_t * m_uid;

typedef struct service_list {
    dap_chain_net_srv_uid_t uid;
    dap_chain_net_srv_t * srv;
    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);
/**
 * @brief dap_chain_net_srv_init
 * @return
 */
int dap_chain_net_srv_init(void)
{
    m_uid = NULL;
    m_uid_count = 0;
    if( dap_chain_net_srv_order_init() != 0 )
        return -1;

    dap_chain_node_cli_cmd_item_create ("net_srv", s_cli_net_srv, "Network services managment",
        "net_srv -net <chain net name> order find [-direction <sell|buy>][-srv_uid <Service UID>] [-srv_class <Service Class>] [-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 <chain net name> order delete -hash <Order hash>\n"
        "\tOrder delete\n"
        "net_srv -net <chain net name> order dump -hash <Order hash>\n"
        "\tOrder dump info\n"
        "net_srv -net <chain net name> order create -direction <sell|buy> -srv_uid <Service UID> -srv_class <Service Class> -price <Price>\\\n"
        "        -price_unit <Price Unit> -price_ticker <Token ticker> -node_addr <Node Address> -tx_cond <TX Cond Hash> \\\n"
        "        [-expires <Unix time when expires>]\\\n"
        "\tOrder create\n" );

    return 0;
}

/**
 * @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;

    int 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;
        dap_chain_node_cli_find_option_val(argv, arg_index, argc, "order", &l_order_str);
        if ( l_order_str == NULL){
            dap_string_append_printf( l_string_ret, "Expected subcommand. Variants: find, dump, create, delete\n");
            ret=-3;
        } else if ( strcmp( l_order_str, "find" ) == 0 ){

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

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

            // Select with specified service class
            const char *l_srv_class_str = NULL;
            dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-srv_class", &l_srv_class_str);

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

            // Token ticker
            const char*  l_price_token_str = NULL;
            dap_chain_node_cli_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_chain_node_cli_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_chain_node_cli_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}};
            dap_chain_net_srv_class_t l_srv_class= SERV_CLASS_UNDEFINED;
            uint64_t l_price_min=0, l_price_max =0 ;
            dap_chain_net_srv_price_unit_uid_t l_price_unit={{0}};

            if ( l_direction_str ){
                if ( strcmp(l_direction_str, "sell")==0)
                    l_direction = SERV_DIR_SELL;
                else if ( strcmp(l_direction_str, "buy")==0)
                    l_direction = SERV_DIR_BUY;
            }

            if ( l_srv_uid_str)
                l_srv_uid.uint64 = (uint128_t) atoll( l_srv_uid_str);

            if ( l_srv_class_str )
                l_srv_class = (dap_chain_net_srv_class_t) atoi( l_srv_class_str );

            if ( l_price_min_str )
                l_price_min = (uint64_t) atoll ( l_price_min_str );

            if ( l_price_max_str )
                l_price_max = (uint64_t) atoll ( 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_size =0;
            if( dap_chain_net_srv_order_find_all_by( l_net, l_direction,l_srv_uid,l_srv_class,l_price_unit,l_price_min, l_price_max,&l_orders,&l_orders_size) == 0 ){
                dap_string_append_printf(l_string_ret,"Found %u orders:\n",l_orders_size);
                for (size_t i = 0; i< l_orders_size; i++){
                    dap_chain_net_srv_order_dump_to_string(l_orders+i,l_string_ret);
                    dap_string_append(l_string_ret,"\n");
                }
                ret = 0;
            }else{
                ret = -5 ;
                dap_string_append(l_string_ret,"Can't get orders: some internal error or wrong params\n");
            }

        }else if( strcmp( l_order_str, "dump" ) == 0 ){
            // Select with specified service uid
            const char *l_order_hash_str = NULL;
            dap_chain_node_cli_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( l_net, l_order_hash_str );
                if (l_order){
                    dap_chain_net_srv_order_dump_to_string(l_order,l_string_ret);
                    ret = 0;
                }else{
                    ret = -7 ;
                    dap_string_append_printf(l_string_ret,"Can't find order with hash %s\n", l_order_hash_str );
                }
            } else{

                dap_chain_net_srv_order_t * l_orders;
                size_t l_orders_size =0;
                dap_chain_net_srv_uid_t l_srv_uid={{0}};
                dap_chain_net_srv_class_t l_srv_class= SERV_CLASS_UNDEFINED;
                uint64_t l_price_min=0, l_price_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;
                char l_price_token[DAP_CHAIN_TICKER_SIZE_MAX]={0};


                if( dap_chain_net_srv_order_find_all_by( l_net,l_direction,l_srv_uid,l_srv_class,l_price_unit, l_price_token, l_price_min, l_price_max,&l_orders,&l_orders_size) == 0 ){
                    dap_string_append_printf(l_string_ret,"Found %u orders:\n",l_orders_size);
                    for (size_t i = 0; i< l_orders_size; i++){
                        dap_chain_net_srv_order_dump_to_string(l_orders+i,l_string_ret);
                        dap_string_append(l_string_ret,"\n");
                    }
                    ret = 0;
                }else{
                    ret = -5 ;
                    dap_string_append(l_string_ret,"Can't get orders: some internal error or wrong params\n");
                }
            }
        }else if( strcmp( l_order_str, "delete" ) == 0 ){
            // Select with specified service uid
            const char *l_order_hash_str = NULL;
            dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-hash", &l_order_hash_str);
            if ( l_order_hash_str ){
                if (dap_chain_net_srv_order_delete_by_hash_str(l_net,l_order_hash_str) == 0){
                    ret = 0 ;
                    dap_string_append_printf(l_string_ret,"Deleted order %s\n", l_order_hash_str );
                }else{
                    ret = -8 ;
                    dap_string_append_printf(l_string_ret,"Can't find order with hash %s\n", l_order_hash_str );
                }
            } else{
                ret = -9 ;
                dap_string_append(l_string_ret,"need -hash param to obtain what the order we need to dump\n");
            }
        }else if( strcmp( l_order_str, "create" ) == 0 ){
            // Order direction
            const char *l_direction_str = NULL;
            dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-direction", &l_direction_str);

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

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

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

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

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

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

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

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

            const char*  l_comments = NULL;
            dap_chain_node_cli_find_option_val(argv, arg_index, argc, "-comments", &l_comments);

            if ( l_srv_uid_str && l_srv_class_str && l_node_addr_str && l_tx_cond_hash_str && l_price_str ) {
                dap_chain_net_srv_uid_t l_srv_uid={{0}};
                dap_chain_net_srv_class_t l_srv_class= SERV_CLASS_UNDEFINED;
                dap_chain_node_addr_t l_node_addr={0};
                dap_chain_hash_fast_t l_tx_cond_hash={{0}};
                dap_chain_time_t l_expires=0; // TS when the service expires
                uint64_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")==0)
                        l_direction = SERV_DIR_SELL;
                    else if ( strcmp(l_direction_str, "buy")==0)
                        l_direction = SERV_DIR_BUY;
                }


                if (l_expires_str)
                    l_expires = (dap_chain_time_t ) atoll( l_expires_str);
                l_srv_uid.uint64 = (uint64_t) atoll( l_srv_uid_str);
                l_srv_class = (dap_chain_net_srv_class_t) atoi( l_srv_class_str );
                dap_chain_node_addr_from_str( &l_node_addr, l_node_addr_str );
                dap_chain_str_to_hash_fast (l_tx_cond_hash_str, &l_tx_cond_hash);
                l_price = (uint64_t) atoll ( l_price_str );
                l_price_unit.uint32 = (uint32_t) atol ( l_price_unit_str );

                char * l_order_new_hash_str = dap_chain_net_srv_order_create(
                            l_net,l_direction, l_srv_uid, l_srv_class, l_node_addr,l_tx_cond_hash, l_price, l_price_unit,
                            l_price_token, l_expires,l_comments);
                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");
                    ret = -4;
                }
            } else {
                dap_string_append_printf( l_string_ret, "Missed some required params\n");
                ret=-5;
            }
        } else {
            dap_string_append_printf( l_string_ret, "Unknown subcommand \n");
            ret=-3;
        }
        dap_chain_node_cli_set_reply_text(a_str_reply, l_string_ret->str);
        dap_string_free(l_string_ret, true);
    }

    return ret;
}

/**
 * @brief dap_chain_net_srv_add
 * @param a_srv
 */
void dap_chain_net_srv_add(dap_chain_net_srv_t * a_srv)
{
    service_list_t *l_sdata = NULL;
    pthread_mutex_lock(&s_srv_list_mutex);
    HASH_FIND(hh, s_srv_list, &(a_srv->uid), sizeof(a_srv->uid), l_sdata);
    if(l_sdata == NULL) {
        l_sdata = DAP_NEW_Z(service_list_t);
        memcpy(&l_sdata->uid, &a_srv->uid, sizeof(dap_chain_net_srv_uid_t));
        l_sdata->srv = DAP_NEW(dap_chain_net_srv_t);
        memcpy(&l_sdata->srv, a_srv, sizeof(dap_chain_net_srv_t));
        HASH_ADD(hh, s_srv_list, uid, sizeof(a_srv->uid), l_sdata);
    }
    pthread_mutex_unlock(&s_srv_list_mutex);
}

/**
 * @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;
    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) {
        DAP_DELETE(l_sdata);
        HASH_DEL(s_srv_list, l_sdata);
    }
    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)
    {
        DAP_DELETE(l_sdata);
        HASH_DEL(s_srv_list, 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_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;
}