/*
 * Authors:
 * Dmitriy A. Gerasimov <kahovski@gmail.com>
 * DeM Labs Ltd.   https://demlabs.net
 * CellFrame         https://cellframe.net
 * Copyright  (c) 2017-2020
 * 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>
#include <unistd.h>
#include <sys/types.h>
#include <getopt.h>
#include <signal.h>

#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#include <mswsock.h>
#include <ws2tcpip.h>
#include <io.h>
#include <pthread.h>
#include "userenv.h"

#endif

#define LOG_TAG "main"

#ifndef _WIN32
  #include "sig_unix_handler.h"
#else
    #include "sig_win32_handler.h"
    #include "registry.h"
    void  S_SetExceptionFilter( void );
#endif
#include "dap_common.h"
#include "dap_config.h"
#include "dap_server.h"
#include "dap_notify_srv.h"
#include "dap_http.h"
#include "dap_http_folder.h"
#include "dap_chain_node_dns_client.h"
#include "dap_chain_node_dns_server.h"

#ifdef DAP_MODULES_DYNAMIC
#include "dap_modules_dynamic_cdb.h"
#endif

#include "dap_events.h"
#include "dap_enc.h"
#include "dap_enc_ks.h"
#include "dap_enc_http.h"

#include "dap_chain.h"
#include "dap_chain_wallet.h"

#include "dap_chain_cs_blocks.h"
#include "dap_chain_cs_block_poa.h"
#include "dap_chain_cs_block_pos.h"
#include "dap_chain_cs_block_ton.h"
#include "dap_chain_cs_dag.h"
#include "dap_chain_cs_dag_poa.h"
#include "dap_chain_cs_dag_pos.h"
#include "dap_chain_cs_none.h"

//#include "dap_chain_bridge.h"
//#include "dap_chain_bridge_btc.h"

#include "dap_chain_net.h"
#include "dap_chain_net_srv.h"
#include "dap_chain_net_srv_app.h"
#include "dap_chain_net_srv_app_db.h"
#include "dap_chain_net_srv_datum.h"
#include "dap_chain_net_srv_geoip.h"

#ifdef DAP_OS_LINUX
#include "dap_chain_net_srv_vpn.h"
#include "dap_chain_net_vpn_client.h"
#endif

#include "dap_chain_global_db.h"
#include "dap_chain_mempool.h"
#include "dap_chain_node.h"
#include "dap_chain_node_cli.h"

#include "dap_stream_session.h"
#include "dap_stream.h"
#include "dap_stream_ctl.h"
#include "dap_stream_ch_chain.h"
#include "dap_stream_ch_chain_net.h"
#include "dap_stream_ch_chain_net_srv.h"
#include "dap_chain_net_srv_xchange.h"
#include "dap_chain_net_srv_stake.h"

#include "dap_common.h"
#include "dap_events_socket.h"
#include "dap_client.h"
#include "dap_http_client.h"
//#include "dap_http_client_simple.h"
#include "dap_http_simple.h"
#include "dap_process_manager.h"

#include "dap_defines.h"
#include "dap_file_utils.h"

#ifdef DAP_SUPPORT_PYTHON_PLUGINS
    #include "dap_chain_plugins.h"
    #include "dap_plugins_python_app_context.h"
#endif


#define ENC_HTTP_URL "/enc_init"
#define STREAM_CTL_URL "/stream_ctl"

#define STREAM_URL "/stream"
#define MEMPOOL_URL "/mempool"
#define MAIN_URL "/"

#ifdef __ANDROID__
    #include "cellframe_node.h"
#endif

void parse_args( int argc, const char **argv );
void exit_if_server_already_running( void );

static const char *s_pid_file_path = NULL;

#ifdef __ANDROID__
int cellframe_node_Main(int argc, const char **argv)
#else
int main( int argc, const char **argv )
#endif
{
	dap_server_t *l_server = NULL; // DAP Server instance
    bool l_debug_mode = true;
	bool bServerEnabled = false;
	int rc = 0;

    dap_set_appname("cellframe-node");
	#if defined(_WIN32) && defined(NDEBUG)
		S_SetExceptionFilter( );
	#endif
#ifdef _WIN32
    g_sys_dir_path = dap_strdup_printf("%s/%s", regGetUsrPath(), dap_get_appname());
#elif DAP_OS_MAC
    char * l_username = NULL;
    exec_with_ret(&l_username,"whoami|tr -d '\n'");
    if (!l_username){
        printf("Fatal Error: Can't obtain username");
    return 2;
    }
    g_sys_dir_path = dap_strdup_printf("/Users/%s/Applications/Cellframe.app/Contents/Resources", l_username);
    DAP_DELETE(l_username);
#elif DAP_OS_ANDROID
    g_sys_dir_path = dap_strdup_printf("/storage/emulated/0/opt/%s",dap_get_appname());
#elif DAP_OS_UNIX
    g_sys_dir_path = dap_strdup_printf("/opt/%s", dap_get_appname());
#endif

    {
        char *l_log_dir = dap_strdup_printf("%s/var/log", g_sys_dir_path);
        dap_mkdir_with_parents(l_log_dir);
        char * l_log_file = dap_strdup_printf( "%s/%s.log", l_log_dir, dap_get_appname());
        if (dap_common_init(dap_get_appname(), l_log_file, l_log_dir) != 0) {
            printf("Fatal Error: Can't init common functions module");
            return -2;
        }
        DAP_DELETE(l_log_dir);
        DAP_DELETE(l_log_file);
    }

    {
        char l_config_dir[MAX_PATH] = {'\0'};
        dap_sprintf(l_config_dir, "%s/etc", g_sys_dir_path);
        dap_config_init(l_config_dir);
    }

    if ((g_config = dap_config_open(dap_get_appname())) == NULL ) {
        log_it( L_CRITICAL,"Can't init general configurations" );
        return -1;
    }

    s_pid_file_path = dap_config_get_item_str_default( g_config,  "resources", "pid_path","/tmp") ;

    log_it(L_DEBUG, "Parsing command line args");
    parse_args( argc, argv );
    #ifdef _WIN32
        CreateMutexW( NULL, FALSE, (WCHAR *) L"DAP_CELLFRAME_NODE_74E9201D33F7F7F684D2FEF1982799A79B6BF94B568446A8D1DE947B00E3C75060F3FD5BF277592D02F77D7E50935E56" );
	#endif

      l_debug_mode = dap_config_get_item_bool_default( g_config,"general","debug_mode", false );
    //  bDebugMode = true;//dap_config_get_item_bool_default( g_config,"general","debug_mode", false );

    if ( l_debug_mode )
	    log_it( L_ATT, "*** DEBUG MODE ***" );
	else
 	   log_it( L_ATT, "*** NORMAL MODE ***" );

    dap_log_level_set( l_debug_mode ? L_DEBUG : L_NOTICE );

    log_it( L_DAP, "*** CellFrame Node version: %s ***", DAP_VERSION );

	// change to dap_config_get_item_int_default when it's will be possible
	size_t l_thread_cnt = 0;

	const char *s_thrd_cnt = dap_config_get_item_str( g_config, "resources", "threads_cnt" );
	if ( s_thrd_cnt != NULL )
	    l_thread_cnt = (size_t)atoi( s_thrd_cnt );

	if ( !l_thread_cnt ) {
    	#ifndef _WIN32
      		l_thread_cnt = (size_t)sysconf(_SC_NPROCESSORS_ONLN);
    	#else
      		SYSTEM_INFO si;
      		GetSystemInfo( &si );
      		l_thread_cnt = si.dwNumberOfProcessors;
    	#endif
  	}
    if ( dap_enc_init() != 0 ){
        log_it( L_CRITICAL, "Can't init encryption module" );
        return -56;
    }

    // New event loop init
    dap_events_init( 0, 0 );
    dap_events_t *l_events = dap_events_new( );
    dap_events_start( l_events );

    bServerEnabled = dap_config_get_item_bool_default( g_config, "server", "enabled", false );

    if ( bServerEnabled && dap_server_init() != 0 ) {
        log_it( L_CRITICAL, "Can't init socket server module" );
        return -4;
    }


	if ( dap_http_init() != 0 ) {
    	log_it( L_CRITICAL, "Can't init http server module" );
	    return -5;
	}

	if ( dap_http_folder_init() != 0 ){
	    log_it( L_CRITICAL, "Can't init http server module" );
	    return -55;
	}
    if ( dap_http_simple_module_init() != 0 ) {
        log_it(L_CRITICAL,"Can't init http simple module");
        return -9;
    }

    if ( enc_http_init() != 0 ) {
        log_it( L_CRITICAL, "Can't init encryption http session storage module" );
        return -81;
    }

    if ( dap_stream_init(g_config) != 0 ) {
        log_it( L_CRITICAL, "Can't init stream server module" );
        return -82;
    }

    if ( dap_stream_ctl_init() != 0 ){
        log_it( L_CRITICAL, "Can't init stream control module" );
        return -83;
    }

    dap_client_init();

    // Create and init notify server
    if ( dap_notify_server_init() != 0 ){
        log_it( L_ERROR, "Can't init notify server module" );
    }

	if ( dap_chain_global_db_init(g_config) ) {
	    log_it( L_CRITICAL, "Can't init global db module" );
	    return -58;
	}

	//dap_http_client_simple_init( );

	if ( dap_datum_mempool_init() ) {
	    log_it( L_CRITICAL, "Can't init mempool module" );
	    return -59;
	}

    if( dap_chain_init() !=0){
        log_it(L_CRITICAL,"Can't init dap chain modules");
        return -60;
    }

    if( dap_chain_wallet_init() !=0){
        log_it(L_CRITICAL,"Can't init dap chain wallet module");
        return -61;
    }

    if( dap_chain_cs_dag_init() !=0){
        log_it(L_CRITICAL,"Can't init dap chain dag consensus module");
        return -62;
    }

    if( dap_chain_cs_dag_poa_init() !=0){
        log_it(L_CRITICAL,"Can't init dap chain dag consensus PoA module");
        return -63;
    }

    if( dap_chain_cs_dag_pos_init() !=0){
        log_it(L_CRITICAL,"Can't init dap chain dag consensus PoS module");
        return -64;
    }

    if (dap_chain_cs_blocks_init() != 0) {
        log_it(L_CRITICAL,"Can't init dap chain blocks consensus module");
        return -62;
    }

    if (dap_chain_cs_block_poa_init() != 0) {
        log_it(L_CRITICAL,"Can't init dap chain blocks consensus PoA module");
        return -63;
    }

    if (dap_chain_cs_block_pos_init() != 0) {
        log_it(L_CRITICAL,"Can't init dap chain blocks consensus PoS module");
        return -64;
    }

    if (dap_chain_cs_block_ton_init() != 0) {
        log_it(L_CRITICAL,"Can't init dap chain blocks consensus TON module");
        return -69;
    }

    if(dap_chain_gdb_init() != 0) {
        log_it(L_CRITICAL, "Can't init dap chain gdb module");
        return -71;
    }
    dap_chain_ledger_verificator_rwlock_init();
    dap_chain_ledger_verificator_add(DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_XCHANGE, dap_chain_net_srv_xchange_verificator);
    dap_chain_ledger_verificator_add(DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_PAY, dap_chain_net_srv_pay_verificator);
    dap_chain_ledger_verificator_add(DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE, dap_chain_net_srv_stake_verificator);
    dap_chain_ledger_verificator_add(DAP_CHAIN_TX_OUT_COND_SUBTYPE_SRV_STAKE_UPDATE, dap_chain_net_srv_stake_updater);

    if( dap_chain_net_init() !=0){
        log_it(L_CRITICAL,"Can't init dap chain network module");
        return -65;
    }
    if( dap_chain_net_srv_init() !=0){
        log_it(L_CRITICAL,"Can't init dap chain network service module");
        return -66;
    }

    if (!dap_chain_net_srv_xchange_init()) {
        log_it(L_ERROR, "Can't provide exchange capability");
    }

    if (!dap_chain_net_srv_stake_init()) {
        log_it(L_ERROR, "Can't start delegated stake service");
    }

    if( dap_chain_net_srv_app_init() !=0){
        log_it(L_CRITICAL,"Can't init dap chain network service applications module");
        return -67;
    }

    if( dap_chain_net_srv_datum_init() !=0){
        log_it(L_CRITICAL,"Can't init dap chain network service datum module");
        return -68;
    }


#if defined(DAP_OS_LINUX) && ! defined (DAP_OS_ANDROID)
    // vpn server
    if(dap_config_get_item_bool_default(g_config, "srv_vpn", "enabled", false)) {
        if(dap_chain_net_srv_vpn_init(g_config) != 0) {
            log_it(L_ERROR, "Can't init dap chain network service vpn module");
            return -70;
        }
    }
    // vpn client
    if(dap_chain_net_vpn_client_init(g_config) != 0) {
        log_it(L_ERROR, "Can't init dap chain network service vpn client");
        return -72;
    }

    if(dap_config_get_item_bool_default(g_config, "srv_vpn", "geoip_enabled", false)) {
        if(chain_net_geoip_init(g_config) != 0) {
            log_it(L_CRITICAL, "Can't init geoip module");
            return -73;
        }
    }
#endif

	if ( dap_chain_node_cli_init(g_config) ) {
	    log_it( L_CRITICAL, "Can't init server for console" );
	    return -11;
	}

#ifndef _WIN32
    if (sig_unix_handler_init(dap_config_get_item_str_default(g_config,
                                                              "resources",
                                                              "pid_path",
                                                              "/tmp")) != 0) {
        log_it(L_CRITICAL,"Can't init sig unix handler module");
        return -12;
    }
#else
    if ( sig_win32_handler_init( NULL ) ) {
        log_it( L_CRITICAL,"Can't init sig win32 handler module" );
        return -12;
    }
#endif

    log_it(L_INFO, "Automatic mempool processing %s",
           dap_chain_node_mempool_autoproc_init() ? "enabled" : "disabled");

    save_process_pid_in_file(s_pid_file_path);

	if ( bServerEnabled ) {

        int32_t l_port = dap_config_get_item_int32(g_config, "server", "listen_port_tcp");

        if( l_port > 0 ) {
            l_server = dap_server_new(l_events,  (dap_config_get_item_str(g_config, "server", "listen_address")),
                                      (uint16_t) l_port, SERVER_TCP, NULL );
        } else
            log_it( L_WARNING, "Server is enabled but no port is defined" );

    }

    if ( l_server ) { // If listener server is initialized
        // TCP-specific things
		if ( dap_config_get_item_int32_default(g_config, "server", "listen_port_tcp",-1) > 0) {
            // Init HTTP-specific values
            dap_http_new( l_server, dap_get_appname() );

#ifdef DAP_MODULES_DYNAMIC
            if( dap_config_get_item_bool_default(g_config,"cdb","enabled",false) ) {
                if(dap_modules_dynamic_load_cdb(DAP_HTTP( l_server ))){
                    log_it(L_CRITICAL,"Can't init CDB module");
                    return -3;
                }else{
                    log_it(L_NOTICE, "Central DataBase (CDB) is initialized");
                }
            }
#endif

	        // Handshake URL
	        enc_http_add_proc( DAP_HTTP(l_server), ENC_HTTP_URL );

	        // Streaming URLs
	        dap_stream_add_proc_http( DAP_HTTP(l_server), STREAM_URL );
	        dap_stream_ctl_add_proc( DAP_HTTP(l_server), STREAM_CTL_URL );

            const char *str_start_mempool = dap_config_get_item_str( g_config, "mempool", "accept" );
	        if ( str_start_mempool && !strcmp(str_start_mempool, "true")) {
	                dap_chain_mempool_add_proc(DAP_HTTP(l_server), MEMPOOL_URL);
	        }

	        // Built in WWW server

	        if (  dap_config_get_item_bool_default(g_config,"www","enabled",false)  ){
	                dap_http_folder_add( DAP_HTTP(l_server), "/",
	                                dap_config_get_item_str(g_config,
                                                                "resources",
                                                                "www_root") );
	        }

		}
    } else
        log_it( L_INFO, "No enabled server, working in client mode only" );

    if (dap_config_get_item_bool_default(g_config, "dns_server", "enabled", false))
    {
        // DNS server start
        bool bootstrap_balancer_enabled = dap_config_get_item_bool_default(g_config, "dns_server", "bootstrap_balancer", false);
        log_it(L_DEBUG, "config dns_server->bootstrap_balancer = \"%u\" ", bootstrap_balancer_enabled);
        if (bootstrap_balancer_enabled) {
            dap_dns_server_start(l_events, dap_config_get_item_uint16_default(g_config, "dns_server", "bootstrap_balancer_port", DNS_LISTEN_PORT));
        }
    }

    //dap_chain_net_load_all();

//Init python plugins
#ifdef DAP_SUPPORT_PYTHON_PLUGINS
    log_it(L_NOTICE, "Loading python plugins");
    dap_plugins_python_app_content_init(l_server);
    dap_chain_plugins_init(g_config);
#endif

    /* Test code for service client
    #include "dap_chain_net_srv_client.h"
    dap_chain_net_srv_client_callbacks_t l_callbacks = {};
    dap_chain_net_srv_client_t *l_client = dap_chain_net_srv_client_create_n_connect(
                                                dap_chain_net_by_name("kelvin-testnet"),
                                                "51.89.133.15", 80, &l_callbacks, NULL);
    */

    rc = dap_events_wait(l_events);
    log_it( rc ? L_CRITICAL : L_NOTICE, "Server loop stopped with return code %d", rc );
    // Deinit modules

