//------------------------------------------------------------------------------
/*
    Copyright (c) 2011-2013, OpenCoin, Inc.
*/
//==============================================================================

//
// This a trusted interface, the user is expected to provide valid input to perform valid requests.
// Error catching and reporting is not a requirement of this command line interface.
//
// Improvements to be more strict and to provide better diagnostics are welcome.
//

SETUP_LOG (RPCParser)

static inline bool isSwitchChar (char c)
{
#ifdef __WXMSW__
    return c == '-' || c == '/';
#else
    return c == '-';
#endif
}

std::string EncodeBase64 (const std::string& s)
{
    // FIXME: This performs terribly
    BIO* b64, *bmem;
    BUF_MEM* bptr;

    b64 = BIO_new (BIO_f_base64 ());
    BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL);
    bmem = BIO_new (BIO_s_mem ());
    b64 = BIO_push (b64, bmem);
    BIO_write (b64, s.data (), s.size ());
    (void) BIO_flush (b64);
    BIO_get_mem_ptr (b64, &bptr);

    std::string result (bptr->data, bptr->length);
    BIO_free_all (b64);

    return result;
}

// TODO New routine for parsing ledger parameters, other routines should standardize on this.
static bool jvParseLedger (Json::Value& jvRequest, const std::string& strLedger)
{
    if (strLedger == "current" || strLedger == "closed" || strLedger == "validated")
    {
        jvRequest["ledger_index"]   = strLedger;
    }
    else if (strLedger.length () == 64)
    {
        // YYY Could confirm this is a uint256.
        jvRequest["ledger_hash"]    = strLedger;
    }
    else
    {
        jvRequest["ledger_index"]   = lexicalCast <uint32> (strLedger);
    }

    return true;
}

// Build a object { "currency" : "XYZ", "issuer" : "rXYX" }
static Json::Value jvParseCurrencyIssuer (const std::string& strCurrencyIssuer)
{
    static boost::regex reCurIss ("\\`([[:alpha:]]{3})(?:/(.+))?\\'");

    boost::smatch   smMatch;

    if (boost::regex_match (strCurrencyIssuer, smMatch, reCurIss))
    {
        Json::Value jvResult (Json::objectValue);
        std::string strCurrency = smMatch[1];
        std::string strIssuer   = smMatch[2];

        jvResult["currency"]    = strCurrency;

        if (strIssuer.length ())
        {
            // Could confirm issuer is a valid Ripple address.
            jvResult["issuer"]      = strIssuer;
        }

        return jvResult;
    }
    else
    {
        return rpcError (rpcINVALID_PARAMS);
    }
}

Json::Value RPCParser::parseAsIs (const Json::Value& jvParams)
{
    Json::Value v (Json::objectValue);

    if (jvParams.isArray () && (jvParams.size () > 0))
        v["params"] = jvParams;

    return v;
}

Json::Value RPCParser::parseInternal (const Json::Value& jvParams)
{
    Json::Value v (Json::objectValue);
    v["internal_command"] = jvParams[0u];

    Json::Value params (Json::arrayValue);

    for (unsigned i = 1; i < jvParams.size (); ++i)
        params.append (jvParams[i]);

    v["params"] = params;

    return v;
}

// fetch_info [clear]
Json::Value RPCParser::parseFetchInfo (const Json::Value& jvParams)
{
    Json::Value     jvRequest (Json::objectValue);
    unsigned int    iParams = jvParams.size ();

    if (iParams != 0)
        jvRequest[jvParams[0u].asString()] = true;

    return jvRequest;
}

