/*
 * Authors:
 * Dmitriy A. Gerasimov <kahovski@gmail.com>
 * 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 (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 <dap_client.h>

#include <stdlib.h>
#include <sys/types.h>
#include <assert.h>
#include <errno.h>

#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#include <mswsock.h>
#include <ws2tcpip.h>
#include <io.h>
#else
#include <sys/socket.h>
#include <sys/un.h>
#endif

#include "dap_common.h"
#include "dap_string.h"
#include "dap_strfuncs.h"
#include "dap_chain_node_cli.h" // for UNIX_SOCKET_FILE
#include "dap_app_cli.h"
#include "dap_app_cli_net.h"

static int s_status;

//callback function to receive http data
static void dap_app_cli_http_read(uint64_t *socket, dap_app_cli_cmd_state_t *l_cmd)
{
    ssize_t l_recv_len = recv(*socket, &l_cmd->cmd_res[l_cmd->cmd_res_cur], DAP_CLI_HTTP_RESPONSE_SIZE_MAX, 0);
    if (l_recv_len == -1) {
        s_status = DAP_CLI_ERROR_SOCKET;
        return;
    }
    l_cmd->cmd_res_cur +=(size_t) l_recv_len;
    switch (s_status) {
        case 1: {   // Find content length
            const char *l_cont_len_str = "Content-Length: ";
            char *l_str_ptr = strstr(l_cmd->cmd_res, l_cont_len_str);
            if (l_str_ptr && strstr(l_str_ptr, "\r\n")) {
                l_cmd->cmd_res_len = atoi(l_str_ptr + strlen(l_cont_len_str));
                if (l_cmd->cmd_res_len == 0) {
                    s_status = DAP_CLI_ERROR_FORMAT;
                } else {
                    s_status++;
                }
            } else {
                break;
            }
        }
        case 2: {   // Find header end and throw out header
            const char *l_head_end_str = "\r\n\r\n";
            char *l_str_ptr = strstr(l_cmd->cmd_res, l_head_end_str);
            if (l_str_ptr) {
                l_str_ptr += strlen(l_head_end_str);
                size_t l_head_size = l_str_ptr - l_cmd->cmd_res;
                memmove(l_cmd->cmd_res, l_str_ptr, l_cmd->cmd_res_cur - l_head_size);
                l_cmd->cmd_res_cur -= l_head_size;
                s_status++;
            } else {
                break;
            }
        }
        default:
        case 3: {   // Complete command reply
            if (l_cmd->cmd_res_cur == l_cmd->cmd_res_len) {
                l_cmd->cmd_res[l_cmd->cmd_res_cur] = 0;
                s_status = 0;
            }
        } break;
    }
}

/**
 * @brief dap_app_cli_connect
 * @details Connect to node unix socket server
 * @param a_socket_path
 * @return if connect established, else NULL
 */
