// Copyright (c) 2014-2019, The Monero Project
// Copyright (c)      2018, The Loki Project
// 
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
// 
// 1. Redistributions of source code must retain the above copyright notice, this list of
//    conditions and the following disclaimer.
// 
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
//    of conditions and the following disclaimer in the documentation and/or other
//    materials provided with the distribution.
// 
// 3. Neither the name of the copyright holder nor the names of its contributors may be
//    used to endorse or promote products derived from this software without specific
//    prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// 
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers

#pragma  once

#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include <string>

#include <uWebSockets/App.h>

#include "common/util.h"
#include "common/fs.h"
#include "common/periodic_task.h"
#include "wallet_rpc_server_commands_defs.h"
#include "wallet2.h"
#include "rpc/http_server_base.h"

#undef OXEN_DEFAULT_LOG_CATEGORY
#define OXEN_DEFAULT_LOG_CATEGORY "wallet.rpc"

namespace tools
{
  using HttpRequest = uWS::HttpRequest;
  using HttpResponse = uWS::HttpResponse<false/*SSL*/>;

  /************************************************************************/
  /*                                                                      */
  /************************************************************************/
  class wallet_rpc_server final : public cryptonote::rpc::http_server_base
  {
  public:
    static const char* tr(const char* str);

    wallet_rpc_server(boost::program_options::variables_map vm);

    bool init();
    bool run(bool /*interactive - ignored (rpc wallet is always non-interactive) */);
    void stop(); // Makes run() start cleaning up and shutting down

    /// Thrown if we get invalid/unparseable JSON data.
    struct parse_error : std::runtime_error { using std::runtime_error::runtime_error; };