// account_tx accountID [ledger_min [ledger_max [limit [offset]]]] [binary] [count] [descending]
Json::Value RPCParser::parseAccountTransactions (const Json::Value& jvParams)
{
    Json::Value     jvRequest (Json::objectValue);
    RippleAddress   raAccount;
    unsigned int    iParams = jvParams.size ();

    if (!raAccount.setAccountID (jvParams[0u].asString ()))
        return rpcError (rpcACT_MALFORMED);

    jvRequest["account"]    = raAccount.humanAccountID ();

    bool            bDone   = false;

    while (!bDone && iParams >= 2)
    {
        if (jvParams[iParams - 1].asString () == "binary")
        {
            jvRequest["binary"]     = true;
            --iParams;
        }
        else if (jvParams[iParams - 1].asString () == "count")
        {
            jvRequest["count"]      = true;
            --iParams;
        }
        else if (jvParams[iParams - 1].asString () == "descending")
        {
            jvRequest["descending"] = true;
            --iParams;
        }
        else
        {
            bDone   = true;
        }
    }

    if (1 == iParams)
    {
        nothing ();
    }
    else if (2 == iParams)
    {
        if (!jvParseLedger (jvRequest, jvParams[1u].asString ()))
            return jvRequest;
    }
    else
    {
        int64   uLedgerMin  = jvParams[1u].asInt ();
        int64   uLedgerMax  = jvParams[2u].asInt ();

        if (uLedgerMax != -1 && uLedgerMax < uLedgerMin)
        {
            return rpcError (rpcLGR_IDXS_INVALID);
        }

        jvRequest["ledger_index_min"]   = jvParams[1u].asInt ();
        jvRequest["ledger_index_max"]   = jvParams[2u].asInt ();

        if (iParams >= 4)
            jvRequest["limit"]  = jvParams[3u].asInt ();

        if (iParams >= 5)
            jvRequest["offset"] = jvParams[4u].asInt ();
    }

    return jvRequest;
}

// book_offers <taker_pays> <taker_gets> [<taker> [<ledger> [<limit> [<proof> [<marker>]]]]]
// limit: 0 = no limit
// proof: 0 or 1
//
// Mnemonic: taker pays --> offer --> taker gets
Json::Value RPCParser::parseBookOffers (const Json::Value& jvParams)
{
    Json::Value     jvRequest (Json::objectValue);

    Json::Value     jvTakerPays = jvParseCurrencyIssuer (jvParams[0u].asString ());
    Json::Value     jvTakerGets = jvParseCurrencyIssuer (jvParams[1u].asString ());

    if (isRpcError (jvTakerPays))
    {
        return jvTakerPays;
    }
    else
    {
        jvRequest["taker_pays"] = jvTakerPays;
    }

    if (isRpcError (jvTakerGets))
    {
        return jvTakerGets;
    }
    else
    {
        jvRequest["taker_gets"] = jvTakerGets;
    }

    if (jvParams.size () >= 3)
    {
        jvRequest["issuer"] = jvParams[2u].asString ();
    }

    if (jvParams.size () >= 4 && !jvParseLedger (jvRequest, jvParams[3u].asString ()))
        return jvRequest;

    if (jvParams.size () >= 5)
    {
        int     iLimit  = jvParams[5u].asInt ();

        if (iLimit > 0)
            jvRequest["limit"]  = iLimit;
    }

    if (jvParams.size () >= 6 && jvParams[5u].asInt ())
    {
        jvRequest["proof"]  = true;
    }

    if (jvParams.size () == 7)
        jvRequest["marker"] = jvParams[6u];

    return jvRequest;
}

// connect <ip> [port]
Json::Value RPCParser::parseConnect (const Json::Value& jvParams)
{
    Json::Value     jvRequest (Json::objectValue);

    jvRequest["ip"] = jvParams[0u].asString ();

    if (jvParams.size () == 2)
        jvRequest["port"]   = jvParams[1u].asUInt ();

    return jvRequest;
}

#if ENABLE_INSECURE
// data_delete <key>
Json::Value RPCParser::parseDataDelete (const Json::Value& jvParams)
{
    Json::Value     jvRequest (Json::objectValue);

    jvRequest["key"]    = jvParams[0u].asString ();

    return jvRequest;
}
#endif

#if ENABLE_INSECURE
// data_fetch <key>
Json::Value RPCParser::parseDataFetch (const Json::Value& jvParams)
{
    Json::Value     jvRequest (Json::objectValue);

    jvRequest["key"]    = jvParams[0u].asString ();

    return jvRequest;
}
#endif

#if ENABLE_INSECURE
// data_store <key> <value>
Json::Value RPCParser::parseDataStore (const Json::Value& jvParams)
{
    Json::Value     jvRequest (Json::objectValue);

    jvRequest["key"]    = jvParams[0u].asString ();
    jvRequest["value"]  = jvParams[1u].asString ();

    return jvRequest;
}
#endif

// Return an error for attemping to subscribe/unsubscribe via RPC.
Json::Value RPCParser::parseEvented (const Json::Value& jvParams)
{
    return rpcError (rpcNO_EVENTS);
}