//failure:

//    #ifdef DAP_SUPPORT_PYTHON_PLUGINS
//        dap_chain_plugins_deinit();
//    #endif
    dap_dns_server_stop();
	dap_stream_deinit();
	dap_stream_ctl_deinit();
	dap_http_folder_deinit();
	dap_http_deinit();
	if (bServerEnabled) dap_server_deinit();
	dap_enc_ks_deinit();
    dap_chain_node_mempool_autoproc_deinit();
    dap_chain_net_srv_xchange_deinit();
    dap_chain_net_srv_stake_deinit();
    dap_chain_net_deinit();
#ifdef DAP_MODULES_DYNAMIC
    dap_modules_dynamic_close_cdb();
#endif
    dap_chain_global_db_deinit();
    dap_chain_deinit();
	dap_config_close( g_config );
    dap_interval_timer_deinit();
	dap_common_deinit();

	return rc * 10;
}

static struct option long_options[] = {

	{ "stop", 0, NULL, 0 },
	{ NULL,   0, NULL, 0 } // must be a last element
};

void parse_args( int argc, const char **argv ) {

	int opt, option_index = 0, is_daemon = 0;

	while ( (opt = getopt_long(argc, (char *const *)argv, "D0",
                              long_options, &option_index)) != -1) {
	    switch ( opt ) {

	    case 0: // --stop
    	{
            pid_t pid = get_pid_from_file(s_pid_file_path);

	    	if ( pid == 0 ) {
	        	log_it( L_ERROR, "Can't read pid from file" );
	        	exit( -20 );
	      	}

	    	if ( kill_process(pid) ) {
	        	log_it( L_INFO, "Server successfully stopped" );
	        	exit( 0 );
	      	}

	    	log_it( L_WARNING, "Server not stopped. Maybe he is not running now?" );
	    	exit( -21 );
	    }

    	case 'D':
    	{
        	log_it( L_INFO, "Daemonize server starting..." );
        	exit_if_server_already_running( );
        	is_daemon = 1;
        	daemonize_process( );
        	break;
      	}

        default:
        log_it( L_WARNING, "Unknown option from command line" );
		}
	}

	if( !is_daemon )
		exit_if_server_already_running( );
}

void exit_if_server_already_running( void ) {

    pid_t pid = get_pid_from_file(s_pid_file_path);

	bool  mf = false;

	#ifdef _WIN32
        CreateMutexW( NULL, FALSE, (WCHAR *) L"DAP_CELLFRAME_NODE_74E9201D33F7F7F684D2FEF1982799A79B6BF94B568446A8D1DE947B00E3C75060F3FD5BF277592D02F77D7E50935E56" );

		if ( GetLastError( ) == 183 ) {
      		mf = true;
    	}
	#endif

	if ( (pid != 0 && is_process_running(pid)) || mf ) {
        log_it( L_WARNING, "Proccess %"DAP_UINT64_FORMAT_U" is running, don't allow "
                            "to run more than one copy of DapServer, exiting...", (uint64_t)pid );
		exit( -2 );
	}
}