    //json_rpc
    wallet_rpc::GET_BALANCE::response                     invoke(wallet_rpc::GET_BALANCE::request&& req);
    wallet_rpc::GET_ADDRESS::response                     invoke(wallet_rpc::GET_ADDRESS::request&& req);
    wallet_rpc::GET_ADDRESS_INDEX::response               invoke(wallet_rpc::GET_ADDRESS_INDEX::request&& req);
    wallet_rpc::CREATE_ADDRESS::response                  invoke(wallet_rpc::CREATE_ADDRESS::request&& req);
    wallet_rpc::LABEL_ADDRESS::response                   invoke(wallet_rpc::LABEL_ADDRESS::request&& req);
    wallet_rpc::GET_ACCOUNTS::response                    invoke(wallet_rpc::GET_ACCOUNTS::request&& req);
    wallet_rpc::CREATE_ACCOUNT::response                  invoke(wallet_rpc::CREATE_ACCOUNT::request&& req);
    wallet_rpc::LABEL_ACCOUNT::response                   invoke(wallet_rpc::LABEL_ACCOUNT::request&& req);
    wallet_rpc::GET_ACCOUNT_TAGS::response                invoke(wallet_rpc::GET_ACCOUNT_TAGS::request&& req);
    wallet_rpc::TAG_ACCOUNTS::response                    invoke(wallet_rpc::TAG_ACCOUNTS::request&& req);
    wallet_rpc::UNTAG_ACCOUNTS::response                  invoke(wallet_rpc::UNTAG_ACCOUNTS::request&& req);
    wallet_rpc::SET_ACCOUNT_TAG_DESCRIPTION::response     invoke(wallet_rpc::SET_ACCOUNT_TAG_DESCRIPTION::request&& req);
    wallet_rpc::GET_HEIGHT::response                      invoke(wallet_rpc::GET_HEIGHT::request&& req);
    wallet_rpc::TRANSFER::response                        invoke(wallet_rpc::TRANSFER::request&& req);
    wallet_rpc::TRANSFER_SPLIT::response                  invoke(wallet_rpc::TRANSFER_SPLIT::request&& req);
    wallet_rpc::SIGN_TRANSFER::response                   invoke(wallet_rpc::SIGN_TRANSFER::request&& req);
    wallet_rpc::DESCRIBE_TRANSFER::response               invoke(wallet_rpc::DESCRIBE_TRANSFER::request&& req);
    wallet_rpc::SUBMIT_TRANSFER::response                 invoke(wallet_rpc::SUBMIT_TRANSFER::request&& req);
    wallet_rpc::SWEEP_DUST::response                      invoke(wallet_rpc::SWEEP_DUST::request&& req);
    wallet_rpc::SWEEP_ALL::response                       invoke(wallet_rpc::SWEEP_ALL::request&& req);
    wallet_rpc::SWEEP_SINGLE::response                    invoke(wallet_rpc::SWEEP_SINGLE::request&& req);
    wallet_rpc::RELAY_TX::response                        invoke(wallet_rpc::RELAY_TX::request&& req);
    wallet_rpc::MAKE_INTEGRATED_ADDRESS::response         invoke(wallet_rpc::MAKE_INTEGRATED_ADDRESS::request&& req);
    wallet_rpc::SPLIT_INTEGRATED_ADDRESS::response        invoke(wallet_rpc::SPLIT_INTEGRATED_ADDRESS::request&& req);
    wallet_rpc::STORE::response                           invoke(wallet_rpc::STORE::request&& req);
    wallet_rpc::GET_PAYMENTS::response                    invoke(wallet_rpc::GET_PAYMENTS::request&& req);
    wallet_rpc::GET_BULK_PAYMENTS::response               invoke(wallet_rpc::GET_BULK_PAYMENTS::request&& req);
    wallet_rpc::INCOMING_TRANSFERS::response              invoke(wallet_rpc::INCOMING_TRANSFERS::request&& req);
    wallet_rpc::STOP_WALLET::response                     invoke(wallet_rpc::STOP_WALLET::request&& req);
    wallet_rpc::RESCAN_BLOCKCHAIN::response               invoke(wallet_rpc::RESCAN_BLOCKCHAIN::request&& req);
    wallet_rpc::SET_TX_NOTES::response                    invoke(wallet_rpc::SET_TX_NOTES::request&& req);
    wallet_rpc::GET_TX_NOTES::response                    invoke(wallet_rpc::GET_TX_NOTES::request&& req);
    wallet_rpc::SET_ATTRIBUTE::response                   invoke(wallet_rpc::SET_ATTRIBUTE::request&& req);
    wallet_rpc::GET_ATTRIBUTE::response                   invoke(wallet_rpc::GET_ATTRIBUTE::request&& req);
    wallet_rpc::GET_TX_KEY::response                      invoke(wallet_rpc::GET_TX_KEY::request&& req);
    wallet_rpc::CHECK_TX_KEY::response                    invoke(wallet_rpc::CHECK_TX_KEY::request&& req);
    wallet_rpc::GET_TX_PROOF::response                    invoke(wallet_rpc::GET_TX_PROOF::request&& req);
    wallet_rpc::CHECK_TX_PROOF::response                  invoke(wallet_rpc::CHECK_TX_PROOF::request&& req);
    wallet_rpc::GET_SPEND_PROOF::response                 invoke(wallet_rpc::GET_SPEND_PROOF::request&& req);
    wallet_rpc::CHECK_SPEND_PROOF::response               invoke(wallet_rpc::CHECK_SPEND_PROOF::request&& req);
    wallet_rpc::GET_RESERVE_PROOF::response               invoke(wallet_rpc::GET_RESERVE_PROOF::request&& req);
    wallet_rpc::CHECK_RESERVE_PROOF::response             invoke(wallet_rpc::CHECK_RESERVE_PROOF::request&& req);
    wallet_rpc::GET_TRANSFERS::response                   invoke(wallet_rpc::GET_TRANSFERS::request&& req);
    wallet_rpc::GET_TRANSFERS_CSV::response               invoke(wallet_rpc::GET_TRANSFERS_CSV::request&& req);
    wallet_rpc::GET_TRANSFER_BY_TXID::response            invoke(wallet_rpc::GET_TRANSFER_BY_TXID::request&& req);
    wallet_rpc::SIGN::response                            invoke(wallet_rpc::SIGN::request&& req);
    wallet_rpc::VERIFY::response                          invoke(wallet_rpc::VERIFY::request&& req);
    wallet_rpc::EXPORT_OUTPUTS::response                  invoke(wallet_rpc::EXPORT_OUTPUTS::request&& req);
    wallet_rpc::EXPORT_TRANSFERS::response                invoke(wallet_rpc::EXPORT_TRANSFERS::request&& req);
    wallet_rpc::IMPORT_OUTPUTS::response                  invoke(wallet_rpc::IMPORT_OUTPUTS::request&& req);
    wallet_rpc::EXPORT_KEY_IMAGES::response               invoke(wallet_rpc::EXPORT_KEY_IMAGES::request&& req);
    wallet_rpc::IMPORT_KEY_IMAGES::response               invoke(wallet_rpc::IMPORT_KEY_IMAGES::request&& req);
    wallet_rpc::MAKE_URI::response                        invoke(wallet_rpc::MAKE_URI::request&& req);
    wallet_rpc::PARSE_URI::response                       invoke(wallet_rpc::PARSE_URI::request&& req);
    wallet_rpc::GET_ADDRESS_BOOK_ENTRY::response          invoke(wallet_rpc::GET_ADDRESS_BOOK_ENTRY::request&& req);
    wallet_rpc::ADD_ADDRESS_BOOK_ENTRY::response          invoke(wallet_rpc::ADD_ADDRESS_BOOK_ENTRY::request&& req);
    wallet_rpc::EDIT_ADDRESS_BOOK_ENTRY::response         invoke(wallet_rpc::EDIT_ADDRESS_BOOK_ENTRY::request&& req);
    wallet_rpc::DELETE_ADDRESS_BOOK_ENTRY::response       invoke(wallet_rpc::DELETE_ADDRESS_BOOK_ENTRY::request&& req);
    wallet_rpc::REFRESH::response                         invoke(wallet_rpc::REFRESH::request&& req);
    wallet_rpc::AUTO_REFRESH::response                    invoke(wallet_rpc::AUTO_REFRESH::request&& req);
    wallet_rpc::RESCAN_SPENT::response                    invoke(wallet_rpc::RESCAN_SPENT::request&& req);
    wallet_rpc::START_MINING::response                    invoke(wallet_rpc::START_MINING::request&& req);
    wallet_rpc::STOP_MINING::response                     invoke(wallet_rpc::STOP_MINING::request&& req);
    wallet_rpc::GET_LANGUAGES::response                   invoke(wallet_rpc::GET_LANGUAGES::request&& req);
    wallet_rpc::CREATE_WALLET::response                   invoke(wallet_rpc::CREATE_WALLET::request&& req);
    wallet_rpc::OPEN_WALLET::response                     invoke(wallet_rpc::OPEN_WALLET::request&& req);
    wallet_rpc::CLOSE_WALLET::response                    invoke(wallet_rpc::CLOSE_WALLET::request&& req);
    wallet_rpc::CHANGE_WALLET_PASSWORD::response          invoke(wallet_rpc::CHANGE_WALLET_PASSWORD::request&& req);
    wallet_rpc::GENERATE_FROM_KEYS::response              invoke(wallet_rpc::GENERATE_FROM_KEYS::request&& req);
    wallet_rpc::RESTORE_DETERMINISTIC_WALLET::response    invoke(wallet_rpc::RESTORE_DETERMINISTIC_WALLET::request&& req);
    wallet_rpc::IS_MULTISIG::response                     invoke(wallet_rpc::IS_MULTISIG::request&& req);
    wallet_rpc::PREPARE_MULTISIG::response                invoke(wallet_rpc::PREPARE_MULTISIG::request&& req);
    wallet_rpc::MAKE_MULTISIG::response                   invoke(wallet_rpc::MAKE_MULTISIG::request&& req);
    wallet_rpc::EXPORT_MULTISIG::response                 invoke(wallet_rpc::EXPORT_MULTISIG::request&& req);
    wallet_rpc::IMPORT_MULTISIG::response                 invoke(wallet_rpc::IMPORT_MULTISIG::request&& req);
    wallet_rpc::FINALIZE_MULTISIG::response               invoke(wallet_rpc::FINALIZE_MULTISIG::request&& req);
    wallet_rpc::EXCHANGE_MULTISIG_KEYS::response          invoke(wallet_rpc::EXCHANGE_MULTISIG_KEYS::request&& req);
    wallet_rpc::SIGN_MULTISIG::response                   invoke(wallet_rpc::SIGN_MULTISIG::request&& req);
    wallet_rpc::SUBMIT_MULTISIG::response                 invoke(wallet_rpc::SUBMIT_MULTISIG::request&& req);
    wallet_rpc::VALIDATE_ADDRESS::response                invoke(wallet_rpc::VALIDATE_ADDRESS::request&& req);
    wallet_rpc::SET_DAEMON::response                      invoke(wallet_rpc::SET_DAEMON::request&& req);
    wallet_rpc::SET_LOG_LEVEL::response                   invoke(wallet_rpc::SET_LOG_LEVEL::request&& req);
    wallet_rpc::SET_LOG_CATEGORIES::response              invoke(wallet_rpc::SET_LOG_CATEGORIES::request&& req);
    wallet_rpc::GET_VERSION::response                     invoke(wallet_rpc::GET_VERSION::request&& req);
    wallet_rpc::STAKE::response                           invoke(wallet_rpc::STAKE::request&& req);
    wallet_rpc::REGISTER_SERVICE_NODE::response           invoke(wallet_rpc::REGISTER_SERVICE_NODE::request&& req);
    wallet_rpc::CAN_REQUEST_STAKE_UNLOCK::response        invoke(wallet_rpc::CAN_REQUEST_STAKE_UNLOCK::request&& req);
    wallet_rpc::REQUEST_STAKE_UNLOCK::response            invoke(wallet_rpc::REQUEST_STAKE_UNLOCK::request&& req);
    wallet_rpc::ONS_BUY_MAPPING::response                 invoke(wallet_rpc::ONS_BUY_MAPPING::request&& req);
    wallet_rpc::ONS_RENEW_MAPPING::response               invoke(wallet_rpc::ONS_RENEW_MAPPING::request&& req);
    wallet_rpc::ONS_UPDATE_MAPPING::response              invoke(wallet_rpc::ONS_UPDATE_MAPPING::request&& req);
    wallet_rpc::ONS_MAKE_UPDATE_SIGNATURE::response       invoke(wallet_rpc::ONS_MAKE_UPDATE_SIGNATURE::request&& req);
    wallet_rpc::ONS_HASH_NAME::response                   invoke(wallet_rpc::ONS_HASH_NAME::request&& req);
    wallet_rpc::ONS_KNOWN_NAMES::response                 invoke(wallet_rpc::ONS_KNOWN_NAMES::request&& req);
    wallet_rpc::ONS_ADD_KNOWN_NAMES::response             invoke(wallet_rpc::ONS_ADD_KNOWN_NAMES::request&& req);
    wallet_rpc::ONS_DECRYPT_VALUE::response               invoke(wallet_rpc::ONS_DECRYPT_VALUE::request&& req);
    wallet_rpc::ONS_ENCRYPT_VALUE::response               invoke(wallet_rpc::ONS_ENCRYPT_VALUE::request&& req);
    wallet_rpc::QUERY_KEY::response                       invoke(wallet_rpc::QUERY_KEY::request&& req);