// feature [<feature>] [true|false]
Json::Value RPCParser::parseFeature (const Json::Value& jvParams)
{
    Json::Value     jvRequest (Json::objectValue);

    if (jvParams.size () > 0)
        jvRequest["feature"]    = jvParams[0u].asString ();

    if (jvParams.size () > 1)
        jvRequest["vote"]       = lexicalCastThrow <bool> (jvParams[1u].asString ());

    return jvRequest;
}

// get_counts [<min_count>]
Json::Value RPCParser::parseGetCounts (const Json::Value& jvParams)
{
    Json::Value     jvRequest (Json::objectValue);

    if (jvParams.size ())
        jvRequest["min_count"]  = jvParams[0u].asUInt ();

    return jvRequest;
}

// json <command> <json>
Json::Value RPCParser::parseJson (const Json::Value& jvParams)
{
    Json::Reader    reader;
    Json::Value     jvRequest;

    WriteLog (lsTRACE, RPCParser) << "RPC method: " << jvParams[0u];
    WriteLog (lsTRACE, RPCParser) << "RPC json: " << jvParams[1u];

    if (reader.parse (jvParams[1u].asString (), jvRequest))
    {
        jvRequest["method"] = jvParams[0u];

        return jvRequest;
    }

    return rpcError (rpcINVALID_PARAMS);
}

// ledger [id|index|current|closed|validated] [full]
Json::Value RPCParser::parseLedger (const Json::Value& jvParams)
{
    Json::Value     jvRequest (Json::objectValue);

    if (!jvParams.size ())
    {
        return jvRequest;
    }

    jvParseLedger (jvRequest, jvParams[0u].asString ());

    if (2 == jvParams.size () && jvParams[1u].asString () == "full")
    {
        jvRequest["full"]   = bool (1);
    }

    return jvRequest;
}

// ledger_header <id>|<index>
Json::Value RPCParser::parseLedgerId (const Json::Value& jvParams)
{
    Json::Value     jvRequest (Json::objectValue);

    std::string     strLedger   = jvParams[0u].asString ();

    if (strLedger.length () == 32)
    {
        jvRequest["ledger_hash"]    = strLedger;
    }
    else
    {
        jvRequest["ledger_index"]   = lexicalCast <uint32> (strLedger);
    }

    return jvRequest;
}

#if ENABLE_INSECURE
// login <username> <password>
Json::Value RPCParser::parseLogin (const Json::Value& jvParams)
{
    Json::Value     jvRequest (Json::objectValue);

    jvRequest["username"]   = jvParams[0u].asString ();
    jvRequest["password"]   = jvParams[1u].asString ();

    return jvRequest;
}
#endif

// log_level:                           Get log levels
// log_level <severity>:                Set master log level to the specified severity
// log_level <partition> <severity>:    Set specified partition to specified severity
Json::Value RPCParser::parseLogLevel (const Json::Value& jvParams)
{
    Json::Value     jvRequest (Json::objectValue);

    if (jvParams.size () == 1)
    {
        jvRequest["severity"] = jvParams[0u].asString ();
    }
    else if (jvParams.size () == 2)
    {
        jvRequest["partition"] = jvParams[0u].asString ();
        jvRequest["severity"] = jvParams[1u].asString ();
    }

    return jvRequest;
}

// owner_info <account>|<nickname>|<account_public_key>
// owner_info <seed>|<pass_phrase>|<key> [<ledfer>]
// account_info <account>|<nickname>|<account_public_key>
// account_info <seed>|<pass_phrase>|<key> [<ledger>]
// account_offers <account>|<nickname>|<account_public_key> [<ledger>]
Json::Value RPCParser::parseAccountItems (const Json::Value& jvParams)
{
    return parseAccountRaw (jvParams, false);
}

// account_lines <account> <account>|"" [<ledger>]
Json::Value RPCParser::parseAccountLines (const Json::Value& jvParams)
{
    return parseAccountRaw (jvParams, true);
}

