#ifndef RTORRENT_RPC_MANAGER_H
#define RTORRENT_RPC_MANAGER_H

#include <atomic>
#include <cstdint>
#include <functional>
#include <torrent/common.h>
#include <torrent/exceptions.h>

#include "rpc/command.h"
#include "rpc/command_map.h"
#include "rpc/exec_file.h"
#include "rpc/jsonrpc.h"
#include "rpc/xmlrpc.h"

namespace core {
class Download;
}

namespace rpc {

extern ExecFile   execFile;
extern CommandMap commands;

class rpc_error : public torrent::base_error {
public:
  rpc_error(int type, std::string msg) :
      m_type(type), m_msg(msg) {}
  virtual ~rpc_error() throw() = default;

  virtual int         type() const throw() { return m_type; }
  virtual const char* what() const throw() { return m_msg.c_str(); }

private:
  int         m_type;
  std::string m_msg;
};

class untrusted_error : public torrent::input_error {
public:
  using torrent::input_error::input_error;
  virtual ~untrusted_error() throw() = default;
};

class RpcManager {
public:
  using slot_download          = std::function<core::Download*(const char*)>;
  using slot_file              = std::function<torrent::File*(core::Download*, uint32_t)>;
  using slot_tracker           = std::function<torrent::tracker::Tracker(core::Download*, uint32_t)>;
  using slot_peer              = std::function<torrent::Peer*(core::Download*, const torrent::HashString&)>;
  using slot_response_callback = std::function<bool(const char*, uint32_t)>;

  enum RPCType { XML,
                 JSON };

  RpcManager()  = default;
  ~RpcManager() = default;

  bool                is_handlers_initialized() const { return m_handlers_initialized; }

  void                initialize_handlers();
  void                cleanup();

  int64_t             size_limit()                  { return m_xmlrpc.size_limit(); };
  void                set_size_limit(uint64_t size);

  int                 dialect()                     { return m_xmlrpc.dialect(); }
  void                set_dialect(int dialect)      { m_xmlrpc.set_dialect(dialect); }

  bool                use_xmlrpc() const;
  void                set_use_xmlrpc(bool v);

  bool                use_jsonrpc() const;
  void                set_use_jsonrpc(bool v);

  bool                process(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback);
  bool                process_untrusted(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback);

  void                insert_command(const char* name, const char* parm, const char* doc);
  void                mark_safe(const std::string& key);

  bool                scgi_allow_compression() const                { return m_scgi_allow_compression; }
  void                set_scgi_allow_compression(bool allow)        { m_scgi_allow_compression = allow; }

  unsigned int        scgi_min_compress_size() const                { return m_scgi_min_compress_size; }
  void                set_scgi_min_compress_size(unsigned int size) { m_scgi_min_compress_size = size; }

  slot_download&      slot_find_download() { return m_slot_find_download; }
  slot_file&          slot_find_file()     { return m_slot_find_file; }
  slot_tracker&       slot_find_tracker()  { return m_slot_find_tracker; }
  slot_peer&          slot_find_peer()     { return m_slot_find_peer; }

  // Trusted/untrusted XMLRPC connection model.
  //
  // When an SCGI request includes the UNTRUSTED_CONNECTION header,
  // commands without flag_untrusted_safe are blocked.
  bool                is_trusted() const;

  static void         object_to_target(const torrent::Object& obj, int callFlags, rpc::target_type* target, std::function<void()>* deleter);

private:
  bool          m_trusted{true};

  XmlRpc        m_xmlrpc;
  JsonRpc       m_jsonrpc;

  bool          m_handlers_initialized{};

  bool          m_use_jsonrpc{true};
  bool          m_use_xmlrpc{true};

  std::atomic<bool>         m_scgi_allow_compression{true};
  std::atomic<unsigned int> m_scgi_min_compress_size{1000};

  slot_download m_slot_find_download;
  slot_file     m_slot_find_file;
  slot_tracker  m_slot_find_tracker;
  slot_peer     m_slot_find_peer;
};

extern RpcManager rpc;

inline bool RpcManager::use_xmlrpc() const      { return m_use_xmlrpc; }
inline void RpcManager::set_use_xmlrpc(bool v)  { m_use_xmlrpc = v; }
inline bool RpcManager::use_jsonrpc() const     { return m_use_jsonrpc; }
inline void RpcManager::set_use_jsonrpc(bool v) { m_use_jsonrpc = v; }

} // namespace rpc

#endif