dap_app_cli_connect_param_t* dap_app_cli_connect(const char *a_socket_path)
{
    // set socket param
    int buffsize = DAP_CLI_HTTP_RESPONSE_SIZE_MAX;
#ifdef WIN32
    // TODO connect to the named pipe "\\\\.\\pipe\\node_cli.pipe"
    uint16_t l_cli_port = dap_config_get_item_uint16 ( g_config, "conserver", "listen_port_tcp");
    if (!l_cli_port)
        return NULL;
    SOCKET l_socket = socket(AF_INET, SOCK_STREAM, 0);
    setsockopt((SOCKET)l_socket, SOL_SOCKET, SO_SNDBUF, (char *)&buffsize, sizeof(int) );
    setsockopt((SOCKET)l_socket, SOL_SOCKET, SO_RCVBUF, (char *)&buffsize, sizeof(int) );
#else
    if (!a_socket_path) {
        return NULL;
    }
    // create socket
    int l_socket = socket(AF_UNIX, SOCK_STREAM, 0);
    if (l_socket < 0) {
        return NULL;
    }
    setsockopt(l_socket, SOL_SOCKET, SO_SNDBUF, (void*) &buffsize, sizeof(buffsize));
    setsockopt(l_socket, SOL_SOCKET, SO_RCVBUF, (void*) &buffsize, sizeof(buffsize));
#endif
    // connect
    int l_addr_len;
#ifdef WIN32
    struct sockaddr_in l_remote_addr;
    l_remote_addr.sin_family = AF_INET;
    IN_ADDR _in_addr = { { .S_addr = htonl(INADDR_LOOPBACK) } };
    l_remote_addr.sin_addr = _in_addr;
    l_remote_addr.sin_port = l_cli_port;
    l_addr_len = sizeof(struct sockaddr_in);
#else
    struct sockaddr_un l_remote_addr;
    l_remote_addr.sun_family =  AF_UNIX;
    strcpy(l_remote_addr.sun_path, a_socket_path);
    l_addr_len = SUN_LEN(&l_remote_addr);
#endif
    if (connect(l_socket, (struct sockaddr *)&l_remote_addr, l_addr_len) == SOCKET_ERROR) {
#ifdef __WIN32
            _set_errno(WSAGetLastError());
#endif
        printf("Socket connection err: %d\n", errno);
        closesocket(l_socket);
        return NULL;
    }
    uint64_t *l_ret = DAP_NEW(uint64_t);
    *l_ret = l_socket;
    return l_ret;
}

/**
 * Send request to kelvin-node
 *
 * return 0 if OK, else error code
 */
int dap_app_cli_post_command( dap_app_cli_connect_param_t *a_socket, dap_app_cli_cmd_state_t *a_cmd )
{
    if(!a_socket || !a_cmd || !a_cmd->cmd_name) {
        assert(0);
        return -1;
    }
    s_status = 1;
    a_cmd->cmd_res = DAP_NEW_Z_SIZE(char, DAP_CLI_HTTP_RESPONSE_SIZE_MAX);
    a_cmd->cmd_res_cur = 0;
    dap_string_t *l_cmd_data = dap_string_new(a_cmd->cmd_name);
    if (a_cmd->cmd_param) {
        for (int i = 0; i < a_cmd->cmd_param_count; i++) {
            if (a_cmd->cmd_param[i]) {
                dap_string_append(l_cmd_data, "\r\n");
                dap_string_append(l_cmd_data, a_cmd->cmd_param[i]);
            }
        }
    }
    dap_string_append(l_cmd_data, "\r\n\r\n");
    dap_string_t *l_post_data = dap_string_new("");
    dap_string_printf(l_post_data, "POST /connect HTTP/1.1\r\n"
                                   "Host: localhost\r\n"
                                   "Content-Type: text/text\r\n"
                                   "Content-Length: %d\r\n"
                                   "\r\n"
                                   "%s", l_cmd_data->len, l_cmd_data->str);
    send(*a_socket, l_post_data->str, l_post_data->len, 0);

    //wait for command execution
    time_t l_start_time = time(NULL);
    while(s_status > 0) {
        dap_app_cli_http_read(a_socket, a_cmd);
        if (time(NULL) - l_start_time > DAP_CLI_HTTP_TIMEOUT)
            return DAP_CLI_ERROR_TIMEOUT;
    }
    // process result
    if (a_cmd->cmd_res && !s_status) {
        char **l_str = dap_strsplit(a_cmd->cmd_res, "\r\n", 1);
        int l_cnt = dap_str_countv(l_str);
        char *l_str_reply = NULL;
        if (l_cnt == 2) {
            //long l_err_code = strtol(l_str[0], NULL, 10);
            l_str_reply = l_str[1];
        }
        printf("%s\n", (l_str_reply) ? l_str_reply : "no response");
        dap_strfreev(l_str);
    }
    DAP_DELETE(a_cmd->cmd_res);
    dap_string_free(l_cmd_data, true);
    dap_string_free(l_post_data, true);
    return s_status;
}

int dap_app_cli_disconnect(dap_app_cli_connect_param_t *a_socket)
{
    closesocket(*a_socket);
    DAP_DELETE(a_socket);
    return 0;
}