// TODO: Get index from an alternate syntax: rXYZ:<index>
Json::Value RPCParser::parseAccountRaw (const Json::Value& jvParams, bool bPeer)
{
    std::string     strIdent    = jvParams[0u].asString ();
    unsigned int    iCursor     = jvParams.size ();
    bool            bStrict     = false;
    std::string     strPeer;

    if (!bPeer && iCursor >= 2 && jvParams[iCursor - 1] == "strict")
    {
        bStrict = true;
        --iCursor;
    }

    if (bPeer && iCursor >= 2)
        strPeer = jvParams[iCursor].asString ();

    int             iIndex      = 0;
    //  int             iIndex      = jvParams.size() >= 2 ? lexicalCast <int>(jvParams[1u].asString()) : 0;

    RippleAddress   raAddress;

    if (!raAddress.setAccountPublic (strIdent) && !raAddress.setAccountID (strIdent) && !raAddress.setSeedGeneric (strIdent))
        return rpcError (rpcACT_MALFORMED);

    // Get info on account.
    Json::Value jvRequest (Json::objectValue);

    jvRequest["account"]    = strIdent;

    if (bStrict)
        jvRequest["strict"]     = 1;

    if (iIndex)
        jvRequest["account_index"]  = iIndex;

    if (!strPeer.empty ())
    {
        RippleAddress   raPeer;

        if (!raPeer.setAccountPublic (strPeer) && !raPeer.setAccountID (strPeer) && !raPeer.setSeedGeneric (strPeer))
            return rpcError (rpcACT_MALFORMED);

        jvRequest["peer"]   = strPeer;
    }

    if (iCursor == (2 + bPeer) && !jvParseLedger (jvRequest, jvParams[1u + bPeer].asString ()))
        return rpcError (rpcLGR_IDX_MALFORMED);

    return jvRequest;
}

// proof_create [<difficulty>] [<secret>]
Json::Value RPCParser::parseProofCreate (const Json::Value& jvParams)
{
    Json::Value     jvRequest;

    if (jvParams.size () >= 1)
        jvRequest["difficulty"] = jvParams[0u].asInt ();

    if (jvParams.size () >= 2)
        jvRequest["secret"] = jvParams[1u].asString ();

    return jvRequest;
}

// proof_solve <token>
Json::Value RPCParser::parseProofSolve (const Json::Value& jvParams)
{
    Json::Value     jvRequest;

    jvRequest["token"] = jvParams[0u].asString ();

    return jvRequest;
}

// proof_verify <token> <solution> [<difficulty>] [<secret>]
Json::Value RPCParser::parseProofVerify (const Json::Value& jvParams)
{
    Json::Value     jvRequest;

    jvRequest["token"] = jvParams[0u].asString ();
    jvRequest["solution"] = jvParams[1u].asString ();

    if (jvParams.size () >= 3)
        jvRequest["difficulty"] = jvParams[2u].asInt ();

    if (jvParams.size () >= 4)
        jvRequest["secret"] = jvParams[3u].asString ();

    return jvRequest;
}

// ripple_path_find <json> [<ledger>]
Json::Value RPCParser::parseRipplePathFind (const Json::Value& jvParams)
{
    Json::Reader    reader;
    Json::Value     jvRequest;
    bool            bLedger     = 2 == jvParams.size ();

    WriteLog (lsTRACE, RPCParser) << "RPC json: " << jvParams[0u];

    if (reader.parse (jvParams[0u].asString (), jvRequest))
    {
        if (bLedger)
        {
            jvParseLedger (jvRequest, jvParams[1u].asString ());
        }

        return jvRequest;
    }

    return rpcError (rpcINVALID_PARAMS);
}

// sign/submit any transaction to the network
//
// sign <private_key> <json> offline
// submit <private_key> <json>
// submit <tx_blob>
Json::Value RPCParser::parseSignSubmit (const Json::Value& jvParams)
{
    Json::Value     txJSON;
    Json::Reader    reader;
    bool            bOffline    = 3 == jvParams.size () && jvParams[2u].asString () == "offline";

    if (1 == jvParams.size ())
    {
        // Submitting tx_blob

        Json::Value jvRequest;

        jvRequest["tx_blob"]    = jvParams[0u].asString ();

        return jvRequest;
    }
    else if ((2 == jvParams.size () || bOffline)
             && reader.parse (jvParams[1u].asString (), txJSON))
    {
        // Signing or submitting tx_json.
        Json::Value jvRequest;

        jvRequest["secret"]     = jvParams[0u].asString ();
        jvRequest["tx_json"]    = txJSON;

        if (bOffline)
            jvRequest["offline"]    = true;

        return jvRequest;
    }

    return rpcError (rpcINVALID_PARAMS);
}