  private:

      /// Handles a POST request to /json_rpc.
      void handle_json_rpc_request(HttpResponse& res, HttpRequest& req);

      // Checks that a wallet is open; if not, throws an error.
      void require_open();

      // Safely and cleanly closes the currently open wallet (if one is open)
      void close_wallet(bool save_current);

      template<typename Ts, typename Tu>
      void fill_response(std::vector<tools::wallet2::pending_tx> &ptx_vector,
          bool get_tx_key, Ts& tx_key, Tu &amount, Tu &fee, std::string &multisig_txset, std::string &unsigned_txset, bool do_not_relay, bool blink,
          Ts &tx_hash, bool get_tx_hex, Ts &tx_blob, bool get_tx_metadata, Ts &tx_metadata);

      cryptonote::address_parse_info extract_account_addr(cryptonote::network_type nettype, std::string_view addr_or_url);

      void validate_transfer(const std::list<wallet::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, bool at_least_one_destination);

      // Parse options and opens the wallet.  Returns nullptr if in directory mode (i.e. no wallet
      // gets opened).  Throws on error.
      std::unique_ptr<tools::wallet2> load_wallet();

      // Sets up the RPC endpoints (called before listening).
      void create_rpc_endpoints(uWS::App& http);

      // Runs the server event loop; does not return until the server is shut down (by a signal or a
      // remote STOP command).
      void run_loop();

      // Starts the long poll thread, if not already active and m_long_poll_disabled is not set.
      // m_wallet must already be set.
      void start_long_poll_thread();

      // Stops the long poll thread and joins it.  This must be done before resetting m_wallet.
      // After the call `m_long_poll_disabled` will be false (and must be set back to true if you
      // want to re-start the thread).
      void stop_long_poll_thread();

      std::unique_ptr<wallet2> m_wallet;
      fs::path m_wallet_dir;
      std::vector<std::tuple<std::string /*ip*/, uint16_t /*port*/, bool /*required*/>> m_bind;
      tools::private_file rpc_login_file;
      std::atomic<bool> m_stop;
      bool m_restricted;
      boost::program_options::variables_map m_vm;
      std::chrono::milliseconds m_auto_refresh_period;
      std::chrono::steady_clock::time_point m_last_auto_refresh_time;
      std::atomic<bool> m_long_poll_new_changes;
      std::atomic<bool> m_long_poll_disabled;
      std::thread m_long_poll_thread;
  };
}
