/*
 * Authors:
 * Dmitriy A. Gearasimov <naeper@demlabs.net>
 * Alexander Lysikov <alexander.lysikov@demlabs.net>
 * DeM Labs Inc.   https://demlabs.net

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

#pragma once

#include <pthread.h>
#include <stdbool.h>

#include "uthash.h"
#include "dap_client.h"
#include "dap_chain_node.h"

// connection states
typedef enum dap_chain_node_client_state {
    NODE_CLIENT_STATE_ERROR = -1,
    NODE_CLIENT_STATE_DISCONNECTED = 0,
    NODE_CLIENT_STATE_GET_NODE_ADDR = 1,
    NODE_CLIENT_STATE_NODE_ADDR_LEASED = 2,
    NODE_CLIENT_STATE_PING = 3,
    NODE_CLIENT_STATE_PONG = 4,
    NODE_CLIENT_STATE_CONNECTING = 5,
    NODE_CLIENT_STATE_ESTABLISHED = 100,
    NODE_CLIENT_STATE_SYNC_GDB_UPDATES = 101,
    NODE_CLIENT_STATE_SYNC_GDB = 102,
    NODE_CLIENT_STATE_SYNC_GDB_RVRS = 103,
    NODE_CLIENT_STATE_SYNC_CHAINS_UPDATES = 110,
    NODE_CLIENT_STATE_SYNC_CHAINS = 111,
    NODE_CLIENT_STATE_SYNC_CHAINS_RVRS = 112,
    NODE_CLIENT_STATE_SYNCED = 120,
    NODE_CLIENT_STATE_CHECKED = 130,
} dap_chain_node_client_state_t;

typedef struct dap_chain_node_client dap_chain_node_client_t;

typedef void (*dap_chain_node_client_callback_t)(dap_chain_node_client_t *, void*);
typedef void (*dap_chain_node_client_callback_stage_t)(dap_chain_node_client_t *, dap_client_stage_t, void * );
typedef void (*dap_chain_node_client_callback_error_t)(dap_chain_node_client_t *, int, void *);

typedef struct dap_chain_node_client_callbacks{
    dap_chain_node_client_callback_t connected;
    dap_chain_node_client_callback_t disconnected;
    dap_chain_node_client_callback_t delete;
    dap_chain_node_client_callback_stage_t stage;
    dap_chain_node_client_callback_error_t error;
} dap_chain_node_client_callbacks_t;

// state for a client connection
typedef struct dap_chain_node_client {
    dap_chain_node_client_state_t state;

    bool sync_gdb;
    bool sync_chains;

    dap_chain_cell_id_t cell_id;

    dap_client_t *client;
    dap_stream_worker_t * stream_worker;

    // Update section
    dap_chain_t * cur_chain; // Current chain to update
    dap_chain_cell_t * cur_cell; // Current cell to update

    // Channel chain
    dap_stream_ch_t * ch_chain;
    uint128_t ch_chain_uuid;
    // Channel chain net
    dap_stream_ch_t * ch_chain_net;
    uint128_t ch_chain_net_uuid;

    dap_chain_node_info_t * info;
    dap_events_t *events;

    dap_chain_net_t * net;
    char last_error[128];

    // Timer
    dap_events_socket_t * timer_update_states;
    dap_events_socket_handler_t *own_esh;


    #ifndef _WIN32
    pthread_cond_t wait_cond;
    #else
    HANDLE wait_cond;
    #endif
    pthread_mutex_t wait_mutex;

    // For hash indexing
    UT_hash_handle hh;
    dap_chain_node_addr_t cur_node_addr;
    dap_chain_node_addr_t remote_node_addr;
    struct in_addr remote_ipv4;
    struct in6_addr remote_ipv6;

    bool keep_connection;

    bool is_reconnecting;
    // callbacks
    dap_chain_node_client_callbacks_t callbacks;
    void * callbacks_arg;
} dap_chain_node_client_t;
#define DAP_CHAIN_NODE_CLIENT(a) (a ? (dap_chain_node_client_t *) (a)->_inheritor : NULL)

int dap_chain_node_client_init(void);

void dap_chain_node_client_deinit(void);


dap_chain_node_client_t* dap_chain_node_client_create_n_connect(dap_chain_net_t * a_net, dap_chain_node_info_t *a_node_info,
                                                                const char *a_active_channels,dap_chain_node_client_callbacks_t *a_callbacks,
                                                                void * a_callback_arg );


dap_chain_node_client_t* dap_chain_node_client_connect_channels(dap_chain_net_t * a_net, dap_chain_node_info_t *a_node_info,  const char *a_active_channels);
/**
 * Create handshake to server
 *
 * return a connection handle, or NULL, if an error
 */
dap_chain_node_client_t* dap_chain_node_client_connect(dap_chain_net_t * a_net, dap_chain_node_info_t *node_info);


/**
 * Reset client state to connected state if it is connected
 */
void dap_chain_node_client_reset(dap_chain_node_client_t *a_client);
/**
 * Close connection to server, delete chain_node_client_t *client
 */
void dap_chain_node_client_close(dap_chain_node_client_t *client);

/**
 * Send stream request to server
 */
int dap_chain_node_client_send_ch_pkt(dap_chain_node_client_t *a_client, uint8_t a_ch_id, uint8_t a_type,
        const void *a_buf, size_t a_buf_size);

/**
 * wait for the complete of request
 *
 * timeout_ms timeout in milliseconds
 * waited_state state which we will wait, sample NODE_CLIENT_STATE_CONNECT or NODE_CLIENT_STATE_SENDED
 * return -1 false, 0 timeout, 1 end of connection or sending data
 */
int dap_chain_node_client_wait(dap_chain_node_client_t *a_client, int a_waited_state, int a_timeout_ms);

int dap_chain_node_client_set_callbacks(dap_client_t *a_client, uint8_t a_ch_id);

int dap_chain_node_client_send_nodelist_req(dap_chain_node_client_t *a_client);

static inline const char * dap_chain_node_client_state_to_str( dap_chain_node_client_state_t a_state)
{
    switch (a_state) {
        case NODE_CLIENT_STATE_ERROR: return "ERROR";
        case NODE_CLIENT_STATE_DISCONNECTED: return "DISCONNECTED";
        case NODE_CLIENT_STATE_GET_NODE_ADDR: return "GET_NODE_ADDR";
        case NODE_CLIENT_STATE_NODE_ADDR_LEASED: return "NODE_ADDR_LEASED";
        case NODE_CLIENT_STATE_PING: return "PING";
        case NODE_CLIENT_STATE_PONG: return "PONG";
        case NODE_CLIENT_STATE_CONNECTING: return "CONNECT";
        case NODE_CLIENT_STATE_ESTABLISHED: return "CONNECTED";
        case NODE_CLIENT_STATE_SYNC_GDB: return "SYNC_GDB";
        case NODE_CLIENT_STATE_SYNC_CHAINS: return "SYNC_CHAINS";
        case NODE_CLIENT_STATE_SYNCED: return "SYNCED";
        case NODE_CLIENT_STATE_CHECKED: return "CHECKED";
        default: return "(Undefined node client state)";
    }

}