// sms <text>
Json::Value RPCParser::parseSMS (const Json::Value& jvParams)
{
    Json::Value     jvRequest;

    jvRequest["text"]   = jvParams[0u].asString ();

    return jvRequest;
}

// tx <transaction_id>
Json::Value RPCParser::parseTx (const Json::Value& jvParams)
{
    Json::Value jvRequest;

    if (jvParams.size () > 1)
    {
        if (jvParams[1u].asString () == "binary")
            jvRequest["binary"] = true;
    }

    jvRequest["transaction"]    = jvParams[0u].asString ();
    return jvRequest;
}

// tx_history <index>
Json::Value RPCParser::parseTxHistory (const Json::Value& jvParams)
{
    Json::Value jvRequest;

    jvRequest["start"]  = jvParams[0u].asUInt ();

    return jvRequest;
}

// unl_add <domain>|<node_public> [<comment>]
Json::Value RPCParser::parseUnlAdd (const Json::Value& jvParams)
{
    std::string strNode     = jvParams[0u].asString ();
    std::string strComment  = (jvParams.size () == 2) ? jvParams[1u].asString () : "";

    RippleAddress   naNodePublic;

    if (strNode.length ())
    {
        Json::Value jvRequest;

        jvRequest["node"]       = strNode;

        if (strComment.length ())
            jvRequest["comment"]    = strComment;

        return jvRequest;
    }

    return rpcError (rpcINVALID_PARAMS);
}

// unl_delete <domain>|<public_key>
Json::Value RPCParser::parseUnlDelete (const Json::Value& jvParams)
{
    Json::Value jvRequest;

    jvRequest["node"]       = jvParams[0u].asString ();

    return jvRequest;
}

// validation_create [<pass_phrase>|<seed>|<seed_key>]
//
// NOTE: It is poor security to specify secret information on the command line.  This information might be saved in the command
// shell history file (e.g. .bash_history) and it may be leaked via the process status command (i.e. ps).
Json::Value RPCParser::parseValidationCreate (const Json::Value& jvParams)
{
    Json::Value jvRequest;

    if (jvParams.size ())
        jvRequest["secret"]     = jvParams[0u].asString ();

    return jvRequest;
}

// validation_seed [<pass_phrase>|<seed>|<seed_key>]
//
// NOTE: It is poor security to specify secret information on the command line.  This information might be saved in the command
// shell history file (e.g. .bash_history) and it may be leaked via the process status command (i.e. ps).
Json::Value RPCParser::parseValidationSeed (const Json::Value& jvParams)
{
    Json::Value jvRequest;

    if (jvParams.size ())
        jvRequest["secret"]     = jvParams[0u].asString ();

    return jvRequest;
}

// wallet_accounts <seed>
Json::Value RPCParser::parseWalletAccounts (const Json::Value& jvParams)
{
    Json::Value jvRequest;

    jvRequest["seed"]       = jvParams[0u].asString ();

    return jvRequest;
}

// wallet_propose [<passphrase>]
// <passphrase> is only for testing. Master seeds should only be generated randomly.
Json::Value RPCParser::parseWalletPropose (const Json::Value& jvParams)
{
    Json::Value jvRequest;

    if (jvParams.size ())
        jvRequest["passphrase"]     = jvParams[0u].asString ();

    return jvRequest;
}

// wallet_seed [<seed>|<passphrase>|<passkey>]
Json::Value RPCParser::parseWalletSeed (const Json::Value& jvParams)
{
    Json::Value jvRequest;

    if (jvParams.size ())
        jvRequest["secret"]     = jvParams[0u].asString ();

    return jvRequest;
}

//
// parseCommand
//

