// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2012 The Bitcoin developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "rpcserver.h"
#include "rpcclient.h"
#include "init.h"

#include <boost/algorithm/string/predicate.hpp>

#include "protocol.h"


void ThreadCli()
{
    // - Simple persistent cli
    //   TODO:
    //      unbuffered terminal input on linux
    //      format help text
    
    char buffer[4096];
    size_t n;
    fd_set rfds;
    struct timeval tv;
    
    printf("Shadow CLI ready:\n> ");
    fflush(stdout);
    
    for (;;)
    {
        boost::this_thread::interruption_point();
        
        // - must be set every iteration
        FD_ZERO(&rfds);
        FD_SET(STDIN_FILENO, &rfds);
        tv.tv_sec = 0;
        tv.tv_usec = 200000;
        
        if (select(1, &rfds, NULL, NULL, &tv) < 1) // read blocks thread from interrupt
            continue;
        
        if ((n = read(STDIN_FILENO, buffer, sizeof(buffer))) < 1)
            continue;
        
        buffer[n] = '\0';
        for ( ; n > 0 && (buffer[n-1] == '\n' || buffer[n-1] == '\r'); --n)
            buffer[n-1] = '\0';
        
        if (strcmp(buffer, "stop") == 0
            || strcmp(buffer, "exit") == 0
            || strcmp(buffer, "quit") == 0
            || strcmp(buffer, "q") == 0)
        {
            puts("Exiting...");
            break;
        };
        
        
        std::string strMethod;
        std::vector<std::string> strParams;
        char *p;
        if ((p = strchr(buffer, ' ')))
        {
            strMethod = std::string(buffer, p);
            
            char *pPS = p+1;
            char *pPE = p+1;
            while (*pPS
                && ((pPE = strchr(pPS, ' '))
                || (pPE = strchr(pPS, '\0'))))
            {
                strParams.push_back(std::string(pPS, pPE));
                pPS = pPE+1;
            };
        } else
        {
            strMethod = std::string(buffer);
        };
        
        std::string strReply;
        JSONRequest jreq;
        
        try
        {
            json_spirit::Array params = RPCConvertValues(strMethod, strParams);
            json_spirit::Value result = tableRPC.execute(strMethod, params);
            
            strReply = json_spirit::write_string(result, true);
            
            ReplaceStrInPlace(strReply, "\\n", "\n"); // format help msg
            
            if (write(STDOUT_FILENO, strReply.data(), strReply.length()) != (uint32_t) strReply.length())
                throw std::runtime_error("write failed.");
            
            printf("\n> ");
            fflush(stdout);
        } catch (json_spirit::Object& objError)
        {
            std::string strReply = JSONRPCReply(json_spirit::Value::null, objError, 0);
            printf("Error: %s\n> ", strReply.c_str());
            fflush(stdout);
        } catch (std::exception& e)
        {
            printf("Error: %s\n> ", e.what());
            fflush(stdout);
        };
        fflush(stdout);
    };
    
    StartShutdown();
};

void WaitForShutdown(boost::thread_group* threadGroup)
{
    bool fShutdown = ShutdownRequested();
    
    if (!fDaemon && GetBoolArg("-cli", false))
    {
        threadGroup->create_thread(boost::bind(&TraceThread<void (*)()>, "cli", &ThreadCli));
    };
    
    // Tell the main threads to shutdown.
    while (!fShutdown)
    {
        MilliSleep(200);
        fShutdown = ShutdownRequested();
    };
    
    LogPrintf("ShadowCoin shutdown.\n\n");
    
    if (threadGroup)
    {
        threadGroup->interrupt_all();
        threadGroup->join_all();
    };
}

//////////////////////////////////////////////////////////////////////////////
//
// Start
//
bool AppInit(int argc, char* argv[])
{
    boost::thread_group threadGroup;

    bool fRet = false;
    try
    {
        //
        // Parameters
        //
        // If Qt is used, parameters/bitcoin.conf are parsed in qt/bitcoin.cpp's main()
        ParseParameters(argc, argv);
        if (!boost::filesystem::is_directory(GetDataDir(false)))
        {
            fprintf(stderr, "Error: Specified directory does not exist\n");
            Shutdown();
        };

        ReadConfigFile(mapArgs, mapMultiArgs);

        if (mapArgs.count("-?") || mapArgs.count("-h") || mapArgs.count("-help"))
        {
            // First part of help message is specific to shadowcoind / RPC client
            std::string strUsage = _("ShadowCoin version") + " " + FormatFullVersion() + "\n\n" +
                _("Usage:") + "\n" +
                  "  shadowcoind [options]                     " + "\n" +
                  "  shadowcoind [options] <command> [params]  " + _("Send command to -server or shadowcoind") + "\n" +
                  "  shadowcoind [options] help                " + _("List commands") + "\n" +
                  "  shadowcoind [options] help <command>      " + _("Get help for a command") + "\n";

            strUsage += "\n" + HelpMessage();

            fprintf(stdout, "%s", strUsage.c_str());
            return false;
        };

        // Command-line RPC
        for (int i = 1; i < argc; i++)
            if (!IsSwitchChar(argv[i][0]) && !boost::algorithm::istarts_with(argv[i], "shadowcoin:"))
                fCommandLine = true;

        if (fCommandLine)
        {
            if (!SelectParamsFromCommandLine())
            {
                fprintf(stderr, "Error: invalid combination of -regtest and -testnet.\n");
                return false;
            };
            
            int ret = CommandLineRPC(argc, argv);
            exit(ret);
        };
        
#if !defined(WIN32)
        fDaemon = GetBoolArg("-daemon", false);
        if (fDaemon)
        {
            // Daemonize
            pid_t pid = fork();
            if (pid < 0)
            {
                fprintf(stderr, "Error: fork() returned %d errno %d\n", pid, errno);
                return false;
            };
            
            if (pid > 0) // Parent process, pid is child process id
            {
                CreatePidFile(GetPidFile(), pid);
                return true;
            };
            
            // Child process falls through to rest of initialization
            
            
            pid_t sid = setsid();
            if (sid < 0)
                fprintf(stderr, "Error: setsid() returned %d errno %d\n", sid, errno);
        };
#endif
        
        if (GetBoolArg("-cli", false))
            printf("Starting...\n");
         
        fRet = AppInit2(threadGroup);
    } catch (std::exception& e)
    {
        PrintException(&e, "AppInit()");
    } catch (...)
    {
        PrintException(NULL, "AppInit()");
    };

    if (!fRet)
    {
        threadGroup.interrupt_all();
        // threadGroup.join_all(); was left out intentionally here, because we didn't re-test all of
        // the startup-failure cases to make sure they don't result in a hang due to some
        // thread-blocking-waiting-for-another-thread-during-startup case
    } else
    {
        WaitForShutdown(&threadGroup);
    };
    Shutdown();

    return fRet;
}

extern void noui_connect();
int main(int argc, char* argv[])
{
    bool fRet = false;
    fHaveGUI = false;
    
    // Connect shadowcoind signal handlers
    noui_connect();
    
    fRet = AppInit(argc, argv);
    
    if (fRet && fDaemon)
        return 0;
    
    return (fRet ? 0 : 1);
};