// Convert a rpc method and params to a request.
// <-- { method: xyz, params: [... ] } or { error: ..., ... }
Json::Value RPCParser::parseCommand (std::string strMethod, Json::Value jvParams)
{
    WriteLog (lsTRACE, RPCParser) << "RPC method:" << strMethod;
    WriteLog (lsTRACE, RPCParser) << "RPC params:" << jvParams;

    static struct
    {
        const char*     pCommand;
        parseFuncPtr    pfpFunc;
        int             iMinParams;
        int             iMaxParams;
    } commandsA[] =
    {
        // Request-response methods
        // - Returns an error, or the request.
        // - To modify the method, provide a new method in the request.
        {   "account_info",         &RPCParser::parseAccountItems,          1,  2   },
        {   "account_lines",        &RPCParser::parseAccountLines,          1,  3   },
        {   "account_offers",       &RPCParser::parseAccountItems,          1,  2   },
        {   "account_tx",           &RPCParser::parseAccountTransactions,   1,  8   },
        {   "book_offers",          &RPCParser::parseBookOffers,            2,  7   },
        {   "connect",              &RPCParser::parseConnect,               1,  2   },
        {   "consensus_info",       &RPCParser::parseAsIs,                  0,  0   },
        {   "feature",              &RPCParser::parseFeature,               0,  2   },
        {   "fetch_info",           &RPCParser::parseFetchInfo,             0,  1   },
        {   "get_counts",           &RPCParser::parseGetCounts,             0,  1   },
        {   "json",                 &RPCParser::parseJson,                  2,  2   },
        {   "ledger",               &RPCParser::parseLedger,                0,  2   },
        {   "ledger_accept",        &RPCParser::parseAsIs,                  0,  0   },
        {   "ledger_closed",        &RPCParser::parseAsIs,                  0,  0   },
        {   "ledger_current",       &RPCParser::parseAsIs,                  0,  0   },
        //      {   "ledger_entry",         &RPCParser::parseLedgerEntry,          -1, -1   },
        {   "ledger_header",        &RPCParser::parseLedgerId,              1,  1   },
        {   "log_level",            &RPCParser::parseLogLevel,              0,  2   },
        {   "logrotate",            &RPCParser::parseAsIs,                  0,  0   },
        //      {   "nickname_info",        &RPCParser::parseNicknameInfo,          1,  1   },
        {   "owner_info",           &RPCParser::parseAccountItems,          1,  2   },
        {   "peers",                &RPCParser::parseAsIs,                  0,  0   },
        {   "ping",                 &RPCParser::parseAsIs,                  0,  0   },
        //      {   "profile",              &RPCParser::parseProfile,               1,  9   },
        {   "proof_create",         &RPCParser::parseProofCreate,           0,  2   },
        {   "proof_solve",          &RPCParser::parseProofSolve,            1,  1   },
        {   "proof_verify",         &RPCParser::parseProofVerify,           2,  4   },
        {   "random",               &RPCParser::parseAsIs,                  0,  0   },
        {   "ripple_path_find",     &RPCParser::parseRipplePathFind,        1,  2   },
        {   "sign",                 &RPCParser::parseSignSubmit,            2,  3   },
        {   "sms",                  &RPCParser::parseSMS,                   1,  1   },
        {   "submit",               &RPCParser::parseSignSubmit,            1,  2   },
        {   "server_info",          &RPCParser::parseAsIs,                  0,  0   },
        {   "server_state",         &RPCParser::parseAsIs,                  0,  0   },
        {   "stop",                 &RPCParser::parseAsIs,                  0,  0   },
        //      {   "transaction_entry",    &RPCParser::parseTransactionEntry,     -1,  -1  },
        {   "tx",                   &RPCParser::parseTx,                    1,  2   },
        {   "tx_history",           &RPCParser::parseTxHistory,             1,  1   },

        {   "unl_add",              &RPCParser::parseUnlAdd,                1,  2   },
        {   "unl_delete",           &RPCParser::parseUnlDelete,             1,  1   },
        {   "unl_list",             &RPCParser::parseAsIs,                  0,  0   },
        {   "unl_load",             &RPCParser::parseAsIs,                  0,  0   },
        {   "unl_network",          &RPCParser::parseAsIs,                  0,  0   },
        {   "unl_reset",            &RPCParser::parseAsIs,                  0,  0   },
        {   "unl_score",            &RPCParser::parseAsIs,                  0,  0   },

        {   "validation_create",    &RPCParser::parseValidationCreate,      0,  1   },
        {   "validation_seed",      &RPCParser::parseValidationSeed,        0,  1   },

        {   "wallet_accounts",      &RPCParser::parseWalletAccounts,        1,  1   },
        {   "wallet_propose",       &RPCParser::parseWalletPropose,         0,  1   },
        {   "wallet_seed",          &RPCParser::parseWalletSeed,            0,  1   },

        {   "internal",             &RPCParser::parseInternal,              1,  -1  },

#if ENABLE_INSECURE
        // XXX Unnecessary commands which should be removed.
        {   "login",                &RPCParser::parseLogin,                 2,  2   },
        {   "data_delete",          &RPCParser::parseDataDelete,            1,  1   },
        {   "data_fetch",           &RPCParser::parseDataFetch,             1,  1   },
        {   "data_store",           &RPCParser::parseDataStore,             2,  2   },
#endif

        // Evented methods
        {   "path_find",            &RPCParser::parseEvented,               -1, -1  },
        {   "subscribe",            &RPCParser::parseEvented,               -1, -1  },
        {   "unsubscribe",          &RPCParser::parseEvented,               -1, -1  },
    };

    int     i = NUMBER (commandsA);

    while (i-- && strMethod != commandsA[i].pCommand)
        ;

    if (i < 0)
    {
        return rpcError (rpcUNKNOWN_COMMAND);
    }
    else if ((commandsA[i].iMinParams >= 0 && jvParams.size () < commandsA[i].iMinParams)
             || (commandsA[i].iMaxParams >= 0 && jvParams.size () > commandsA[i].iMaxParams))
    {
        WriteLog (lsWARNING, RPCParser) << "Wrong number of parameters: minimum=" << commandsA[i].iMinParams
                                        << " maximum=" << commandsA[i].iMaxParams
                                        << " actual=" << jvParams.size ();

        return rpcError (rpcBAD_SYNTAX);
    }

    return (this->* (commandsA[i].pfpFunc)) (jvParams);
}

// Place the async result somewhere useful.
void callRPCHandler (Json::Value* jvOutput, const Json::Value& jvInput)
{
    (*jvOutput) = jvInput;
}

int commandLineRPC (const std::vector<std::string>& vCmd)
{
    Json::Value jvOutput;
    int         nRet = 0;
    Json::Value jvRequest (Json::objectValue);

    try
    {
        RPCParser   rpParser;
        Json::Value jvRpcParams (Json::arrayValue);

        if (vCmd.empty ()) return 1;                                            // 1 = print usage.

        for (int i = 1; i != vCmd.size (); i++)
            jvRpcParams.append (vCmd[i]);

        Json::Value jvRpc   = Json::Value (Json::objectValue);

        jvRpc["method"] = vCmd[0];
        jvRpc["params"] = jvRpcParams;

        jvRequest   = rpParser.parseCommand (vCmd[0], jvRpcParams);

        WriteLog (lsTRACE, RPCParser) << "RPC Request: " << jvRequest << std::endl;

        if (jvRequest.isMember ("error"))
        {
            jvOutput            = jvRequest;
            jvOutput["rpc"]     = jvRpc;
        }
        else
        {
            Json::Value jvParams (Json::arrayValue);

            jvParams.append (jvRequest);

            if (!getConfig ().RPC_ADMIN_USER.empty ())
                jvRequest["admin_user"]     = getConfig ().RPC_ADMIN_USER;

            if (!getConfig ().RPC_ADMIN_PASSWORD.empty ())
                jvRequest["admin_password"] = getConfig ().RPC_ADMIN_PASSWORD;

            boost::asio::io_service         isService;

            callRPC (
                isService,
                getConfig ().getRpcIP (),
                getConfig ().getRpcPort (),
                getConfig ().RPC_USER,
                getConfig ().RPC_PASSWORD,
                "",
                jvRequest.isMember ("method")           // Allow parser to rewrite method.
                    ? jvRequest["method"].asString () : vCmd[0],
                jvParams,                               // Parsed, execute.
                false,
                BIND_TYPE (callRPCHandler, &jvOutput, P_1));

            isService.run (); // This blocks until there is no more outstanding async calls.

            if (jvOutput.isMember ("result"))
            {
                // Had a successful JSON-RPC 2.0 call.
                jvOutput    = jvOutput["result"];

                // jvOutput may report a server side error.
                // It should report "status".
            }
            else
            {
                // Transport error.
                Json::Value jvRpcError  = jvOutput;

                jvOutput            = rpcError (rpcJSON_RPC);
                jvOutput["result"]  = jvRpcError;
            }

            // If had an error, supply invokation in result.
            if (jvOutput.isMember ("error"))
            {
                jvOutput["rpc"]             = jvRpc;            // How the command was seen as method + params.
                jvOutput["request_sent"]    = jvRequest;        // How the command was translated.
            }
        }

        if (jvOutput.isMember ("error"))
        {
            jvOutput["status"]  = "error";

            nRet    = jvOutput.isMember ("error_code")
                      ? lexicalCast <int> (jvOutput["error_code"].asString ())
                      : 1;
        }

        // YYY We could have a command line flag for single line output for scripts.
        // YYY We would intercept output here and simplify it.
    }
    catch (std::exception& e)
    {
        jvOutput                = rpcError (rpcINTERNAL);
        jvOutput["error_what"]  = e.what ();
        nRet                    = rpcINTERNAL;
    }
    catch (...)
    {
        jvOutput                = rpcError (rpcINTERNAL);
        jvOutput["error_what"]  = "exception";
        nRet                    = rpcINTERNAL;
    }

    std::cout << jvOutput.toStyledString ();

    return nRet;
}

#define RPC_REPLY_MAX_BYTES     (128*1024*1024)
#define RPC_NOTIFY_SECONDS      30

bool responseRPC (
    FUNCTION_TYPE<void (const Json::Value& jvInput)> callbackFuncP,
    const boost::system::error_code& ecResult, int iStatus, const std::string& strData)
{
    if (callbackFuncP)
    {
        // Only care about the result, if we care to deliver it callbackFuncP.

        // Receive reply
        if (iStatus == 401)
            throw std::runtime_error ("incorrect rpcuser or rpcpassword (authorization failed)");
        else if ((iStatus >= 400) && (iStatus != 400) && (iStatus != 404) && (iStatus != 500)) // ?
            throw std::runtime_error (strprintf ("server returned HTTP error %d", iStatus));
        else if (strData.empty ())
            throw std::runtime_error ("no response from server");

        // Parse reply
        WriteLog (lsDEBUG, RPCParser) << "RPC reply: " << strData << std::endl;

        Json::Reader    reader;
        Json::Value     jvReply;

        if (!reader.parse (strData, jvReply))
            throw std::runtime_error ("couldn't parse reply from server");

        if (jvReply.isNull ())
            throw std::runtime_error ("expected reply to have result, error and id properties");

        Json::Value     jvResult (Json::objectValue);

        jvResult["result"] = jvReply;

        (callbackFuncP) (jvResult);
    }

    return false;
}

// Build the request.
void requestRPC (const std::string& strMethod, const Json::Value& jvParams,
                 const std::map<std::string, std::string>& mHeaders, const std::string& strPath,
                 boost::asio::streambuf& sb, const std::string& strHost)
{
    WriteLog (lsDEBUG, RPCParser) << "requestRPC: strPath='" << strPath << "'";

    std::ostream    osRequest (&sb);

    osRequest <<
              createHTTPPost (
                  strHost,
                  strPath,
                  JSONRPCRequest (strMethod, jvParams, Json::Value (1)),
                  mHeaders);
}

void callRPC (
    boost::asio::io_service& io_service,
    const std::string& strIp, const int iPort,
    const std::string& strUsername, const std::string& strPassword,
    const std::string& strPath, const std::string& strMethod,
    const Json::Value& jvParams, const bool bSSL,
    FUNCTION_TYPE<void (const Json::Value& jvInput)> callbackFuncP)
{
    // Connect to localhost
    if (!getConfig ().QUIET)
    {
        Log::out() << "Connecting to: " << strIp << ":" << iPort;
        //  Log::out() << "Username: " << strUsername << ":" << strPassword;
        //  Log::out() << "Path: " << strPath;
        //  Log::out() << "Method: " << strMethod;
    }

    // HTTP basic authentication
    std::string strUserPass64 = EncodeBase64 (strUsername + ":" + strPassword);

    std::map<std::string, std::string> mapRequestHeaders;

    mapRequestHeaders["Authorization"] = std::string ("Basic ") + strUserPass64;

    // Send request
    // Log(lsDEBUG) << "requesting" << std::endl;
    // WriteLog (lsDEBUG, RPCParser) << "send request " << strMethod << " : " << strRequest << std::endl;

    HttpsClient::httpsRequest (
        bSSL,
        io_service,
        strIp,
        iPort,
        BIND_TYPE (
            &requestRPC,
            strMethod,
            jvParams,
            mapRequestHeaders,
            strPath, P_1, P_2),
        RPC_REPLY_MAX_BYTES,
        boost::posix_time::seconds (RPC_NOTIFY_SECONDS),
        BIND_TYPE (&responseRPC, callbackFuncP, P_1, P_2, P_3));
}

// vim:ts=4
