text: // The MIT License (MIT)
  //
  // Copyright (c) 2015-2017 Simon Ninon <simon.ninon@gmail.com>
  //
  // Permission is hereby granted, free of charge, to any person obtaining a copy
  // of this software and associated documentation files (the "Software"), to deal
  // in the Software without restriction, including without limitation the rights
  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  // copies of the Software, and to permit persons to whom the Software is
  // furnished to do so, subject to the following conditions:
  //
  // The above copyright notice and this permission notice shall be included in all
  // copies or substantial portions of the Software.
  //
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  // SOFTWARE.
  
  #include <cpp_redis/core/client.hpp>
  #include <cpp_redis/misc/error.hpp>
  #include <cpp_redis/misc/macro.hpp>
  
  namespace cpp_redis {
  
  #ifndef __CPP_REDIS_USE_CUSTOM_TCP_CLIENT
  
  client::client()
  : m_reconnecting(false), m_cancel(false), m_callbacks_running(0) {
    __CPP_REDIS_LOG(debug, "cpp_redis::client created");
  }
  
  #endif /* __CPP_REDIS_USE_CUSTOM_TCP_CLIENT */
  
  client::client(const std::shared_ptr<network::tcp_client_iface>& tcp_client)
  : m_client(tcp_client), m_sentinel(tcp_client), m_reconnecting(false), m_cancel(false), m_callbacks_running(0) {
    __CPP_REDIS_LOG(debug, "cpp_redis::client created");
  }
  
  client::~client() {
    /**
   * ensure we stopped reconnection attempts
   */
    if (!m_cancel) {
      cancel_reconnect();
    }
  
    /**
   * If for some reason sentinel is connected then disconnect now.
   */
    if (m_sentinel.is_connected()) {
      m_sentinel.disconnect(true);
    }
  
    /**
   * disconnect underlying tcp socket
   */
    if (m_client.is_connected()) {
      m_client.disconnect(true);
    }
  
    __CPP_REDIS_LOG(debug, "cpp_redis::client destroyed");
  }
  
  void
  client::connect(
    const std::string& name,
    const connect_callback_t& connect_callback,
    std::uint32_t timeout_ms,
    std::int32_t max_reconnects,
    std::uint32_t reconnect_interval_ms) {
    /**
   * Save for auto reconnects
   */
    m_master_name = name;
  
    /**
   * We rely on the sentinel to tell us which redis server is currently the master.
   */
    if (m_sentinel.get_master_addr_by_name(name, m_redis_server, m_redis_port, true)) {
      connect(m_redis_server, m_redis_port, connect_callback, timeout_ms, max_reconnects, reconnect_interval_ms);
    }
    else {
      throw redis_error("cpp_redis::client::connect() could not find master for m_name " + name);
    }
  }
  
  
  void
  client::connect(
    const std::string& host, std::size_t port,
    const connect_callback_t& connect_callback,
    std::uint32_t timeout_ms,
    std::int32_t max_reconnects,
    std::uint32_t reconnect_interval_ms) {
    __CPP_REDIS_LOG(debug, "cpp_redis::client attempts to connect");
  
    /**
   * Save for auto reconnects
   */
    m_redis_server          = host;
    m_redis_port            = port;
    m_connect_callback      = connect_callback;
    m_max_reconnects        = max_reconnects;
    m_reconnect_interval_ms = reconnect_interval_ms;
  
    /**
   * notify start
   */
    if (m_connect_callback) {
      m_connect_callback(host, port, connect_state::start);
    }
  
    auto disconnection_handler = std::bind(&client::connection_disconnection_handler, this, std::placeholders::_1);
    auto receive_handler       = std::bind(&client::connection_receive_handler, this, std::placeholders::_1,
      std::placeholders::_2);
    m_client.connect(host, port, disconnection_handler, receive_handler, timeout_ms);
  
    __CPP_REDIS_LOG(info, "cpp_redis::client connected");
  
    /**
   * notify end
   */
    if (m_connect_callback) {
      m_connect_callback(m_redis_server, m_redis_port, connect_state::ok);
    }
  }
  
  void
  client::disconnect(bool wait_for_removal) {
    __CPP_REDIS_LOG(debug, "cpp_redis::client attempts to disconnect");
  
    /**
   * close connection
   */
    m_client.disconnect(wait_for_removal);
  
    /**
   * make sure we clear buffer of unsent commands
   */
    clear_callbacks();
  
    __CPP_REDIS_LOG(info, "cpp_redis::client disconnected");
  }
  
  bool
  client::is_connected() const {
    return m_client.is_connected();
  }
  
  void
  client::cancel_reconnect() {
    m_cancel = true;
  }
  
  bool
  client::is_reconnecting() const {
    return m_reconnecting;
  }
  
  void
  client::add_sentinel(const std::string& host, std::size_t port, std::uint32_t timeout_ms) {
    m_sentinel.add_sentinel(host, port, timeout_ms);
  }
  
  const sentinel&
  client::get_sentinel() const {
    return m_sentinel;
  }
  
  sentinel&
  client::get_sentinel() {
    return m_sentinel;
  }
  
  void
  client::clear_sentinels() {
    m_sentinel.clear_sentinels();
  }
  
  client&
  client::send(const std::vector<std::string>& redis_cmd, const reply_callback_t& callback) {
    std::lock_guard<std::mutex> lock_callback(m_callbacks_mutex);
  
    __CPP_REDIS_LOG(info, "cpp_redis::client attempts to store new command in the send buffer");
    unprotected_send(redis_cmd, callback);
    __CPP_REDIS_LOG(info, "cpp_redis::client stored new command in the send buffer");
  
    return *this;
  }
  
  void
  client::unprotected_send(const std::vector<std::string>& redis_cmd, const reply_callback_t& callback) {
    m_client.send(redis_cmd);
    m_commands.push({redis_cmd, callback});
  }
  
  /**
   * commit pipelined transaction
   */
  client&
  client::commit() {
    /**
   * no need to call commit in case of reconnection
   * the reconnection flow will do it for us
   */
    if (!is_reconnecting()) {
      try_commit();
    }
  
    return *this;
  }
  
  client&
  client::sync_commit() {
    /**
   * no need to call commit in case of reconnection
   * the reconnection flow will do it for us
   */
    if (!is_reconnecting()) {
      try_commit();
    }
  
    std::unique_lock<std::mutex> lock_callback(m_callbacks_mutex);
    __CPP_REDIS_LOG(debug, "cpp_redis::client waiting for callbacks to complete");
    m_sync_condvar.wait(lock_callback, [=] { return m_callbacks_running == 0 && m_commands.empty(); });
    __CPP_REDIS_LOG(debug, "cpp_redis::client finished waiting for callback completion");
    return *this;
  }
  
  void
  client::try_commit() {
    try {
      __CPP_REDIS_LOG(debug, "cpp_redis::client attempts to send pipelined commands");
      m_client.commit();
      __CPP_REDIS_LOG(info, "cpp_redis::client sent pipelined commands");
    }
    catch (const cpp_redis::redis_error&) {
      __CPP_REDIS_LOG(error, "cpp_redis::client could not send pipelined commands");
      /**
  			 * ensure commands are flushed
  			 */
      clear_callbacks();
      throw;
    }
  }
  
  void
  client::connection_receive_handler(network::redis_connection&, reply& reply) {
    reply_callback_t callback = nullptr;
  
    __CPP_REDIS_LOG(info, "cpp_redis::client received reply");
    {
      std::lock_guard<std::mutex> lock(m_callbacks_mutex);
      m_callbacks_running += 1;
  
      if (!m_commands.empty()) {
        callback = m_commands.front().callback;
        m_commands.pop();
      }
    }
  
    if (callback) {
      __CPP_REDIS_LOG(debug, "cpp_redis::client executes reply callback");
      callback(reply);
    }
  
    {
      std::lock_guard<std::mutex> lock(m_callbacks_mutex);
      m_callbacks_running -= 1;
      m_sync_condvar.notify_all();
    }
  }
  
  void
  client::clear_callbacks() {
    if (m_commands.empty()) {
      return;
    }
  
    /**
   * dequeue commands and move them to a local variable
   */
    std::queue<command_request> commands = std::move(m_commands);
  
    m_callbacks_running += __CPP_REDIS_LENGTH(commands.size());
  
    std::thread t([=]() mutable {
      while (!commands.empty()) {
        const auto& callback = commands.front().callback;
  
        if (callback) {
          reply r = {"network failure", reply::string_type::error};
          callback(r);
        }
  
        --m_callbacks_running;
        commands.pop();
      }
  
      m_sync_condvar.notify_all();
    });
    t.detach();
  }
  
  void
  client::resend_failed_commands() {
    if (m_commands.empty()) {
      return;
    }
  
    /**
   * dequeue commands and move them to a local variable
   */
    std::queue<command_request> commands = std::move(m_commands);
  
    while (!commands.empty()) {
      /**
   * Reissue the pending command and its callback.
   */
      unprotected_send(commands.front().command, commands.front().callback);
  
      commands.pop();
    }
  }
  
  void
  client::connection_disconnection_handler(network::redis_connection&) {
    /**
   * leave right now if we are already dealing with reconnection
   */
    if (is_reconnecting()) {
      return;
    }
  
    /**
   * initiate reconnection process
   */
    m_reconnecting               = true;
    m_current_reconnect_attempts = 0;
  
    __CPP_REDIS_LOG(warn, "cpp_redis::client has been disconnected");
  
    if (m_connect_callback) {
      m_connect_callback(m_redis_server, m_redis_port, connect_state::dropped);
    }
  
    /**
   * Lock the callbacks mutex of the base class to prevent more client commands from being issued until our reconnect has completed.
   */
    std::lock_guard<std::mutex> lock_callback(m_callbacks_mutex);
  
    while (should_reconnect()) {
      sleep_before_next_reconnect_attempt();
      reconnect();
    }
  
    if (!is_connected()) {
      clear_callbacks();
  
      /**
   * Tell the user we gave up!
   */
      if (m_connect_callback) {
        m_connect_callback(m_redis_server, m_redis_port, connect_state::stopped);
      }
    }
  
    /**
   * terminate reconnection
   */
    m_reconnecting = false;
  }
  
  void
  client::sleep_before_next_reconnect_attempt() {
    if (m_reconnect_interval_ms <= 0) {
      return;
    }
  
    if (m_connect_callback) {
      m_connect_callback(m_redis_server, m_redis_port, connect_state::sleeping);
    }
  
    std::this_thread::sleep_for(std::chrono::milliseconds(m_reconnect_interval_ms));
  }
  
  bool
  client::should_reconnect() const {
    return !is_connected() && !m_cancel && (m_max_reconnects == -1 || m_current_reconnect_attempts < m_max_reconnects);
  }
  
  void
  client::re_auth() {
    if (m_password.empty()) {
      return;
    }
  
    // In re_auth we do not want to serialize the command to the TCP buffer (i.e. call m_client.send)
    // as it will get serialized to the buffer in resend_failed_commands after re_auth is called in
    // reconnect.  So we just push the command into the command queue.
    //
    // Otherwise we will end up with the AUTH command serialized to
    // the buffer twice and in the command queue once, which causes a problem when we get 2 responses
    // back.  The subsequent responses are off by one in the command callbacks.
    //
    m_commands.push({{"AUTH", m_password}, [&](cpp_redis::reply& reply) {
                       if (reply.is_string() && reply.as_string() == "OK") {
                         __CPP_REDIS_LOG(warn, "client successfully re-authenticated");
                       }
                       else {
                         __CPP_REDIS_LOG(warn, std::string("client failed to re-authenticate: " + reply.as_string()).c_str());
                       }
                     }});
  }
  
  void
  client::re_select() {
    if (m_database_index <= 0) {
      return;
    }
  
    unprotected_select(m_database_index, [&](cpp_redis::reply& reply) {
      if (reply.is_string() && reply.as_string() == "OK") {
        __CPP_REDIS_LOG(warn, "client successfully re-selected redis database");
      }
      else {
        __CPP_REDIS_LOG(warn, std::string("client failed to re-select database: " + reply.as_string()).c_str());
      }
    });
  }
  
  void
  client::reconnect() {
    /**
   * increase the number of attempts to reconnect
   */
    ++m_current_reconnect_attempts;
  
  
    /**
   * We rely on the sentinel to tell us which redis server is currently the master.
   */
    if (!m_master_name.empty() && !m_sentinel.get_master_addr_by_name(m_master_name, m_redis_server, m_redis_port, true)) {
      if (m_connect_callback) {
        m_connect_callback(m_redis_server, m_redis_port, connect_state::lookup_failed);
      }
      return;
    }
  
    /**
   * Try catch block because the redis client throws an error if connection cannot be made.
   */
    try {
      connect(m_redis_server, m_redis_port, m_connect_callback, m_connect_timeout_ms, m_max_reconnects,
        m_reconnect_interval_ms);
    }
    catch (...) {
    }
  
    if (!is_connected()) {
      if (m_connect_callback) {
        m_connect_callback(m_redis_server, m_redis_port, connect_state::failed);
      }
      return;
    }
  
    /**
   * notify end
   */
    if (m_connect_callback) {
      m_connect_callback(m_redis_server, m_redis_port, connect_state::ok);
    }
  
    __CPP_REDIS_LOG(info, "client reconnected ok");
  
    re_auth();
    re_select();
    resend_failed_commands();
    try_commit();
  }
  
  std::string
  client::aggregate_method_to_string(aggregate_method method) const {
    switch (method) {
    case aggregate_method::sum:
      return "SUM";
    case aggregate_method::min:
      return "MIN";
    case aggregate_method::max:
      return "MAX";
    default:
      return "";
    }
  }
  
  std::string
  client::geo_unit_to_string(geo_unit unit) const {
    switch (unit) {
    case geo_unit::m:
      return "m";
    case geo_unit::km:
      return "km";
    case geo_unit::ft:
      return "ft";
    case geo_unit::mi:
      return "mi";
    default:
      return "";
    }
  }
  
  std::string
  client::bitfield_operation_type_to_string(bitfield_operation_type operation) const {
    switch (operation) {
    case bitfield_operation_type::get:
      return "GET";
    case bitfield_operation_type::set:
      return "SET";
    case bitfield_operation_type::incrby:
      return "INCRBY";
    default:
      return "";
    }
  }
  
  std::string
  client::overflow_type_to_string(overflow_type type) const {
    switch (type) {
    case overflow_type::wrap:
      return "WRAP";
    case overflow_type::sat:
      return "SAT";
    case overflow_type::fail:
      return "FAIL";
    default:
      return "";
    }
  }
  
  client::bitfield_operation
  client::bitfield_operation::get(const std::string& type, int offset, overflow_type overflow) {
    return {bitfield_operation_type::get, type, offset, 0, overflow};
  }
  
  client::bitfield_operation
  client::bitfield_operation::set(const std::string& type, int offset, int value, overflow_type overflow) {
    return {bitfield_operation_type::set, type, offset, value, overflow};
  }
  
  client::bitfield_operation
  client::bitfield_operation::incrby(const std::string& type, int offset, int increment, overflow_type overflow) {
    return {bitfield_operation_type::incrby, type, offset, increment, overflow};
  }
  
  /**
   * Redis commands
   * Callback-based
   */
  
  client&
  client::append(const std::string& key, const std::string& value, const reply_callback_t& reply_callback) {
    send({"APPEND", key, value}, reply_callback);
    return *this;
  }
  
  client&
  client::auth(const std::string& password, const reply_callback_t& reply_callback) {
    std::lock_guard<std::mutex> lock(m_callbacks_mutex);
  
    unprotected_auth(password, reply_callback);
  
    return *this;
  }
  
  void
  client::unprotected_auth(const std::string& password, const reply_callback_t& reply_callback) {
    /**
   * save the password for reconnect attempts.
   */
    m_password = password;
    /**
   * store command in pipeline
   */
    unprotected_send({"AUTH", password}, reply_callback);
  }
  
  client&
  client::bgrewriteaof(const reply_callback_t& reply_callback) {
    send({"BGREWRITEAOF"}, reply_callback);
    return *this;
  }
  
  client&
  client::bgsave(const reply_callback_t& reply_callback) {
    send({"BGSAVE"}, reply_callback);
    return *this;
  }
  
  client&
  client::bitcount(const std::string& key, const reply_callback_t& reply_callback) {
    send({"BITCOUNT", key}, reply_callback);
    return *this;
  }
  
  client&
  client::bitcount(const std::string& key, int start, int end, const reply_callback_t& reply_callback) {
    send({"BITCOUNT", key, std::to_string(start), std::to_string(end)}, reply_callback);
    return *this;
  }
  
  client&
  client::bitfield(const std::string& key, const std::vector<bitfield_operation>& operations,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"BITFIELD", key};
  
    for (const auto& operation : operations) {
      cmd.push_back(bitfield_operation_type_to_string(operation.operation_type));
      cmd.push_back(operation.type);
      cmd.push_back(std::to_string(operation.offset));
  
      if (operation.operation_type == bitfield_operation_type::set || operation.operation_type == bitfield_operation_type::incrby) {
        cmd.push_back(std::to_string(operation.value));
      }
  
      if (operation.overflow != overflow_type::server_default) {
        cmd.emplace_back("OVERFLOW");
        cmd.push_back(overflow_type_to_string(operation.overflow));
      }
    }
  
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::bitop(const std::string& operation, const std::string& destkey, const std::vector<std::string>& keys,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"BITOP", operation, destkey};
    cmd.insert(cmd.end(), keys.begin(), keys.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::bitpos(const std::string& key, int bit, const reply_callback_t& reply_callback) {
    send({"BITPOS", key, std::to_string(bit)}, reply_callback);
    return *this;
  }
  
  client&
  client::bitpos(const std::string& key, int bit, int start, const reply_callback_t& reply_callback) {
    send({"BITPOS", key, std::to_string(bit), std::to_string(start)}, reply_callback);
    return *this;
  }
  
  client&
  client::bitpos(const std::string& key, int bit, int start, int end, const reply_callback_t& reply_callback) {
    send({"BITPOS", key, std::to_string(bit), std::to_string(start), std::to_string(end)}, reply_callback);
    return *this;
  }
  
  client&
  client::blpop(const std::vector<std::string>& keys, int timeout, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"BLPOP"};
    cmd.insert(cmd.end(), keys.begin(), keys.end());
    cmd.push_back(std::to_string(timeout));
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::brpop(const std::vector<std::string>& keys, int timeout, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"BRPOP"};
    cmd.insert(cmd.end(), keys.begin(), keys.end());
    cmd.push_back(std::to_string(timeout));
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::brpoplpush(const std::string& src, const std::string& dst, int timeout,
    const reply_callback_t& reply_callback) {
    send({"BRPOPLPUSH", src, dst, std::to_string(timeout)}, reply_callback);
    return *this;
  }
  
  client&
  client::bzpopmin(const std::vector<std::string>& keys, int timeout, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"BZPOPMIN"};
    cmd.insert(cmd.end(), keys.begin(), keys.end());
    cmd.push_back(std::to_string(timeout));
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::bzpopmax(const std::vector<std::string>& keys, int timeout, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"BZPOPMAX"};
    cmd.insert(cmd.end(), keys.begin(), keys.end());
    cmd.push_back(std::to_string(timeout));
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::client_id(const reply_callback_t& reply_callback) {
    send({"CLIENT", "ID"}, reply_callback);
    return *this;
  }
  
  client&
  client::client_list(const reply_callback_t& reply_callback) {
    send({"CLIENT", "LIST"}, reply_callback);
    return *this;
  }
  
  client&
  client::client_getname(const reply_callback_t& reply_callback) {
    send({"CLIENT", "GETNAME"}, reply_callback);
    return *this;
  }
  
  client&
  client::client_pause(int timeout, const reply_callback_t& reply_callback) {
    send({"CLIENT", "PAUSE", std::to_string(timeout)}, reply_callback);
    return *this;
  }
  
  client&
  client::client_reply(const std::string& mode, const reply_callback_t& reply_callback) {
    send({"CLIENT", "REPLY", mode}, reply_callback);
    return *this;
  }
  
  client&
  client::client_setname(const std::string& name, const reply_callback_t& reply_callback) {
    send({"CLIENT", "SETNAME", name}, reply_callback);
    return *this;
  }
  
  client&
  client::client_unblock(int id, const reply_callback_t& reply_callback) {
    send({"CLIENT", "UNBLOCK", std::to_string(id)}, reply_callback);
    return *this;
  }
  
  client&
  client::client_unblock(int id, bool witherror, const reply_callback_t& reply_callback) {
    if (witherror)
      send({"CLIENT", "UNBLOCK", std::to_string(id), "ERROR"}, reply_callback);
    else
      send({"CLIENT", "UNBLOCK", std::to_string(id)}, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_addslots(const std::vector<std::string>& p_slots, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"CLUSTER", "ADDSLOTS"};
    cmd.insert(cmd.end(), p_slots.begin(), p_slots.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_count_failure_reports(const std::string& node_id, const reply_callback_t& reply_callback) {
    send({"CLUSTER", "COUNT-FAILURE-REPORTS", node_id}, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_countkeysinslot(const std::string& slot, const reply_callback_t& reply_callback) {
    send({"CLUSTER", "COUNTKEYSINSLOT", slot}, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_delslots(const std::vector<std::string>& p_slots, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"CLUSTER", "DELSLOTS"};
    cmd.insert(cmd.end(), p_slots.begin(), p_slots.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_failover(const reply_callback_t& reply_callback) {
    send({"CLUSTER", "FAILOVER"}, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_failover(const std::string& mode, const reply_callback_t& reply_callback) {
    send({"CLUSTER", "FAILOVER", mode}, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_forget(const std::string& node_id, const reply_callback_t& reply_callback) {
    send({"CLUSTER", "FORGET", node_id}, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_getkeysinslot(const std::string& slot, int count, const reply_callback_t& reply_callback) {
    send({"CLUSTER", "GETKEYSINSLOT", slot, std::to_string(count)}, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_info(const reply_callback_t& reply_callback) {
    send({"CLUSTER", "INFO"}, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_keyslot(const std::string& key, const reply_callback_t& reply_callback) {
    send({"CLUSTER", "KEYSLOT", key}, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_meet(const std::string& ip, int port, const reply_callback_t& reply_callback) {
    send({"CLUSTER", "MEET", ip, std::to_string(port)}, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_nodes(const reply_callback_t& reply_callback) {
    send({"CLUSTER", "NODES"}, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_replicate(const std::string& node_id, const reply_callback_t& reply_callback) {
    send({"CLUSTER", "REPLICATE", node_id}, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_reset(const reply_callback_t& reply_callback) {
    send({"CLUSTER", "RESET"}, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_reset(const std::string& mode, const reply_callback_t& reply_callback) {
    send({"CLUSTER", "RESET", mode}, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_saveconfig(const reply_callback_t& reply_callback) {
    send({"CLUSTER", "SAVECONFIG"}, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_set_config_epoch(const std::string& epoch, const reply_callback_t& reply_callback) {
    send({"CLUSTER", "SET-CONFIG-EPOCH", epoch}, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_setslot(const std::string& slot, const std::string& mode, const reply_callback_t& reply_callback) {
    send({"CLUSTER", "SETSLOT", slot, mode}, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_setslot(const std::string& slot, const std::string& mode, const std::string& node_id,
    const reply_callback_t& reply_callback) {
    send({"CLUSTER", "SETSLOT", slot, mode, node_id}, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_slaves(const std::string& node_id, const reply_callback_t& reply_callback) {
    send({"CLUSTER", "SLAVES", node_id}, reply_callback);
    return *this;
  }
  
  client&
  client::cluster_slots(const reply_callback_t& reply_callback) {
    send({"CLUSTER", "SLOTS"}, reply_callback);
    return *this;
  }
  
  client&
  client::command(const reply_callback_t& reply_callback) {
    send({"COMMAND"}, reply_callback);
    return *this;
  }
  
  client&
  client::command_count(const reply_callback_t& reply_callback) {
    send({"COMMAND", "COUNT"}, reply_callback);
    return *this;
  }
  
  client&
  client::command_getkeys(const reply_callback_t& reply_callback) {
    send({"COMMAND", "GETKEYS"}, reply_callback);
    return *this;
  }
  
  client&
  client::command_info(const std::vector<std::string>& command_name, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"COMMAND", "COUNT"};
    cmd.insert(cmd.end(), command_name.begin(), command_name.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::config_get(const std::string& param, const reply_callback_t& reply_callback) {
    send({"CONFIG", "GET", param}, reply_callback);
    return *this;
  }
  
  client&
  client::config_rewrite(const reply_callback_t& reply_callback) {
    send({"CONFIG", "REWRITE"}, reply_callback);
    return *this;
  }
  
  client&
  client::config_set(const std::string& param, const std::string& val, const reply_callback_t& reply_callback) {
    send({"CONFIG", "SET", param, val}, reply_callback);
    return *this;
  }
  
  client&
  client::config_resetstat(const reply_callback_t& reply_callback) {
    send({"CONFIG", "RESETSTAT"}, reply_callback);
    return *this;
  }
  
  client&
  client::dbsize(const reply_callback_t& reply_callback) {
    send({"DBSIZE"}, reply_callback);
    return *this;
  }
  
  client&
  client::debug_object(const std::string& key, const reply_callback_t& reply_callback) {
    send({"DEBUG", "OBJECT", key}, reply_callback);
    return *this;
  }
  
  client&
  client::debug_segfault(const reply_callback_t& reply_callback) {
    send({"DEBUG", "SEGFAULT"}, reply_callback);
    return *this;
  }
  
  client&
  client::decr(const std::string& key, const reply_callback_t& reply_callback) {
    send({"DECR", key}, reply_callback);
    return *this;
  }
  
  client&
  client::decrby(const std::string& key, int val, const reply_callback_t& reply_callback) {
    send({"DECRBY", key, std::to_string(val)}, reply_callback);
    return *this;
  }
  
  client&
  client::del(const std::vector<std::string>& key, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"DEL"};
    cmd.insert(cmd.end(), key.begin(), key.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::discard(const reply_callback_t& reply_callback) {
    send({"DISCARD"}, reply_callback);
    return *this;
  }
  
  client&
  client::dump(const std::string& key, const reply_callback_t& reply_callback) {
    send({"DUMP", key}, reply_callback);
    return *this;
  }
  
  client&
  client::echo(const std::string& msg, const reply_callback_t& reply_callback) {
    send({"ECHO", msg}, reply_callback);
    return *this;
  }
  
  client&
  client::eval(const std::string& script, int numkeys, const std::vector<std::string>& keys,
    const std::vector<std::string>& args, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"EVAL", script, std::to_string(numkeys)};
    cmd.insert(cmd.end(), keys.begin(), keys.end());
    cmd.insert(cmd.end(), args.begin(), args.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::eval(const std::string& script, const std::vector<std::string>& keys,
    const std::vector<std::string>& args, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"EVAL", script, std::to_string(keys.size())};
    cmd.insert(cmd.end(), keys.begin(), keys.end());
    cmd.insert(cmd.end(), args.begin(), args.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::evalsha(const std::string& sha1, int numkeys, const std::vector<std::string>& keys,
    const std::vector<std::string>& args, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"EVALSHA", sha1, std::to_string(numkeys)};
    cmd.insert(cmd.end(), keys.begin(), keys.end());
    cmd.insert(cmd.end(), args.begin(), args.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::evalsha(const std::string& sha1, const std::vector<std::string>& keys,
    const std::vector<std::string>& args, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"EVALSHA", sha1, std::to_string(keys.size())};
    cmd.insert(cmd.end(), keys.begin(), keys.end());
    cmd.insert(cmd.end(), args.begin(), args.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::exec(const reply_callback_t& reply_callback) {
    send({"EXEC"}, reply_callback);
    return *this;
  }
  
  client&
  client::exists(const std::vector<std::string>& keys, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"EXISTS"};
    cmd.insert(cmd.end(), keys.begin(), keys.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::expire(const std::string& key, int seconds, const reply_callback_t& reply_callback) {
    send({"EXPIRE", key, std::to_string(seconds)}, reply_callback);
    return *this;
  }
  
  client&
  client::expireat(const std::string& key, int timestamp, const reply_callback_t& reply_callback) {
    send({"EXPIREAT", key, std::to_string(timestamp)}, reply_callback);
    return *this;
  }
  
  client&
  client::flushall(const reply_callback_t& reply_callback) {
    send({"FLUSHALL"}, reply_callback);
    return *this;
  }
  
  client&
  client::flushdb(const reply_callback_t& reply_callback) {
    send({"FLUSHDB"}, reply_callback);
    return *this;
  }
  
  client&
  client::geoadd(const std::string& key,
    const std::vector<std::tuple<std::string, std::string, std::string>>& long_lat_memb,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"GEOADD", key};
    for (const auto& obj : long_lat_memb) {
      cmd.push_back(std::get<0>(obj));
      cmd.push_back(std::get<1>(obj));
      cmd.push_back(std::get<2>(obj));
    }
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::geohash(const std::string& key, const std::vector<std::string>& members,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"GEOHASH", key};
    cmd.insert(cmd.end(), members.begin(), members.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::geopos(const std::string& key, const std::vector<std::string>& members,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"GEOPOS", key};
    cmd.insert(cmd.end(), members.begin(), members.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::geodist(const std::string& key, const std::string& member_1, const std::string& member_2,
    const reply_callback_t& reply_callback) {
    send({"GEODIST", key, member_1, member_2}, reply_callback);
    return *this;
  }
  
  client&
  client::geodist(const std::string& key, const std::string& member_1, const std::string& member_2,
    const std::string& unit, const reply_callback_t& reply_callback) {
    send({"GEODIST", key, member_1, member_2, unit}, reply_callback);
    return *this;
  }
  
  client&
  client::georadius(const std::string& key, double longitude, double latitude, double radius, geo_unit unit,
    bool with_coord, bool with_dist, bool with_hash, bool asc_order,
    const reply_callback_t& reply_callback) {
    return georadius(key, longitude, latitude, radius, unit, with_coord, with_dist, with_hash, asc_order, 0, "", "",
      reply_callback);
  }
  
  client&
  client::georadius(const std::string& key, double longitude, double latitude, double radius, geo_unit unit,
    bool with_coord, bool with_dist, bool with_hash, bool asc_order, std::size_t count,
    const reply_callback_t& reply_callback) {
    return georadius(key, longitude, latitude, radius, unit, with_coord, with_dist, with_hash, asc_order, count, "", "",
      reply_callback);
  }
  
  client&
  client::georadius(const std::string& key, double longitude, double latitude, double radius, geo_unit unit,
    bool with_coord, bool with_dist, bool with_hash, bool asc_order, const std::string& store_key,
    const reply_callback_t& reply_callback) {
    return georadius(key, longitude, latitude, radius, unit, with_coord, with_dist, with_hash, asc_order, 0, store_key,
      "", reply_callback);
  }
  
  client&
  client::georadius(const std::string& key, double longitude, double latitude, double radius, geo_unit unit,
    bool with_coord, bool with_dist, bool with_hash, bool asc_order, const std::string& store_key,
    const std::string& storedist_key, const reply_callback_t& reply_callback) {
    return georadius(key, longitude, latitude, radius, unit, with_coord, with_dist, with_hash, asc_order, 0, store_key,
      storedist_key, reply_callback);
  }
  
  client&
  client::georadius(const std::string& key, double longitude, double latitude, double radius, geo_unit unit,
    bool with_coord, bool with_dist, bool with_hash, bool asc_order, std::size_t count,
    const std::string& store_key, const reply_callback_t& reply_callback) {
    return georadius(key, longitude, latitude, radius, unit, with_coord, with_dist, with_hash, asc_order, count,
      store_key, "", reply_callback);
  }
  
  client&
  client::georadius(const std::string& key, double longitude, double latitude, double radius, geo_unit unit,
    bool with_coord, bool with_dist, bool with_hash, bool asc_order, std::size_t count,
    const std::string& store_key, const std::string& storedist_key,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"GEORADIUS", key, std::to_string(longitude), std::to_string(latitude),
      std::to_string(radius), geo_unit_to_string(unit)};
  
    /**
   * with_coord (optional)
   */
    if (with_coord) {
      cmd.emplace_back("WITHCOORD");
    }
  
    /**
   * with_dist (optional)
   */
    if (with_dist) {
      cmd.emplace_back("WITHDIST");
    }
  
    /**
   * with_hash (optional)
   */
    if (with_hash) {
      cmd.emplace_back("WITHHASH");
    }
  
    /**
   * order (optional)
   */
    cmd.emplace_back(asc_order ? "ASC" : "DESC");
  
    /**
   * count (optional)
   */
    if (count > 0) {
      cmd.emplace_back("COUNT");
      cmd.push_back(std::to_string(count));
    }
  
    /**
   * store_key (optional)
   */
    if (!store_key.empty()) {
      cmd.emplace_back("STOREDIST");
      cmd.push_back(storedist_key);
    }
  
    /**
   * storedist_key (optional)
   */
    if (!storedist_key.empty()) {
      cmd.emplace_back("STOREDIST");
      cmd.push_back(storedist_key);
    }
  
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::georadiusbymember(const std::string& key, const std::string& member, double radius, geo_unit unit,
    bool with_coord, bool with_dist, bool with_hash, bool asc_order,
    const reply_callback_t& reply_callback) {
    return georadiusbymember(key, member, radius, unit, with_coord, with_dist, with_hash, asc_order, 0, "", "",
      reply_callback);
  }
  
  client&
  client::georadiusbymember(const std::string& key, const std::string& member, double radius, geo_unit unit,
    bool with_coord, bool with_dist, bool with_hash, bool asc_order, std::size_t count,
    const reply_callback_t& reply_callback) {
    return georadiusbymember(key, member, radius, unit, with_coord, with_dist, with_hash, asc_order, count, "", "",
      reply_callback);
  }
  
  client&
  client::georadiusbymember(const std::string& key, const std::string& member, double radius, geo_unit unit,
    bool with_coord, bool with_dist, bool with_hash, bool asc_order,
    const std::string& store_key, const reply_callback_t& reply_callback) {
    return georadiusbymember(key, member, radius, unit, with_coord, with_dist, with_hash, asc_order, 0, store_key, "",
      reply_callback);
  }
  
  client&
  client::georadiusbymember(const std::string& key, const std::string& member, double radius, geo_unit unit,
    bool with_coord, bool with_dist, bool with_hash, bool asc_order,
    const std::string& store_key, const std::string& storedist_key,
    const reply_callback_t& reply_callback) {
    return georadiusbymember(key, member, radius, unit, with_coord, with_dist, with_hash, asc_order, 0, store_key,
      storedist_key, reply_callback);
  }
  
  client&
  client::georadiusbymember(const std::string& key, const std::string& member, double radius, geo_unit unit,
    bool with_coord, bool with_dist, bool with_hash, bool asc_order, std::size_t count,
    const std::string& store_key, const reply_callback_t& reply_callback) {
    return georadiusbymember(key, member, radius, unit, with_coord, with_dist, with_hash, asc_order, count, store_key,
      "", reply_callback);
  }
  
  client&
  client::georadiusbymember(const std::string& key, const std::string& member, double radius, geo_unit unit,
    bool with_coord, bool with_dist, bool with_hash, bool asc_order, std::size_t count,
    const std::string& store_key, const std::string& storedist_key,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"GEORADIUSBYMEMBER", key, member, std::to_string(radius), geo_unit_to_string(unit)};
  
    /**
   * with_coord (optional)
   */
    if (with_coord) {
      cmd.emplace_back("WITHCOORD");
    }
  
    /**
   * with_dist (optional)
   */
    if (with_dist) {
      cmd.emplace_back("WITHDIST");
    }
  
    /**
   * with_hash (optional)
   */
    if (with_hash) {
      cmd.emplace_back("WITHHASH");
    }
  
    /**
   * order (optional)
   */
    cmd.emplace_back(asc_order ? "ASC" : "DESC");
  
    /**
   * count (optional)
   */
    if (count > 0) {
      cmd.emplace_back("COUNT");
      cmd.push_back(std::to_string(count));
    }
  
    /**
   * store_key (optional)
   */
    if (!store_key.empty()) {
      cmd.emplace_back("STOREDIST");
      cmd.push_back(storedist_key);
    }
  
    /**
   * storedist_key (optional)
   */
    if (!storedist_key.empty()) {
      cmd.emplace_back("STOREDIST");
      cmd.push_back(storedist_key);
    }
  
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::get(const std::string& key, const reply_callback_t& reply_callback) {
    send({"GET", key}, reply_callback);
    return *this;
  }
  
  client&
  client::getbit(const std::string& key, int offset, const reply_callback_t& reply_callback) {
    send({"GETBIT", key, std::to_string(offset)}, reply_callback);
    return *this;
  }
  
  client&
  client::getrange(const std::string& key, int start, int end, const reply_callback_t& reply_callback) {
    send({"GETRANGE", key, std::to_string(start), std::to_string(end)}, reply_callback);
    return *this;
  }
  
  client&
  client::getset(const std::string& key, const std::string& val, const reply_callback_t& reply_callback) {
    send({"GETSET", key, val}, reply_callback);
    return *this;
  }
  
  client&
  client::hdel(const std::string& key, const std::vector<std::string>& fields, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"HDEL", key};
    cmd.insert(cmd.end(), fields.begin(), fields.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::hexists(const std::string& key, const std::string& field, const reply_callback_t& reply_callback) {
    send({"HEXISTS", key, field}, reply_callback);
    return *this;
  }
  
  client&
  client::hget(const std::string& key, const std::string& field, const reply_callback_t& reply_callback) {
    send({"HGET", key, field}, reply_callback);
    return *this;
  }
  
  client&
  client::hgetall(const std::string& key, const reply_callback_t& reply_callback) {
    send({"HGETALL", key}, reply_callback);
    return *this;
  }
  
  client&
  client::hincrby(const std::string& key, const std::string& field, int incr, const reply_callback_t& reply_callback) {
    send({"HINCRBY", key, field, std::to_string(incr)}, reply_callback);
    return *this;
  }
  
  client&
  client::hincrbyfloat(const std::string& key, const std::string& field, float incr,
    const reply_callback_t& reply_callback) {
    send({"HINCRBYFLOAT", key, field, std::to_string(incr)}, reply_callback);
    return *this;
  }
  
  client&
  client::hkeys(const std::string& key, const reply_callback_t& reply_callback) {
    send({"HKEYS", key}, reply_callback);
    return *this;
  }
  
  client&
  client::hlen(const std::string& key, const reply_callback_t& reply_callback) {
    send({"HLEN", key}, reply_callback);
    return *this;
  }
  
  client&
  client::hmget(const std::string& key, const std::vector<std::string>& fields,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"HMGET", key};
    cmd.insert(cmd.end(), fields.begin(), fields.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::hmset(const std::string& key, const std::vector<std::pair<std::string, std::string>>& field_val,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"HMSET", key};
    for (const auto& obj : field_val) {
      cmd.push_back(obj.first);
      cmd.push_back(obj.second);
    }
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::hscan(const std::string& key, std::size_t cursor, const reply_callback_t& reply_callback) {
    return hscan(key, cursor, "", 0, reply_callback);
  }
  
  client&
  client::hscan(const std::string& key, std::size_t cursor, const std::string& pattern,
    const reply_callback_t& reply_callback) {
    return hscan(key, cursor, pattern, 0, reply_callback);
  }
  
  client&
  client::hscan(const std::string& key, std::size_t cursor, std::size_t count, const reply_callback_t& reply_callback) {
    return hscan(key, cursor, "", count, reply_callback);
  }
  
  client&
  client::hscan(const std::string& key, std::size_t cursor, const std::string& pattern, std::size_t count,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"HSCAN", key, std::to_string(cursor)};
  
    if (!pattern.empty()) {
      cmd.emplace_back("MATCH");
      cmd.push_back(pattern);
    }
  
    if (count > 0) {
      cmd.emplace_back("COUNT");
      cmd.push_back(std::to_string(count));
    }
  
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::hset(const std::string& key, const std::string& field, const std::string& value,
    const reply_callback_t& reply_callback) {
    send({"HSET", key, field, value}, reply_callback);
    return *this;
  }
  
  client&
  client::hsetnx(const std::string& key, const std::string& field, const std::string& value,
    const reply_callback_t& reply_callback) {
    send({"HSETNX", key, field, value}, reply_callback);
    return *this;
  }
  
  client&
  client::hstrlen(const std::string& key, const std::string& field, const reply_callback_t& reply_callback) {
    send({"HSTRLEN", key, field}, reply_callback);
    return *this;
  }
  
  client&
  client::hvals(const std::string& key, const reply_callback_t& reply_callback) {
    send({"HVALS", key}, reply_callback);
    return *this;
  }
  
  client&
  client::incr(const std::string& key, const reply_callback_t& reply_callback) {
    send({"INCR", key}, reply_callback);
    return *this;
  }
  
  client&
  client::incrby(const std::string& key, int incr, const reply_callback_t& reply_callback) {
    send({"INCRBY", key, std::to_string(incr)}, reply_callback);
    return *this;
  }
  
  client&
  client::incrbyfloat(const std::string& key, float incr, const reply_callback_t& reply_callback) {
    send({"INCRBYFLOAT", key, std::to_string(incr)}, reply_callback);
    return *this;
  }
  
  client&
  client::info(const reply_callback_t& reply_callback) {
    send({"INFO"}, reply_callback);
    return *this;
  }
  
  client&
  client::info(const std::string& section, const reply_callback_t& reply_callback) {
    send({"INFO", section}, reply_callback);
    return *this;
  }
  
  client&
  client::keys(const std::string& pattern, const reply_callback_t& reply_callback) {
    send({"KEYS", pattern}, reply_callback);
    return *this;
  }
  
  client&
  client::lastsave(const reply_callback_t& reply_callback) {
    send({"LASTSAVE"}, reply_callback);
    return *this;
  }
  
  client&
  client::lindex(const std::string& key, int index, const reply_callback_t& reply_callback) {
    send({"LINDEX", key, std::to_string(index)}, reply_callback);
    return *this;
  }
  
  client&
  client::linsert(const std::string& key, const std::string& before_after, const std::string& pivot,
    const std::string& value, const reply_callback_t& reply_callback) {
    send({"LINSERT", key, before_after, pivot, value}, reply_callback);
    return *this;
  }
  
  client&
  client::llen(const std::string& key, const reply_callback_t& reply_callback) {
    send({"LLEN", key}, reply_callback);
    return *this;
  }
  
  client&
  client::lpop(const std::string& key, const reply_callback_t& reply_callback) {
    send({"LPOP", key}, reply_callback);
    return *this;
  }
  
  client&
  client::lpush(const std::string& key, const std::vector<std::string>& values,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"LPUSH", key};
    cmd.insert(cmd.end(), values.begin(), values.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::lpushx(const std::string& key, const std::string& value, const reply_callback_t& reply_callback) {
    send({"LPUSHX", key, value}, reply_callback);
    return *this;
  }
  
  client&
  client::lrange(const std::string& key, int start, int stop, const reply_callback_t& reply_callback) {
    send({"LRANGE", key, std::to_string(start), std::to_string(stop)}, reply_callback);
    return *this;
  }
  
  client&
  client::lrem(const std::string& key, int count, const std::string& value, const reply_callback_t& reply_callback) {
    send({"LREM", key, std::to_string(count), value}, reply_callback);
    return *this;
  }
  
  client&
  client::lset(const std::string& key, int index, const std::string& value, const reply_callback_t& reply_callback) {
    send({"LSET", key, std::to_string(index), value}, reply_callback);
    return *this;
  }
  
  client&
  client::ltrim(const std::string& key, int start, int stop, const reply_callback_t& reply_callback) {
    send({"LTRIM", key, std::to_string(start), std::to_string(stop)}, reply_callback);
    return *this;
  }
  
  client&
  client::mget(const std::vector<std::string>& keys, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"MGET"};
    cmd.insert(cmd.end(), keys.begin(), keys.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::migrate(const std::string& host, int port, const std::string& key, const std::string& dest_db, int timeout,
    const reply_callback_t& reply_callback) {
    send({"MIGRATE", host, std::to_string(port), key, dest_db, std::to_string(timeout)}, reply_callback);
    return *this;
  }
  
  client&
  client::migrate(const std::string& host, int port, const std::string& key, const std::string& dest_db, int timeout,
    bool copy, bool replace, const std::vector<std::string>& keys,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"MIGRATE", host, std::to_string(port), key, dest_db, std::to_string(timeout)};
    if (copy) { cmd.emplace_back("COPY"); }
    if (replace) { cmd.emplace_back("REPLACE"); }
    if (!keys.empty()) {
      cmd.emplace_back("KEYS");
      cmd.insert(cmd.end(), keys.begin(), keys.end());
    }
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::monitor(const reply_callback_t& reply_callback) {
    send({"MONITOR"}, reply_callback);
    return *this;
  }
  
  client&
  client::move(const std::string& key, const std::string& db, const reply_callback_t& reply_callback) {
    send({"MOVE", key, db}, reply_callback);
    return *this;
  }
  
  client&
  client::mset(const std::vector<std::pair<std::string, std::string>>& key_vals,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"MSET"};
    for (const auto& obj : key_vals) {
      cmd.push_back(obj.first);
      cmd.push_back(obj.second);
    }
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::msetnx(const std::vector<std::pair<std::string, std::string>>& key_vals,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"MSETNX"};
    for (const auto& obj : key_vals) {
      cmd.push_back(obj.first);
      cmd.push_back(obj.second);
    }
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::multi(const reply_callback_t& reply_callback) {
    send({"MULTI"}, reply_callback);
    return *this;
  }
  
  client&
  client::object(const std::string& subcommand, const std::vector<std::string>& args,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"OBJECT", subcommand};
    cmd.insert(cmd.end(), args.begin(), args.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::persist(const std::string& key, const reply_callback_t& reply_callback) {
    send({"PERSIST", key}, reply_callback);
    return *this;
  }
  
  client&
  client::pexpire(const std::string& key, int ms, const reply_callback_t& reply_callback) {
    send({"PEXPIRE", key, std::to_string(ms)}, reply_callback);
    return *this;
  }
  
  client&
  client::pexpireat(const std::string& key, int ms_timestamp, const reply_callback_t& reply_callback) {
    send({"PEXPIREAT", key, std::to_string(ms_timestamp)}, reply_callback);
    return *this;
  }
  
  client&
  client::pfadd(const std::string& key, const std::vector<std::string>& elements,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"PFADD", key};
    cmd.insert(cmd.end(), elements.begin(), elements.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::pfcount(const std::vector<std::string>& keys, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"PFCOUNT"};
    cmd.insert(cmd.end(), keys.begin(), keys.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::pfmerge(const std::string& destkey, const std::vector<std::string>& sourcekeys,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"PFMERGE", destkey};
    cmd.insert(cmd.end(), sourcekeys.begin(), sourcekeys.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::ping(const reply_callback_t& reply_callback) {
    send({"PING"}, reply_callback);
    return *this;
  }
  
  client&
  client::ping(const std::string& message, const reply_callback_t& reply_callback) {
    send({"PING", message}, reply_callback);
    return *this;
  }
  
  client&
  client::psetex(const std::string& key, int64_t ms, const std::string& val,
    const reply_callback_t& reply_callback) {
    send({"PSETEX", key, std::to_string(ms), val}, reply_callback);
    return *this;
  }
  
  client&
  client::publish(const std::string& channel, const std::string& message, const reply_callback_t& reply_callback) {
    send({"PUBLISH", channel, message}, reply_callback);
    return *this;
  }
  
  client&
  client::pubsub(const std::string& subcommand, const std::vector<std::string>& args,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"PUBSUB", subcommand};
    cmd.insert(cmd.end(), args.begin(), args.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::pttl(const std::string& key, const reply_callback_t& reply_callback) {
    send({"PTTL", key}, reply_callback);
    return *this;
  }
  
  client&
  client::quit(const reply_callback_t& reply_callback) {
    send({"QUIT"}, reply_callback);
    return *this;
  }
  
  client&
  client::randomkey(const reply_callback_t& reply_callback) {
    send({"RANDOMKEY"}, reply_callback);
    return *this;
  }
  
  client&
  client::readonly(const reply_callback_t& reply_callback) {
    send({"READONLY"}, reply_callback);
    return *this;
  }
  
  client&
  client::readwrite(const reply_callback_t& reply_callback) {
    send({"READWRITE"}, reply_callback);
    return *this;
  }
  
  client&
  client::rename(const std::string& key, const std::string& newkey, const reply_callback_t& reply_callback) {
    send({"RENAME", key, newkey}, reply_callback);
    return *this;
  }
  
  client&
  client::renamenx(const std::string& key, const std::string& newkey, const reply_callback_t& reply_callback) {
    send({"RENAMENX", key, newkey}, reply_callback);
    return *this;
  }
  
  client&
  client::restore(const std::string& key, int ttl, const std::string& serialized_value,
    const reply_callback_t& reply_callback) {
    send({"RESTORE", key, std::to_string(ttl), serialized_value}, reply_callback);
    return *this;
  }
  
  client&
  client::restore(const std::string& key, int ttl, const std::string& serialized_value, const std::string& replace,
    const reply_callback_t& reply_callback) {
    send({"RESTORE", key, std::to_string(ttl), serialized_value, replace}, reply_callback);
    return *this;
  }
  
  client&
  client::role(const reply_callback_t& reply_callback) {
    send({"ROLE"}, reply_callback);
    return *this;
  }
  
  client&
  client::rpop(const std::string& key, const reply_callback_t& reply_callback) {
    send({"RPOP", key}, reply_callback);
    return *this;
  }
  
  client&
  client::rpoplpush(const std::string& source, const std::string& destination, const reply_callback_t& reply_callback) {
    send({"RPOPLPUSH", source, destination}, reply_callback);
    return *this;
  }
  
  client&
  client::rpush(const std::string& key, const std::vector<std::string>& values,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"RPUSH", key};
    cmd.insert(cmd.end(), values.begin(), values.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::rpushx(const std::string& key, const std::string& value, const reply_callback_t& reply_callback) {
    send({"RPUSHX", key, value}, reply_callback);
    return *this;
  }
  
  client&
  client::sadd(const std::string& key, const std::vector<std::string>& members,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"SADD", key};
    cmd.insert(cmd.end(), members.begin(), members.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::save(const reply_callback_t& reply_callback) {
    send({"SAVE"}, reply_callback);
    return *this;
  }
  
  client&
  client::scan(std::size_t cursor, const reply_callback_t& reply_callback) {
    return scan(cursor, "", 0, reply_callback);
  }
  
  client&
  client::scan(std::size_t cursor, const std::string& pattern, const reply_callback_t& reply_callback) {
    return scan(cursor, pattern, 0, reply_callback);
  }
  
  client&
  client::scan(std::size_t cursor, std::size_t count, const reply_callback_t& reply_callback) {
    return scan(cursor, "", count, reply_callback);
  }
  
  client&
  client::scan(std::size_t cursor, const std::string& pattern, std::size_t count,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"SCAN", std::to_string(cursor)};
  
    if (!pattern.empty()) {
      cmd.emplace_back("MATCH");
      cmd.push_back(pattern);
    }
  
    if (count > 0) {
      cmd.emplace_back("COUNT");
      cmd.push_back(std::to_string(count));
    }
  
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::scard(const std::string& key, const reply_callback_t& reply_callback) {
    send({"SCARD", key}, reply_callback);
    return *this;
  }
  
  client&
  client::script_debug(const std::string& mode, const reply_callback_t& reply_callback) {
    send({"SCRIPT", "DEBUG", mode}, reply_callback);
    return *this;
  }
  
  client&
  client::script_exists(const std::vector<std::string>& scripts, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"SCRIPT", "EXISTS"};
    cmd.insert(cmd.end(), scripts.begin(), scripts.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::script_flush(const reply_callback_t& reply_callback) {
    send({"SCRIPT", "FLUSH"}, reply_callback);
    return *this;
  }
  
  client&
  client::script_kill(const reply_callback_t& reply_callback) {
    send({"SCRIPT", "KILL"}, reply_callback);
    return *this;
  }
  
  client&
  client::script_load(const std::string& script, const reply_callback_t& reply_callback) {
    send({"SCRIPT", "LOAD", script}, reply_callback);
    return *this;
  }
  
  client&
  client::sdiff(const std::vector<std::string>& keys, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"SDIFF"};
    cmd.insert(cmd.end(), keys.begin(), keys.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::sdiffstore(const std::string& destination, const std::vector<std::string>& keys,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"SDIFFSTORE", destination};
    cmd.insert(cmd.end(), keys.begin(), keys.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::select(int index, const reply_callback_t& reply_callback) {
    std::lock_guard<std::mutex> lock(m_callbacks_mutex);
  
    unprotected_select(index, reply_callback);
  
    return *this;
  }
  
  void
  client::unprotected_select(int index, const reply_callback_t& reply_callback) {
    /**
   * save the index of the database for reconnect attempts.
   */
    m_database_index = index;
    /**
   * save command in the pipeline
   */
    unprotected_send({"SELECT", std::to_string(index)}, reply_callback);
  }
  
  client&
  client::set(const std::string& key, const std::string& value, const reply_callback_t& reply_callback) {
    send({"SET", key, value}, reply_callback);
    return *this;
  }
  
  client&
  client::set_advanced(const std::string& key, const std::string& value, const reply_callback_t& reply_callback) {
    send({"SET", key, value}, reply_callback);
    return *this;
  }
  
  client&
  client::set_advanced(const std::string& key, const std::string& value, bool ex, int ex_sec, bool px, int px_milli,
    bool nx, bool xx, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"SET", key, value};
    if (ex) {
      cmd.emplace_back("EX");
      cmd.push_back(std::to_string(ex_sec));
    }
    if (px) {
      cmd.emplace_back("PX");
      cmd.push_back(std::to_string(px_milli));
    }
    if (nx) { cmd.emplace_back("NX"); }
    if (xx) { cmd.emplace_back("XX"); }
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::setbit_(const std::string& key, int offset, const std::string& value,
    const reply_callback_t& reply_callback) {
    send({"SETBIT", key, std::to_string(offset), value}, reply_callback);
    return *this;
  }
  
  client&
  client::setex(const std::string& key, int64_t seconds, const std::string& value, const reply_callback_t& reply_callback) {
    send({"SETEX", key, std::to_string(seconds), value}, reply_callback);
    return *this;
  }
  
  client&
  client::setnx(const std::string& key, const std::string& value, const reply_callback_t& reply_callback) {
    send({"SETNX", key, value}, reply_callback);
    return *this;
  }
  
  client&
  client::setrange(const std::string& key, int offset, const std::string& value,
    const reply_callback_t& reply_callback) {
    send({"SETRANGE", key, std::to_string(offset), value}, reply_callback);
    return *this;
  }
  
  client&
  client::shutdown(const reply_callback_t& reply_callback) {
    send({"SHUTDOWN"}, reply_callback);
    return *this;
  }
  
  client&
  client::shutdown(const std::string& save, const reply_callback_t& reply_callback) {
    send({"SHUTDOWN", save}, reply_callback);
    return *this;
  }
  
  client&
  client::sinter(const std::vector<std::string>& keys, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"SINTER"};
    cmd.insert(cmd.end(), keys.begin(), keys.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::sinterstore(const std::string& destination, const std::vector<std::string>& keys,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"SINTERSTORE", destination};
    cmd.insert(cmd.end(), keys.begin(), keys.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::sismember(const std::string& key, const std::string& member, const reply_callback_t& reply_callback) {
    send({"SISMEMBER", key, member}, reply_callback);
    return *this;
  }
  
  client&
  client::slaveof(const std::string& host, int port, const reply_callback_t& reply_callback) {
    send({"SLAVEOF", host, std::to_string(port)}, reply_callback);
    return *this;
  }
  
  client&
  client::slowlog(const std::string subcommand, const reply_callback_t& reply_callback) {
    send({"SLOWLOG", subcommand}, reply_callback);
    return *this;
  }
  
  client&
  client::slowlog(const std::string subcommand, const std::string& argument, const reply_callback_t& reply_callback) {
    send({"SLOWLOG", subcommand, argument}, reply_callback);
    return *this;
  }
  
  client&
  client::smembers(const std::string& key, const reply_callback_t& reply_callback) {
    send({"SMEMBERS", key}, reply_callback);
    return *this;
  }
  
  client&
  client::smove(const std::string& source, const std::string& destination, const std::string& member,
    const reply_callback_t& reply_callback) {
    send({"SMOVE", source, destination, member}, reply_callback);
    return *this;
  }
  
  
  client&
  client::sort(const std::string& key, const reply_callback_t& reply_callback) {
    send({"SORT", key}, reply_callback);
    return *this;
  }
  
  client&
  client::sort(const std::string& key, const std::vector<std::string>& get_patterns, bool asc_order, bool alpha,
    const reply_callback_t& reply_callback) {
    return sort(key, "", false, 0, 0, get_patterns, asc_order, alpha, "", reply_callback);
  }
  
  client&
  client::sort(const std::string& key, std::size_t offset, std::size_t count,
    const std::vector<std::string>& get_patterns, bool asc_order, bool alpha,
    const reply_callback_t& reply_callback) {
    return sort(key, "", true, offset, count, get_patterns, asc_order, alpha, "", reply_callback);
  }
  
  client&
  client::sort(const std::string& key, const std::string& by_pattern, const std::vector<std::string>& get_patterns,
    bool asc_order, bool alpha, const reply_callback_t& reply_callback) {
    return sort(key, by_pattern, false, 0, 0, get_patterns, asc_order, alpha, "", reply_callback);
  }
  
  client&
  client::sort(const std::string& key, const std::vector<std::string>& get_patterns, bool asc_order, bool alpha,
    const std::string& store_dest, const reply_callback_t& reply_callback) {
    return sort(key, "", false, 0, 0, get_patterns, asc_order, alpha, store_dest, reply_callback);
  }
  
  client&
  client::sort(const std::string& key, std::size_t offset, std::size_t count,
    const std::vector<std::string>& get_patterns, bool asc_order, bool alpha, const std::string& store_dest,
    const reply_callback_t& reply_callback) {
    return sort(key, "", true, offset, count, get_patterns, asc_order, alpha, store_dest, reply_callback);
  }
  
  client&
  client::sort(const std::string& key, const std::string& by_pattern, const std::vector<std::string>& get_patterns,
    bool asc_order, bool alpha, const std::string& store_dest, const reply_callback_t& reply_callback) {
    return sort(key, by_pattern, false, 0, 0, get_patterns, asc_order, alpha, store_dest, reply_callback);
  }
  
  client&
  client::sort(const std::string& key, const std::string& by_pattern, std::size_t offset, std::size_t count,
    const std::vector<std::string>& get_patterns, bool asc_order, bool alpha,
    const reply_callback_t& reply_callback) {
    return sort(key, by_pattern, true, offset, count, get_patterns, asc_order, alpha, "", reply_callback);
  }
  
  client&
  client::sort(const std::string& key, const std::string& by_pattern, std::size_t offset, std::size_t count,
    const std::vector<std::string>& get_patterns, bool asc_order, bool alpha, const std::string& store_dest,
    const reply_callback_t& reply_callback) {
    return sort(key, by_pattern, true, offset, count, get_patterns, asc_order, alpha, store_dest, reply_callback);
  }
  
  client&
  client::sort(const std::string& key, const std::string& by_pattern, bool limit, std::size_t offset, std::size_t count,
    const std::vector<std::string>& get_patterns, bool asc_order, bool alpha, const std::string& store_dest,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"SORT", key};
  
    /**
   * add by pattern (optional)
   */
    if (!by_pattern.empty()) {
      cmd.emplace_back("BY");
      cmd.push_back(by_pattern);
    }
  
    /**
   * add limit (optional)
   */
    if (limit) {
      cmd.emplace_back("LIMIT");
      cmd.push_back(std::to_string(offset));
      cmd.push_back(std::to_string(count));
    }
  
    /**
   * add get pattern (optional)
   */
    for (const auto& get_pattern : get_patterns) {
      if (get_pattern.empty()) {
        continue;
      }
  
      cmd.emplace_back("GET");
      cmd.push_back(get_pattern);
    }
  
    /**
   * add order by (optional)
   */
    cmd.emplace_back(asc_order ? "ASC" : "DESC");
  
    /**
   * add alpha (optional)
   */
    if (alpha) {
      cmd.emplace_back("ALPHA");
    }
  
    /**
   * add store dest (optional)
   */
    if (!store_dest.empty()) {
      cmd.emplace_back("STORE");
      cmd.push_back(store_dest);
    }
  
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::spop(const std::string& key, const reply_callback_t& reply_callback) {
    send({"SPOP", key}, reply_callback);
    return *this;
  }
  
  client&
  client::spop(const std::string& key, int count, const reply_callback_t& reply_callback) {
    send({"SPOP", key, std::to_string(count)}, reply_callback);
    return *this;
  }
  
  client&
  client::srandmember(const std::string& key, const reply_callback_t& reply_callback) {
    send({"SRANDMEMBER", key}, reply_callback);
    return *this;
  }
  
  client&
  client::srandmember(const std::string& key, int count, const reply_callback_t& reply_callback) {
    send({"SRANDMEMBER", key, std::to_string(count)}, reply_callback);
    return *this;
  }
  
  client&
  client::srem(const std::string& key, const std::vector<std::string>& members,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"SREM", key};
    cmd.insert(cmd.end(), members.begin(), members.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::sscan(const std::string& key, std::size_t cursor, const reply_callback_t& reply_callback) {
    return sscan(key, cursor, "", 0, reply_callback);
  }
  
  client&
  client::sscan(const std::string& key, std::size_t cursor, const std::string& pattern,
    const reply_callback_t& reply_callback) {
    return sscan(key, cursor, pattern, 0, reply_callback);
  }
  
  client&
  client::sscan(const std::string& key, std::size_t cursor, std::size_t count, const reply_callback_t& reply_callback) {
    return sscan(key, cursor, "", count, reply_callback);
  }
  
  client&
  client::sscan(const std::string& key, std::size_t cursor, const std::string& pattern, std::size_t count,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"SSCAN", key, std::to_string(cursor)};
  
    if (!pattern.empty()) {
      cmd.emplace_back("MATCH");
      cmd.push_back(pattern);
    }
  
    if (count > 0) {
      cmd.emplace_back("COUNT");
      cmd.push_back(std::to_string(count));
    }
  
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::strlen(const std::string& key, const reply_callback_t& reply_callback) {
    send({"STRLEN", key}, reply_callback);
    return *this;
  }
  
  client&
  client::sunion(const std::vector<std::string>& keys, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"SUNION"};
    cmd.insert(cmd.end(), keys.begin(), keys.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::sunionstore(const std::string& destination, const std::vector<std::string>& keys,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"SUNIONSTORE", destination};
    cmd.insert(cmd.end(), keys.begin(), keys.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::sync(const reply_callback_t& reply_callback) {
    send({"SYNC"}, reply_callback);
    return *this;
  }
  
  client&
  client::time(const reply_callback_t& reply_callback) {
    send({"TIME"}, reply_callback);
    return *this;
  }
  
  client&
  client::ttl(const std::string& key, const reply_callback_t& reply_callback) {
    send({"TTL", key}, reply_callback);
    return *this;
  }
  
  client&
  client::type(const std::string& key, const reply_callback_t& reply_callback) {
    send({"TYPE", key}, reply_callback);
    return *this;
  }
  
  client&
  client::unwatch(const reply_callback_t& reply_callback) {
    send({"UNWATCH"}, reply_callback);
    return *this;
  }
  
  client&
  client::wait(int numslaves, int timeout, const reply_callback_t& reply_callback) {
    send({"WAIT", std::to_string(numslaves), std::to_string(timeout)}, reply_callback);
    return *this;
  }
  
  client&
  client::watch(const std::vector<std::string>& keys, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"WATCH"};
    cmd.insert(cmd.end(), keys.begin(), keys.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::xack(const std::string& stream, const std::string& group, const std::vector<std::string>& message_ids,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"XACK", stream, group};
  
    /**
   * ids
   */
    for (auto& id : message_ids) {
      cmd.push_back(id);
    }
  
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::xadd(const std::string& key, const std::string& id,
    const std::multimap<std::string, std::string>& field_members, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"XADD", key, id};
  
    /**
   * score members
   */
    for (auto& sm : field_members) {
      cmd.push_back(sm.first);
      cmd.push_back(sm.second);
    }
  
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::xclaim(const std::string& stream, const std::string& group, const std::string& consumer, int min_idle_time,
    const std::vector<std::string>& message_ids, const xclaim_options_t& options, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"XCLAIM", stream, group, consumer, std::to_string(min_idle_time)};
  
    /**
   * ids
   */
    for (auto& id : message_ids) {
      cmd.push_back(id);
    }
    if (options.Idle > 0) {
      cmd.emplace_back("IDLE");
      cmd.push_back(std::to_string(options.Idle));
    }
    if (options.Time != nullptr) {
      cmd.emplace_back("TIME");
      cmd.push_back(std::to_string(static_cast<long int>(*options.Time)));
    }
    if (options.RetryCount > 0) {
      cmd.emplace_back("RETRYCOUNT");
      cmd.push_back(std::to_string(options.RetryCount));
    }
    if (options.Force) {
      cmd.emplace_back("FORCE");
    }
    if (options.JustId) {
      cmd.emplace_back("JUSTID");
    }
  
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::xdel(const std::string& key, const std::vector<std::string>& id_members,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"XDEL", key};
  
    /**
   * ids
   */
    for (auto& id : id_members) {
      cmd.push_back(id);
    }
  
    send(cmd, reply_callback);
    return *this;
  }
  
  //<editor-fold desc="XGroup">
  client&
  client::xgroup_create(const std::string& key, const std::string& group_name, const reply_callback_t& reply_callback) {
    send({"XGROUP", "CREATE", key, group_name, "$"}, reply_callback);
    return *this;
  }
  
  client&
  client::xgroup_create(const std::string& key, const std::string& group_name, const std::string& id,
    const reply_callback_t& reply_callback) {
    send({"XGROUP", "CREATE", key, group_name, id}, reply_callback);
    return *this;
  }
  
  client&
  client::xgroup_set_id(const std::string& key, const std::string& group_name, const reply_callback_t& reply_callback) {
    send({"XGROUP", "SETID", key, group_name, "$"}, reply_callback);
    return *this;
  }
  
  client&
  client::xgroup_set_id(const std::string& key, const std::string& group_name, const std::string& id,
    const reply_callback_t& reply_callback) {
    send({"XGROUP", "SETID", key, group_name, id}, reply_callback);
    return *this;
  }
  
  client&
  client::xgroup_destroy(const std::string& key, const std::string& group_name,
    const reply_callback_t& reply_callback) {
    send({"XGROUP", "DESTROY", key, group_name}, reply_callback);
    return *this;
  }
  
  client&
  client::xgroup_del_consumer(const std::string& key, const std::string& group_name, const std::string& consumer_name,
    const reply_callback_t& reply_callback) {
    send({"XGROUP", "DELCONSUMER", key, group_name, consumer_name}, reply_callback);
    return *this;
  }
  //</editor-fold>
  
  client&
  client::xinfo_consumers(const std::string& key, const std::string& group_name,
    const reply_callback_t& reply_callback) {
    send({"XINFO", "CONSUMERS", key, group_name}, reply_callback);
    return *this;
  }
  
  client&
  client::xinfo_groups(const std::string& key, const reply_callback_t& reply_callback) {
    send({"XINFO", "GROUPS", key}, reply_callback);
    return *this;
  }
  
  client&
  client::xinfo_stream(const std::string& stream, const reply_callback_t& reply_callback) {
    send({"XINFO", "STREAM", stream}, reply_callback);
    return *this;
  }
  
  /**
  	 * @brief Returns the number of entries inside a stream.
  	 * If the specified key does not exist the command returns zero, as if the stream was empty.
  	 * However note that unlike other Redis types, zero-length streams are possible, so you should call TYPE or EXISTS in order to check if a key exists or not.
  	 * Streams are not auto-deleted once they have no entries inside (for instance after an XDEL call), because the stream may have consumer groups associated with it.
  	 * @param stream
  	 * @param reply_callback
  	 * @return Integer reply: the number of entries of the stream at key.
  	 */
  client&
  client::xlen(const std::string& stream, const reply_callback_t& reply_callback) {
    send({"XLEN", stream}, reply_callback);
    return *this;
  }
  
  client&
  client::xpending(const std::string& stream,
    const std::string& group,
    const xpending_options_t& options,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"XPENDING", stream, group};
    if (!options.Range.Start.empty()) {
      cmd.emplace_back(options.Range.Start);
      cmd.emplace_back(options.Range.Stop);
      cmd.emplace_back(std::to_string(options.Range.Count));
    }
  
    if (!options.Consumer.empty()) {
      cmd.push_back(options.Consumer);
    }
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::xrange(const std::string& stream, const range_options_t& options, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"XRANGE", stream, options.Start, options.Stop};
    if (options.Count > 0) {
      cmd.emplace_back("COUNT");
      cmd.emplace_back(std::to_string(options.Count));
    }
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::xread(const xread_options_t& a, const client::reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"XREAD"};
    if (a.Count > 0) {
      cmd.emplace_back("COUNT");
      cmd.push_back(std::to_string(a.Count));
    }
  
    if (a.Block >= 0) {
      cmd.emplace_back("BLOCK");
      cmd.push_back(std::to_string(a.Block));
    }
  
    // Add streams
    cmd.emplace_back("STREAMS");
    cmd.insert(cmd.end(), a.Streams.first.begin(), a.Streams.first.end());
    // Add ids
    cmd.insert(cmd.end(), a.Streams.second.begin(), a.Streams.second.end());
  
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::xreadgroup(const xreadgroup_options_t& a, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"XREADGROUP",
      "GROUP", a.Group, a.Consumer};
    if (a.Count > 0) {
      cmd.emplace_back("COUNT");
      cmd.push_back(std::to_string(a.Count));
    }
  
    if (a.Block >= 0) {
      cmd.emplace_back("BLOCK");
      cmd.push_back(std::to_string(a.Block));
    }
  
    if (a.NoAck) {
      cmd.emplace_back("NOACK");
    }
  
    // Add streams
    cmd.emplace_back("STREAMS");
    cmd.insert(cmd.end(), a.Streams.first.begin(), a.Streams.first.end());
    // Add ids
    cmd.insert(cmd.end(), a.Streams.second.begin(), a.Streams.second.end());
  
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::xrevrange(const std::string& key, const range_options_t& range_args, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"XREVRANGE", key, range_args.Start, range_args.Stop};
    if (range_args.Count > 0) {
      cmd.emplace_back(std::to_string(range_args.Count));
    }
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::xtrim(const std::string& stream, int max_len, const client::reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"XTRIM", stream, "MAXLEN", std::to_string(max_len)};
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::xtrim_approx(const std::string& key, int max_len,
    const cpp_redis::client::reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"XTRIM", key, "MAXLEN", "~", std::to_string(max_len)};
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::zadd(const std::string& key, const std::vector<std::string>& options,
    const std::multimap<std::string, std::string>& score_members, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"ZADD", key};
  
    /**
   * options
   */
    cmd.insert(cmd.end(), options.begin(), options.end());
  
    /**
   * score members
   */
    for (auto& sm : score_members) {
      cmd.push_back(sm.first);
      cmd.push_back(sm.second);
    }
  
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::zcard(const std::string& key, const reply_callback_t& reply_callback) {
    send({"ZCARD", key}, reply_callback);
    return *this;
  }
  
  client&
  client::zcount(const std::string& key, int min, int max, const reply_callback_t& reply_callback) {
    send({"ZCOUNT", key, std::to_string(min), std::to_string(max)}, reply_callback);
    return *this;
  }
  
  client&
  client::zcount(const std::string& key, double min, double max, const reply_callback_t& reply_callback) {
    send({"ZCOUNT", key, std::to_string(min), std::to_string(max)}, reply_callback);
    return *this;
  }
  
  client&
  client::zcount(const std::string& key, const std::string& min, const std::string& max,
    const reply_callback_t& reply_callback) {
    send({"ZCOUNT", key, min, max}, reply_callback);
    return *this;
  }
  
  client&
  client::zincrby(const std::string& key, int incr, const std::string& member, const reply_callback_t& reply_callback) {
    send({"ZINCRBY", key, std::to_string(incr), member}, reply_callback);
    return *this;
  }
  
  client&
  client::zincrby(const std::string& key, double incr, const std::string& member,
    const reply_callback_t& reply_callback) {
    send({"ZINCRBY", key, std::to_string(incr), member}, reply_callback);
    return *this;
  }
  
  client&
  client::zincrby(const std::string& key, const std::string& incr, const std::string& member,
    const reply_callback_t& reply_callback) {
    send({"ZINCRBY", key, incr, member}, reply_callback);
    return *this;
  }
  
  client&
  client::zinterstore(const std::string& destination, std::size_t numkeys, const std::vector<std::string>& keys,
    const std::vector<std::size_t> weights, aggregate_method method,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"ZINTERSTORE", destination, std::to_string(numkeys)};
  
    /**
   * keys
   */
    for (const auto& key : keys) {
      cmd.push_back(key);
    }
  
    /**
   * weights (optional)
   */
    if (!weights.empty()) {
      cmd.emplace_back("WEIGHTS");
  
      for (auto weight : weights) {
        cmd.push_back(std::to_string(weight));
      }
    }
  
    /**
   * aggregate method
   */
    if (method != aggregate_method::server_default) {
      cmd.emplace_back("AGGREGATE");
      cmd.push_back(aggregate_method_to_string(method));
    }
  
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::zlexcount(const std::string& key, int min, int max, const reply_callback_t& reply_callback) {
    send({"ZLEXCOUNT", key, std::to_string(min), std::to_string(max)}, reply_callback);
    return *this;
  }
  
  client&
  client::zlexcount(const std::string& key, double min, double max, const reply_callback_t& reply_callback) {
    send({"ZLEXCOUNT", key, std::to_string(min), std::to_string(max)}, reply_callback);
    return *this;
  }
  
  client&
  client::zlexcount(const std::string& key, const std::string& min, const std::string& max,
    const reply_callback_t& reply_callback) {
    send({"ZLEXCOUNT", key, min, max}, reply_callback);
    return *this;
  }
  
  client&
  client::zpopmin(const std::string& key, int count, const reply_callback_t& reply_callback) {
    send({"ZPOPMIN", key, std::to_string(count)}, reply_callback);
    return *this;
  }
  
  client&
  client::zpopmax(const std::string& key, int count, const reply_callback_t& reply_callback) {
    send({"ZPOPMAX", key, std::to_string(count)}, reply_callback);
    return *this;
  }
  
  client&
  client::zrange(const std::string& key, int start, int stop, const reply_callback_t& reply_callback) {
    send({"ZRANGE", key, std::to_string(start), std::to_string(stop)}, reply_callback);
    return *this;
  }
  
  client&
  client::zrange(const std::string& key, int start, int stop, bool withscores, const reply_callback_t& reply_callback) {
    if (withscores)
      send({"ZRANGE", key, std::to_string(start), std::to_string(stop), "WITHSCORES"}, reply_callback);
    else
      send({"ZRANGE", key, std::to_string(start), std::to_string(stop)}, reply_callback);
    return *this;
  }
  
  client&
  client::zrange(const std::string& key, double start, double stop, const reply_callback_t& reply_callback) {
    send({"ZRANGE", key, std::to_string(start), std::to_string(stop)}, reply_callback);
    return *this;
  }
  
  client&
  client::zrange(const std::string& key, double start, double stop, bool withscores,
    const reply_callback_t& reply_callback) {
    if (withscores)
      send({"ZRANGE", key, std::to_string(start), std::to_string(stop), "WITHSCORES"}, reply_callback);
    else
      send({"ZRANGE", key, std::to_string(start), std::to_string(stop)}, reply_callback);
    return *this;
  }
  
  client&
  client::zrange(const std::string& key, const std::string& start, const std::string& stop,
    const reply_callback_t& reply_callback) {
    send({"ZRANGE", key, start, stop}, reply_callback);
    return *this;
  }
  
  client&
  client::zrange(const std::string& key, const std::string& start, const std::string& stop, bool withscores,
    const reply_callback_t& reply_callback) {
    if (withscores)
      send({"ZRANGE", key, start, stop, "WITHSCORES"}, reply_callback);
    else
      send({"ZRANGE", key, start, stop}, reply_callback);
    return *this;
  }
  
  client&
  client::zrangebylex(const std::string& key, int min, int max, const reply_callback_t& reply_callback) {
    return zrangebylex(key, std::to_string(min), std::to_string(max), false, 0, 0, false, reply_callback);
  }
  
  client&
  client::zrangebylex(const std::string& key, int min, int max, bool withscores,
    const reply_callback_t& reply_callback) {
    return zrangebylex(key, std::to_string(min), std::to_string(max), false, 0, 0, withscores, reply_callback);
  }
  
  client&
  client::zrangebylex(const std::string& key, double min, double max, const reply_callback_t& reply_callback) {
    return zrangebylex(key, std::to_string(min), std::to_string(max), false, 0, 0, false, reply_callback);
  }
  
  client&
  client::zrangebylex(const std::string& key, double min, double max, bool withscores,
    const reply_callback_t& reply_callback) {
    return zrangebylex(key, std::to_string(min), std::to_string(max), false, 0, 0, withscores, reply_callback);
  }
  
  client&
  client::zrangebylex(const std::string& key, const std::string& min, const std::string& max,
    const reply_callback_t& reply_callback) {
    return zrangebylex(key, min, max, false, 0, 0, false, reply_callback);
  }
  
  client&
  client::zrangebylex(const std::string& key, const std::string& min, const std::string& max, bool withscores,
    const reply_callback_t& reply_callback) {
    return zrangebylex(key, min, max, false, 0, 0, withscores, reply_callback);
  }
  
  client&
  client::zrangebylex(const std::string& key, int min, int max, std::size_t offset, std::size_t count,
    const reply_callback_t& reply_callback) {
    return zrangebylex(key, std::to_string(min), std::to_string(max), true, offset, count, false, reply_callback);
  }
  
  client&
  client::zrangebylex(const std::string& key, int min, int max, std::size_t offset, std::size_t count, bool withscores,
    const reply_callback_t& reply_callback) {
    return zrangebylex(key, std::to_string(min), std::to_string(max), true, offset, count, withscores, reply_callback);
  }
  
  client&
  client::zrangebylex(const std::string& key, double min, double max, std::size_t offset, std::size_t count,
    const reply_callback_t& reply_callback) {
    return zrangebylex(key, std::to_string(min), std::to_string(max), true, offset, count, false, reply_callback);
  }
  
  client&
  client::zrangebylex(const std::string& key, double min, double max, std::size_t offset, std::size_t count,
    bool withscores, const reply_callback_t& reply_callback) {
    return zrangebylex(key, std::to_string(min), std::to_string(max), true, offset, count, withscores, reply_callback);
  }
  
  client&
  client::zrangebylex(const std::string& key, const std::string& min, const std::string& max, std::size_t offset,
    std::size_t count, const reply_callback_t& reply_callback) {
    return zrangebylex(key, min, max, true, offset, count, false, reply_callback);
  }
  
  client&
  client::zrangebylex(const std::string& key, const std::string& min, const std::string& max, std::size_t offset,
    std::size_t count, bool withscores, const reply_callback_t& reply_callback) {
    return zrangebylex(key, min, max, true, offset, count, withscores, reply_callback);
  }
  
  client&
  client::zrangebylex(const std::string& key, const std::string& min, const std::string& max, bool limit,
    std::size_t offset, std::size_t count, bool withscores, const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"ZRANGEBYLEX", key, min, max};
  
    /**
   * withscores (optional)
   */
    if (withscores) {
      cmd.emplace_back("WITHSCORES");
    }
  
    /**
   * limit (optional)
   */
    if (limit) {
      cmd.emplace_back("LIMIT");
      cmd.push_back(std::to_string(offset));
      cmd.push_back(std::to_string(count));
    }
  
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::zrangebyscore(const std::string& key, int min, int max, const reply_callback_t& reply_callback) {
    return zrangebyscore(key, std::to_string(min), std::to_string(max), false, 0, 0, false, reply_callback);
  }
  
  client&
  client::zrangebyscore(const std::string& key, int min, int max, bool withscores,
    const reply_callback_t& reply_callback) {
    return zrangebyscore(key, std::to_string(min), std::to_string(max), false, 0, 0, withscores, reply_callback);
  }
  
  client&
  client::zrangebyscore(const std::string& key, double min, double max, const reply_callback_t& reply_callback) {
    return zrangebyscore(key, std::to_string(min), std::to_string(max), false, 0, 0, false, reply_callback);
  }
  
  client&
  client::zrangebyscore(const std::string& key, double min, double max, bool withscores,
    const reply_callback_t& reply_callback) {
    return zrangebyscore(key, std::to_string(min), std::to_string(max), false, 0, 0, withscores, reply_callback);
  }
  
  client&
  client::zrangebyscore(const std::string& key, const std::string& min, const std::string& max,
    const reply_callback_t& reply_callback) {
    return zrangebyscore(key, min, max, false, 0, 0, false, reply_callback);
  }
  
  client&
  client::zrangebyscore(const std::string& key, const std::string& min, const std::string& max, bool withscores,
    const reply_callback_t& reply_callback) {
    return zrangebyscore(key, min, max, false, 0, 0, withscores, reply_callback);
  }
  
  client&
  client::zrangebyscore(const std::string& key, int min, int max, std::size_t offset, std::size_t count,
    const reply_callback_t& reply_callback) {
    return zrangebyscore(key, std::to_string(min), std::to_string(max), true, offset, count, false, reply_callback);
  }
  
  client&
  client::zrangebyscore(const std::string& key, int min, int max, std::size_t offset, std::size_t count,
    bool withscores, const reply_callback_t& reply_callback) {
    return zrangebyscore(key, std::to_string(min), std::to_string(max), true, offset, count, withscores,
      reply_callback);
  }
  
  client&
  client::zrangebyscore(const std::string& key, double min, double max, std::size_t offset, std::size_t count,
    const reply_callback_t& reply_callback) {
    return zrangebyscore(key, std::to_string(min), std::to_string(max), true, offset, count, false, reply_callback);
  }
  
  client&
  client::zrangebyscore(const std::string& key, double min, double max, std::size_t offset, std::size_t count,
    bool withscores, const reply_callback_t& reply_callback) {
    return zrangebyscore(key, std::to_string(min), std::to_string(max), true, offset, count, withscores,
      reply_callback);
  }
  
  client&
  client::zrangebyscore(const std::string& key, const std::string& min, const std::string& max, std::size_t offset,
    std::size_t count, const reply_callback_t& reply_callback) {
    return zrangebyscore(key, min, max, true, offset, count, false, reply_callback);
  }
  
  client&
  client::zrangebyscore(const std::string& key, const std::string& min, const std::string& max, std::size_t offset,
    std::size_t count, bool withscores, const reply_callback_t& reply_callback) {
    return zrangebyscore(key, min, max, true, offset, count, withscores, reply_callback);
  }
  
  client&
  client::zrangebyscore(const std::string& key, const std::string& min, const std::string& max, bool limit,
    std::size_t offset, std::size_t count, bool withscores,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"ZRANGEBYSCORE", key, min, max};
  
    /**
   * withscores (optional)
   */
    if (withscores) {
      cmd.emplace_back("WITHSCORES");
    }
  
    /**
   * limit (optional)
   */
    if (limit) {
      cmd.emplace_back("LIMIT");
      cmd.push_back(std::to_string(offset));
      cmd.push_back(std::to_string(count));
    }
  
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::zrank(const std::string& key, const std::string& member, const reply_callback_t& reply_callback) {
    send({"ZRANK", key, member}, reply_callback);
    return *this;
  }
  
  client&
  client::zrem(const std::string& key, const std::vector<std::string>& members,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"ZREM", key};
    cmd.insert(cmd.end(), members.begin(), members.end());
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::zremrangebylex(const std::string& key, int min, int max, const reply_callback_t& reply_callback) {
    send({"ZREMRANGEBYLEX", key, std::to_string(min), std::to_string(max)}, reply_callback);
    return *this;
  }
  
  client&
  client::zremrangebylex(const std::string& key, double min, double max, const reply_callback_t& reply_callback) {
    send({"ZREMRANGEBYLEX", key, std::to_string(min), std::to_string(max)}, reply_callback);
    return *this;
  }
  
  client&
  client::zremrangebylex(const std::string& key, const std::string& min, const std::string& max,
    const reply_callback_t& reply_callback) {
    send({"ZREMRANGEBYLEX", key, min, max}, reply_callback);
    return *this;
  }
  
  client&
  client::zremrangebyrank(const std::string& key, int start, int stop, const reply_callback_t& reply_callback) {
    send({"ZREMRANGEBYRANK", key, std::to_string(start), std::to_string(stop)}, reply_callback);
    return *this;
  }
  
  client&
  client::zremrangebyrank(const std::string& key, double start, double stop, const reply_callback_t& reply_callback) {
    send({"ZREMRANGEBYRANK", key, std::to_string(start), std::to_string(stop)}, reply_callback);
    return *this;
  }
  
  client&
  client::zremrangebyrank(const std::string& key, const std::string& start, const std::string& stop,
    const reply_callback_t& reply_callback) {
    send({"ZREMRANGEBYRANK", key, start, stop}, reply_callback);
    return *this;
  }
  
  client&
  client::zremrangebyscore(const std::string& key, int min, int max, const reply_callback_t& reply_callback) {
    send({"ZREMRANGEBYSCORE", key, std::to_string(min), std::to_string(max)}, reply_callback);
    return *this;
  }
  
  client&
  client::zremrangebyscore(const std::string& key, double min, double max, const reply_callback_t& reply_callback) {
    send({"ZREMRANGEBYSCORE", key, std::to_string(min), std::to_string(max)}, reply_callback);
    return *this;
  }
  
  client&
  client::zremrangebyscore(const std::string& key, const std::string& min, const std::string& max,
    const reply_callback_t& reply_callback) {
    send({"ZREMRANGEBYSCORE", key, min, max}, reply_callback);
    return *this;
  }
  
  client&
  client::zrevrange(const std::string& key, int start, int stop, const reply_callback_t& reply_callback) {
    send({"ZREVRANGE", key, std::to_string(start), std::to_string(stop)}, reply_callback);
    return *this;
  }
  
  client&
  client::zrevrange(const std::string& key, int start, int stop, bool withscores,
    const reply_callback_t& reply_callback) {
    if (withscores)
      send({"ZREVRANGE", key, std::to_string(start), std::to_string(stop), "WITHSCORES"}, reply_callback);
    else
      send({"ZREVRANGE", key, std::to_string(start), std::to_string(stop)}, reply_callback);
    return *this;
  }
  
  client&
  client::zrevrange(const std::string& key, double start, double stop, const reply_callback_t& reply_callback) {
    send({"ZREVRANGE", key, std::to_string(start), std::to_string(stop)}, reply_callback);
    return *this;
  }
  
  client&
  client::zrevrange(const std::string& key, double start, double stop, bool withscores,
    const reply_callback_t& reply_callback) {
    if (withscores)
      send({"ZREVRANGE", key, std::to_string(start), std::to_string(stop), "WITHSCORES"}, reply_callback);
    else
      send({"ZREVRANGE", key, std::to_string(start), std::to_string(stop)}, reply_callback);
    return *this;
  }
  
  client&
  client::zrevrange(const std::string& key, const std::string& start, const std::string& stop,
    const reply_callback_t& reply_callback) {
    send({"ZREVRANGE", key, start, stop}, reply_callback);
    return *this;
  }
  
  client&
  client::zrevrange(const std::string& key, const std::string& start, const std::string& stop, bool withscores,
    const reply_callback_t& reply_callback) {
    if (withscores)
      send({"ZREVRANGE", key, start, stop, "WITHSCORES"}, reply_callback);
    else
      send({"ZREVRANGE", key, start, stop}, reply_callback);
    return *this;
  }
  
  client&
  client::zrevrangebylex(const std::string& key, int max, int min, const reply_callback_t& reply_callback) {
    return zrevrangebylex(key, std::to_string(max), std::to_string(min), false, 0, 0, false, reply_callback);
  }
  
  client&
  client::zrevrangebylex(const std::string& key, int max, int min, bool withscores,
    const reply_callback_t& reply_callback) {
    return zrevrangebylex(key, std::to_string(max), std::to_string(min), false, 0, 0, withscores, reply_callback);
  }
  
  client&
  client::zrevrangebylex(const std::string& key, double max, double min, const reply_callback_t& reply_callback) {
    return zrevrangebylex(key, std::to_string(max), std::to_string(min), false, 0, 0, false, reply_callback);
  }
  
  client&
  client::zrevrangebylex(const std::string& key, double max, double min, bool withscores,
    const reply_callback_t& reply_callback) {
    return zrevrangebylex(key, std::to_string(max), std::to_string(min), false, 0, 0, withscores, reply_callback);
  }
  
  client&
  client::zrevrangebylex(const std::string& key, const std::string& max, const std::string& min,
    const reply_callback_t& reply_callback) {
    return zrevrangebylex(key, max, min, false, 0, 0, false, reply_callback);
  }
  
  client&
  client::zrevrangebylex(const std::string& key, const std::string& max, const std::string& min, bool withscores,
    const reply_callback_t& reply_callback) {
    return zrevrangebylex(key, max, min, false, 0, 0, withscores, reply_callback);
  }
  
  client&
  client::zrevrangebylex(const std::string& key, int max, int min, std::size_t offset, std::size_t count,
    const reply_callback_t& reply_callback) {
    return zrevrangebylex(key, std::to_string(max), std::to_string(min), true, offset, count, false, reply_callback);
  }
  
  client&
  client::zrevrangebylex(const std::string& key, int max, int min, std::size_t offset, std::size_t count,
    bool withscores, const reply_callback_t& reply_callback) {
    return zrevrangebylex(key, std::to_string(max), std::to_string(min), true, offset, count, withscores,
      reply_callback);
  }
  
  client&
  client::zrevrangebylex(const std::string& key, double max, double min, std::size_t offset, std::size_t count,
    const reply_callback_t& reply_callback) {
    return zrevrangebylex(key, std::to_string(max), std::to_string(min), true, offset, count, false, reply_callback);
  }
  
  client&
  client::zrevrangebylex(const std::string& key, double max, double min, std::size_t offset, std::size_t count,
    bool withscores, const reply_callback_t& reply_callback) {
    return zrevrangebylex(key, std::to_string(max), std::to_string(min), true, offset, count, withscores,
      reply_callback);
  }
  
  client&
  client::zrevrangebylex(const std::string& key, const std::string& max, const std::string& min, std::size_t offset,
    std::size_t count, const reply_callback_t& reply_callback) {
    return zrevrangebylex(key, max, min, true, offset, count, false, reply_callback);
  }
  
  client&
  client::zrevrangebylex(const std::string& key, const std::string& max, const std::string& min, std::size_t offset,
    std::size_t count, bool withscores, const reply_callback_t& reply_callback) {
    return zrevrangebylex(key, max, min, true, offset, count, withscores, reply_callback);
  }
  
  client&
  client::zrevrangebylex(const std::string& key, const std::string& max, const std::string& min, bool limit,
    std::size_t offset, std::size_t count, bool withscores,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"ZREVRANGEBYLEX", key, max, min};
  
    /**
   * withscores (optional)
   */
    if (withscores) {
      cmd.emplace_back("WITHSCORES");
    }
  
    /**
   * limit (optional)
   */
    if (limit) {
      cmd.emplace_back("LIMIT");
      cmd.push_back(std::to_string(offset));
      cmd.push_back(std::to_string(count));
    }
  
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::zrevrangebyscore(const std::string& key, int max, int min, const reply_callback_t& reply_callback) {
    return zrevrangebyscore(key, std::to_string(max), std::to_string(min), false, 0, 0, false, reply_callback);
  }
  
  client&
  client::zrevrangebyscore(const std::string& key, int max, int min, bool withscores,
    const reply_callback_t& reply_callback) {
    return zrevrangebyscore(key, std::to_string(max), std::to_string(min), false, 0, 0, withscores, reply_callback);
  }
  
  client&
  client::zrevrangebyscore(const std::string& key, double max, double min, const reply_callback_t& reply_callback) {
    return zrevrangebyscore(key, std::to_string(max), std::to_string(min), false, 0, 0, false, reply_callback);
  }
  
  client&
  client::zrevrangebyscore(const std::string& key, double max, double min, bool withscores,
    const reply_callback_t& reply_callback) {
    return zrevrangebyscore(key, std::to_string(max), std::to_string(min), false, 0, 0, withscores, reply_callback);
  }
  
  client&
  client::zrevrangebyscore(const std::string& key, const std::string& max, const std::string& min,
    const reply_callback_t& reply_callback) {
    return zrevrangebyscore(key, max, min, false, 0, 0, false, reply_callback);
  }
  
  client&
  client::zrevrangebyscore(const std::string& key, const std::string& max, const std::string& min, bool withscores,
    const reply_callback_t& reply_callback) {
    return zrevrangebyscore(key, max, min, false, 0, 0, withscores, reply_callback);
  }
  
  client&
  client::zrevrangebyscore(const std::string& key, int max, int min, std::size_t offset, std::size_t count,
    const reply_callback_t& reply_callback) {
    return zrevrangebyscore(key, std::to_string(max), std::to_string(min), true, offset, count, false, reply_callback);
  }
  
  client&
  client::zrevrangebyscore(const std::string& key, int max, int min, std::size_t offset, std::size_t count,
    bool withscores, const reply_callback_t& reply_callback) {
    return zrevrangebyscore(key, std::to_string(max), std::to_string(min), true, offset, count, withscores,
      reply_callback);
  }
  
  client&
  client::zrevrangebyscore(const std::string& key, double max, double min, std::size_t offset, std::size_t count,
    const reply_callback_t& reply_callback) {
    return zrevrangebyscore(key, std::to_string(max), std::to_string(min), true, offset, count, false, reply_callback);
  }
  
  client&
  client::zrevrangebyscore(const std::string& key, double max, double min, std::size_t offset, std::size_t count,
    bool withscores, const reply_callback_t& reply_callback) {
    return zrevrangebyscore(key, std::to_string(max), std::to_string(min), true, offset, count, withscores,
      reply_callback);
  }
  
  client&
  client::zrevrangebyscore(const std::string& key, const std::string& max, const std::string& min, std::size_t offset,
    std::size_t count, const reply_callback_t& reply_callback) {
    return zrevrangebyscore(key, max, min, true, offset, count, false, reply_callback);
  }
  
  client&
  client::zrevrangebyscore(const std::string& key, const std::string& max, const std::string& min, std::size_t offset,
    std::size_t count, bool withscores, const reply_callback_t& reply_callback) {
    return zrevrangebyscore(key, max, min, true, offset, count, withscores, reply_callback);
  }
  
  client&
  client::zrevrangebyscore(const std::string& key, const std::string& max, const std::string& min, bool limit,
    std::size_t offset, std::size_t count, bool withscores,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"ZREVRANGEBYSCORE", key, max, min};
  
    /**
   * withscores (optional)
   */
    if (withscores) {
      cmd.emplace_back("WITHSCORES");
    }
  
    /**
   * limit (optional)
   */
    if (limit) {
      cmd.emplace_back("LIMIT");
      cmd.push_back(std::to_string(offset));
      cmd.push_back(std::to_string(count));
    }
  
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::zrevrank(const std::string& key, const std::string& member, const reply_callback_t& reply_callback) {
    send({"ZREVRANK", key, member}, reply_callback);
    return *this;
  }
  
  client&
  client::zscan(const std::string& key, std::size_t cursor, const reply_callback_t& reply_callback) {
    return zscan(key, cursor, "", 0, reply_callback);
  }
  
  client&
  client::zscan(const std::string& key, std::size_t cursor, const std::string& pattern,
    const reply_callback_t& reply_callback) {
    return zscan(key, cursor, pattern, 0, reply_callback);
  }
  
  client&
  client::zscan(const std::string& key, std::size_t cursor, std::size_t count, const reply_callback_t& reply_callback) {
    return zscan(key, cursor, "", count, reply_callback);
  }
  
  client&
  client::zscan(const std::string& key, std::size_t cursor, const std::string& pattern, std::size_t count,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"ZSCAN", key, std::to_string(cursor)};
  
    if (!pattern.empty()) {
      cmd.emplace_back("MATCH");
      cmd.push_back(pattern);
    }
  
    if (count > 0) {
      cmd.emplace_back("COUNT");
      cmd.push_back(std::to_string(count));
    }
  
    send(cmd, reply_callback);
    return *this;
  }
  
  client&
  client::zscore(const std::string& key, const std::string& member, const reply_callback_t& reply_callback) {
    send({"ZSCORE", key, member}, reply_callback);
    return *this;
  }
  
  client&
  client::zunionstore(const std::string& destination, std::size_t numkeys, const std::vector<std::string>& keys,
    const std::vector<std::size_t> weights, aggregate_method method,
    const reply_callback_t& reply_callback) {
    std::vector<std::string> cmd = {"ZUNIONSTORE", destination, std::to_string(numkeys)};
  
    /**
   * keys
   */
    for (const auto& key : keys) {
      cmd.push_back(key);
    }
  
    /**
   * weights (optional)
   */
    if (!weights.empty()) {
      cmd.emplace_back("WEIGHTS");
  
      for (auto weight : weights) {
        cmd.push_back(std::to_string(weight));
      }
    }
  
    /**
   * aggregate method
   */
    if (method != aggregate_method::server_default) {
      cmd.emplace_back("AGGREGATE");
      cmd.push_back(aggregate_method_to_string(method));
    }
  
    send(cmd, reply_callback);
    return *this;
  }
  
  /**
   * Redis Commands
   * std::future-based
   */
  
  std::future<reply>
  client::exec_cmd(const std::function<client&(const reply_callback_t&)>& f) {
    auto prms = std::make_shared<std::promise<reply>>();
  
    f([prms](reply& reply) {
      prms->set_value(reply);
    });
  
    return prms->get_future();
  }
  
  std::future<reply>
  client::send(const std::vector<std::string>& redis_cmd) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return send(redis_cmd, cb); });
  }
  
  std::future<reply>
  client::append(const std::string& key, const std::string& value) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return append(key, value, cb); });
  }
  
  std::future<reply>
  client::auth(const std::string& password) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return auth(password, cb); });
  }
  
  std::future<reply>
  client::bgrewriteaof() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return bgrewriteaof(cb); });
  }
  
  std::future<reply>
  client::bgsave() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return bgsave(cb); });
  }
  
  std::future<reply>
  client::bitcount(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return bitcount(key, cb); });
  }
  
  std::future<reply>
  client::bitcount(const std::string& key, int start, int end) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return bitcount(key, start, end, cb); });
  }
  
  std::future<reply>
  client::bitfield(const std::string& key, const std::vector<bitfield_operation>& operations) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return bitfield(key, operations, cb); });
  }
  
  std::future<reply>
  client::bitop(const std::string& operation, const std::string& destkey, const std::vector<std::string>& keys) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return bitop(operation, destkey, keys, cb); });
  }
  
  std::future<reply>
  client::bitpos(const std::string& key, int bit) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return bitpos(key, bit, cb); });
  }
  
  std::future<reply>
  client::bitpos(const std::string& key, int bit, int start) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return bitpos(key, bit, start, cb); });
  }
  
  std::future<reply>
  client::bitpos(const std::string& key, int bit, int start, int end) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return bitpos(key, bit, start, end, cb); });
  }
  
  std::future<reply>
  client::blpop(const std::vector<std::string>& keys, int timeout) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return blpop(keys, timeout, cb); });
  }
  
  std::future<reply>
  client::brpop(const std::vector<std::string>& keys, int timeout) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return brpop(keys, timeout, cb); });
  }
  
  std::future<reply>
  client::brpoplpush(const std::string& src, const std::string& dst, int timeout) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return brpoplpush(src, dst, timeout, cb); });
  }
  
  std::future<reply>
  client::bzpopmin(const std::vector<std::string>& keys, int timeout) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return bzpopmin(keys, timeout, cb); });
  }
  
  std::future<reply>
  client::bzpopmax(const std::vector<std::string>& keys, int timeout) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return bzpopmax(keys, timeout, cb); });
  }
  
  std::future<reply>
  client::client_id() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return client_id(cb); });
  }
  
  std::future<reply>
  client::client_list() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return client_list(cb); });
  }
  
  std::future<reply>
  client::client_getname() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return client_getname(cb); });
  }
  
  std::future<reply>
  client::client_pause(int timeout) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return client_pause(timeout, cb); });
  }
  
  std::future<reply>
  client::client_reply(const std::string& mode) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return client_reply(mode, cb); });
  }
  
  std::future<reply>
  client::client_setname(const std::string& name) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return client_setname(name, cb); });
  }
  
  std::future<reply>
  client::client_unblock(int id, bool witherror) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return client_unblock(id, witherror, cb); });
  }
  
  std::future<reply>
  client::cluster_addslots(const std::vector<std::string>& p_slots) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return cluster_addslots(p_slots, cb); });
  }
  
  std::future<reply>
  client::cluster_count_failure_reports(const std::string& node_id) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return cluster_count_failure_reports(node_id, cb); });
  }
  
  std::future<reply>
  client::cluster_countkeysinslot(const std::string& slot) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return cluster_countkeysinslot(slot, cb); });
  }
  
  std::future<reply>
  client::cluster_delslots(const std::vector<std::string>& p_slots) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return cluster_delslots(p_slots, cb); });
  }
  
  std::future<reply>
  client::cluster_failover() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return cluster_failover(cb); });
  }
  
  std::future<reply>
  client::cluster_failover(const std::string& mode) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return cluster_failover(mode, cb); });
  }
  
  std::future<reply>
  client::cluster_forget(const std::string& node_id) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return cluster_forget(node_id, cb); });
  }
  
  std::future<reply>
  client::cluster_getkeysinslot(const std::string& slot, int count) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return cluster_getkeysinslot(slot, count, cb); });
  }
  
  std::future<reply>
  client::cluster_info() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return cluster_info(cb); });
  }
  
  std::future<reply>
  client::cluster_keyslot(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return cluster_keyslot(key, cb); });
  }
  
  std::future<reply>
  client::cluster_meet(const std::string& ip, int port) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return cluster_meet(ip, port, cb); });
  }
  
  std::future<reply>
  client::cluster_nodes() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return cluster_nodes(cb); });
  }
  
  std::future<reply>
  client::cluster_replicate(const std::string& node_id) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return cluster_replicate(node_id, cb); });
  }
  
  std::future<reply>
  client::cluster_reset(const std::string& mode) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return cluster_reset(mode, cb); });
  }
  
  std::future<reply>
  client::cluster_saveconfig() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return cluster_saveconfig(cb); });
  }
  
  std::future<reply>
  client::cluster_set_config_epoch(const std::string& epoch) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return cluster_set_config_epoch(epoch, cb); });
  }
  
  std::future<reply>
  client::cluster_setslot(const std::string& slot, const std::string& mode) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return cluster_setslot(slot, mode, cb); });
  }
  
  std::future<reply>
  client::cluster_setslot(const std::string& slot, const std::string& mode, const std::string& node_id) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return cluster_setslot(slot, mode, node_id, cb); });
  }
  
  std::future<reply>
  client::cluster_slaves(const std::string& node_id) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return cluster_slaves(node_id, cb); });
  }
  
  std::future<reply>
  client::cluster_slots() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return cluster_slots(cb); });
  }
  
  std::future<reply>
  client::command() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return command(cb); });
  }
  
  std::future<reply>
  client::command_count() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return command_count(cb); });
  }
  
  std::future<reply>
  client::command_getkeys() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return command_getkeys(cb); });
  }
  
  std::future<reply>
  client::command_info(const std::vector<std::string>& command_name) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return command_info(command_name, cb); });
  }
  
  std::future<reply>
  client::config_get(const std::string& param) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return config_get(param, cb); });
  }
  
  std::future<reply>
  client::config_rewrite() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return config_rewrite(cb); });
  }
  
  std::future<reply>
  client::config_set(const std::string& param, const std::string& val) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return config_set(param, val, cb); });
  }
  
  std::future<reply>
  client::config_resetstat() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return config_resetstat(cb); });
  }
  
  std::future<reply>
  client::dbsize() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return dbsize(cb); });
  }
  
  std::future<reply>
  client::debug_object(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return debug_object(key, cb); });
  }
  
  std::future<reply>
  client::debug_segfault() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return debug_segfault(cb); });
  }
  
  std::future<reply>
  client::decr(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return decr(key, cb); });
  }
  
  std::future<reply>
  client::decrby(const std::string& key, int val) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return decrby(key, val, cb); });
  }
  
  std::future<reply>
  client::del(const std::vector<std::string>& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return del(key, cb); });
  }
  
  std::future<reply>
  client::discard() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return discard(cb); });
  }
  
  std::future<reply>
  client::dump(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return dump(key, cb); });
  }
  
  std::future<reply>
  client::echo(const std::string& msg) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return echo(msg, cb); });
  }
  
  std::future<reply>
  client::eval(const std::string& script, int numkeys, const std::vector<std::string>& keys,
    const std::vector<std::string>& args) {
    (void) numkeys;
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return eval(script, keys, args, cb); });
  }
  
  std::future<reply>
  client::eval(const std::string& script, const std::vector<std::string>& keys,
    const std::vector<std::string>& args) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return eval(script, keys, args, cb); });
  }
  
  std::future<reply>
  client::evalsha(const std::string& sha1, const std::vector<std::string>& keys,
    const std::vector<std::string>& args) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return evalsha(sha1, keys, args, cb); });
  }
  
  std::future<reply>
  client::evalsha(const std::string& sha1, int numkeys, const std::vector<std::string>& keys,
    const std::vector<std::string>& args) {
    (void) numkeys;
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return evalsha(sha1, keys, args, cb); });
  }
  
  std::future<reply>
  client::exec() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return exec(cb); });
  }
  
  std::future<reply>
  client::exists(const std::vector<std::string>& keys) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return exists(keys, cb); });
  }
  
  std::future<reply>
  client::expire(const std::string& key, int seconds) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return expire(key, seconds, cb); });
  }
  
  std::future<reply>
  client::expireat(const std::string& key, int timestamp) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return expireat(key, timestamp, cb); });
  }
  
  std::future<reply>
  client::flushall() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return flushall(cb); });
  }
  
  std::future<reply>
  client::flushdb() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return flushdb(cb); });
  }
  
  std::future<reply>
  client::geoadd(const std::string& key,
    const std::vector<std::tuple<std::string, std::string, std::string>>& long_lat_memb) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return geoadd(key, long_lat_memb, cb); });
  }
  
  std::future<reply>
  client::geohash(const std::string& key, const std::vector<std::string>& members) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return geohash(key, members, cb); });
  }
  
  std::future<reply>
  client::geopos(const std::string& key, const std::vector<std::string>& members) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return geopos(key, members, cb); });
  }
  
  std::future<reply>
  client::geodist(const std::string& key, const std::string& member_1, const std::string& member_2,
    const std::string& unit) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return geodist(key, member_1, member_2, unit, cb); });
  }
  
  std::future<reply>
  client::georadius(const std::string& key, double longitude, double latitude, double radius, geo_unit unit,
    bool with_coord, bool with_dist, bool with_hash, bool asc_order, std::size_t count,
    const std::string& store_key, const std::string& storedist_key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return georadius(key, longitude, latitude, radius, unit, with_coord, with_dist, with_hash, asc_order, count,
        store_key, storedist_key, cb);
    });
  }
  
  std::future<reply>
  client::georadiusbymember(const std::string& key, const std::string& member, double radius, geo_unit unit,
    bool with_coord, bool with_dist, bool with_hash, bool asc_order, std::size_t count,
    const std::string& store_key, const std::string& storedist_key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return georadiusbymember(key, member, radius, unit, with_coord, with_dist, with_hash, asc_order, count,
        store_key, storedist_key, cb);
    });
  }
  
  std::future<reply>
  client::get(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return get(key, cb); });
  }
  
  std::future<reply>
  client::getbit(const std::string& key, int offset) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return getbit(key, offset, cb); });
  }
  
  std::future<reply>
  client::getrange(const std::string& key, int start, int end) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return getrange(key, start, end, cb); });
  }
  
  std::future<reply>
  client::getset(const std::string& key, const std::string& val) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return getset(key, val, cb); });
  }
  
  std::future<reply>
  client::hdel(const std::string& key, const std::vector<std::string>& fields) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return hdel(key, fields, cb); });
  }
  
  std::future<reply>
  client::hexists(const std::string& key, const std::string& field) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return hexists(key, field, cb); });
  }
  
  std::future<reply>
  client::hget(const std::string& key, const std::string& field) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return hget(key, field, cb); });
  }
  
  std::future<reply>
  client::hgetall(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return hgetall(key, cb); });
  }
  
  std::future<reply>
  client::hincrby(const std::string& key, const std::string& field, int incr) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return hincrby(key, field, incr, cb); });
  }
  
  std::future<reply>
  client::hincrbyfloat(const std::string& key, const std::string& field, float incr) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return hincrbyfloat(key, field, incr, cb); });
  }
  
  std::future<reply>
  client::hkeys(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return hkeys(key, cb); });
  }
  
  std::future<reply>
  client::hlen(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return hlen(key, cb); });
  }
  
  std::future<reply>
  client::hmget(const std::string& key, const std::vector<std::string>& fields) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return hmget(key, fields, cb); });
  }
  
  std::future<reply>
  client::hmset(const std::string& key, const std::vector<std::pair<std::string, std::string>>& field_val) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return hmset(key, field_val, cb); });
  }
  
  std::future<reply>
  client::hscan(const std::string& key, std::size_t cursor) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return hscan(key, cursor, cb); });
  }
  
  std::future<reply>
  client::hscan(const std::string& key, std::size_t cursor, const std::string& pattern) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return hscan(key, cursor, pattern, cb); });
  }
  
  std::future<reply>
  client::hscan(const std::string& key, std::size_t cursor, std::size_t count) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return hscan(key, cursor, count, cb); });
  }
  
  std::future<reply>
  client::hscan(const std::string& key, std::size_t cursor, const std::string& pattern, std::size_t count) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return hscan(key, cursor, pattern, count, cb); });
  }
  
  std::future<reply>
  client::hset(const std::string& key, const std::string& field, const std::string& value) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return hset(key, field, value, cb); });
  }
  
  std::future<reply>
  client::hsetnx(const std::string& key, const std::string& field, const std::string& value) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return hsetnx(key, field, value, cb); });
  }
  
  std::future<reply>
  client::hstrlen(const std::string& key, const std::string& field) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return hstrlen(key, field, cb); });
  }
  
  std::future<reply>
  client::hvals(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return hvals(key, cb); });
  }
  
  std::future<reply>
  client::incr(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return incr(key, cb); });
  }
  
  std::future<reply>
  client::incrby(const std::string& key, int incr) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return incrby(key, incr, cb); });
  }
  
  std::future<reply>
  client::incrbyfloat(const std::string& key, float incr) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return incrbyfloat(key, incr, cb); });
  }
  
  std::future<reply>
  client::info(const std::string& section) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return info(section, cb); });
  }
  
  std::future<reply>
  client::keys(const std::string& pattern) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return keys(pattern, cb); });
  }
  
  std::future<reply>
  client::lastsave() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return lastsave(cb); });
  }
  
  std::future<reply>
  client::lindex(const std::string& key, int index) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return lindex(key, index, cb); });
  }
  
  std::future<reply>
  client::linsert(const std::string& key, const std::string& before_after, const std::string& pivot,
    const std::string& value) {
    return exec_cmd(
      [=](const reply_callback_t& cb) -> client& { return linsert(key, before_after, pivot, value, cb); });
  }
  
  std::future<reply>
  client::llen(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return llen(key, cb); });
  }
  
  std::future<reply>
  client::lpop(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return lpop(key, cb); });
  }
  
  std::future<reply>
  client::lpush(const std::string& key, const std::vector<std::string>& values) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return lpush(key, values, cb); });
  }
  
  std::future<reply>
  client::lpushx(const std::string& key, const std::string& value) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return lpushx(key, value, cb); });
  }
  
  std::future<reply>
  client::lrange(const std::string& key, int start, int stop) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return lrange(key, start, stop, cb); });
  }
  
  std::future<reply>
  client::lrem(const std::string& key, int count, const std::string& value) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return lrem(key, count, value, cb); });
  }
  
  std::future<reply>
  client::lset(const std::string& key, int index, const std::string& value) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return lset(key, index, value, cb); });
  }
  
  std::future<reply>
  client::ltrim(const std::string& key, int start, int stop) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return ltrim(key, start, stop, cb); });
  }
  
  std::future<reply>
  client::mget(const std::vector<std::string>& keys) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return mget(keys, cb); });
  }
  
  std::future<reply>
  client::migrate(const std::string& host, int port, const std::string& key, const std::string& dest_db, int timeout,
    bool copy, bool replace, const std::vector<std::string>& keys) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return migrate(host, port, key, dest_db, timeout, copy, replace, keys, cb);
    });
  }
  
  std::future<reply>
  client::monitor() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return monitor(cb); });
  }
  
  std::future<reply>
  client::move(const std::string& key, const std::string& db) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return move(key, db, cb); });
  }
  
  std::future<reply>
  client::mset(const std::vector<std::pair<std::string, std::string>>& key_vals) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return mset(key_vals, cb); });
  }
  
  std::future<reply>
  client::msetnx(const std::vector<std::pair<std::string, std::string>>& key_vals) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return msetnx(key_vals, cb); });
  }
  
  std::future<reply>
  client::multi() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return multi(cb); });
  }
  
  std::future<reply>
  client::object(const std::string& subcommand, const std::vector<std::string>& args) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return object(subcommand, args, cb); });
  }
  
  std::future<reply>
  client::persist(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return persist(key, cb); });
  }
  
  std::future<reply>
  client::pexpire(const std::string& key, int ms) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return pexpire(key, ms, cb); });
  }
  
  std::future<reply>
  client::pexpireat(const std::string& key, int ms_timestamp) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return pexpireat(key, ms_timestamp, cb); });
  }
  
  std::future<reply>
  client::pfadd(const std::string& key, const std::vector<std::string>& elements) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return pfadd(key, elements, cb); });
  }
  
  std::future<reply>
  client::pfcount(const std::vector<std::string>& keys) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return pfcount(keys, cb); });
  }
  
  std::future<reply>
  client::pfmerge(const std::string& destkey, const std::vector<std::string>& sourcekeys) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return pfmerge(destkey, sourcekeys, cb); });
  }
  
  std::future<reply>
  client::ping() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return ping(cb); });
  }
  
  std::future<reply>
  client::ping(const std::string& message) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return ping(message, cb); });
  }
  
  std::future<reply>
  client::psetex(const std::string& key, int64_t ms, const std::string& val) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return psetex(key, ms, val, cb); });
  }
  
  std::future<reply>
  client::publish(const std::string& channel, const std::string& message) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return publish(channel, message, cb); });
  }
  
  std::future<reply>
  client::pubsub(const std::string& subcommand, const std::vector<std::string>& args) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return pubsub(subcommand, args, cb); });
  }
  
  std::future<reply>
  client::pttl(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return pttl(key, cb); });
  }
  
  std::future<reply>
  client::quit() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return quit(cb); });
  }
  
  std::future<reply>
  client::randomkey() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return randomkey(cb); });
  }
  
  std::future<reply>
  client::readonly() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return readonly(cb); });
  }
  
  std::future<reply>
  client::readwrite() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return readwrite(cb); });
  }
  
  std::future<reply>
  client::rename(const std::string& key, const std::string& newkey) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return rename(key, newkey, cb); });
  }
  
  std::future<reply>
  client::renamenx(const std::string& key, const std::string& newkey) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return renamenx(key, newkey, cb); });
  }
  
  std::future<reply>
  client::restore(const std::string& key, int ttl, const std::string& serialized_value) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return restore(key, ttl, serialized_value, cb); });
  }
  
  std::future<reply>
  client::restore(const std::string& key, int ttl, const std::string& serialized_value, const std::string& replace) {
    return exec_cmd(
      [=](const reply_callback_t& cb) -> client& { return restore(key, ttl, serialized_value, replace, cb); });
  }
  
  std::future<reply>
  client::role() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return role(cb); });
  }
  
  std::future<reply>
  client::rpop(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return rpop(key, cb); });
  }
  
  std::future<reply>
  client::rpoplpush(const std::string& src, const std::string& dst) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return rpoplpush(src, dst, cb); });
  }
  
  std::future<reply>
  client::rpush(const std::string& key, const std::vector<std::string>& values) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return rpush(key, values, cb); });
  }
  
  std::future<reply>
  client::rpushx(const std::string& key, const std::string& value) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return rpushx(key, value, cb); });
  }
  
  std::future<reply>
  client::sadd(const std::string& key, const std::vector<std::string>& members) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return sadd(key, members, cb); });
  }
  
  std::future<reply>
  client::scan(std::size_t cursor) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return scan(cursor, cb); });
  }
  
  std::future<reply>
  client::scan(std::size_t cursor, const std::string& pattern) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return scan(cursor, pattern, cb); });
  }
  
  std::future<reply>
  client::scan(std::size_t cursor, std::size_t count) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return scan(cursor, count, cb); });
  }
  
  std::future<reply>
  client::scan(std::size_t cursor, const std::string& pattern, std::size_t count) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return scan(cursor, pattern, count, cb); });
  }
  
  std::future<reply>
  client::save() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return save(cb); });
  }
  
  std::future<reply>
  client::scard(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return scard(key, cb); });
  }
  
  std::future<reply>
  client::script_debug(const std::string& mode) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return script_debug(mode, cb); });
  }
  
  std::future<reply>
  client::script_exists(const std::vector<std::string>& scripts) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return script_exists(scripts, cb); });
  }
  
  std::future<reply>
  client::script_flush() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return script_flush(cb); });
  }
  
  std::future<reply>
  client::script_kill() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return script_kill(cb); });
  }
  
  std::future<reply>
  client::script_load(const std::string& script) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return script_load(script, cb); });
  }
  
  std::future<reply>
  client::sdiff(const std::vector<std::string>& keys) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return sdiff(keys, cb); });
  }
  
  std::future<reply>
  client::sdiffstore(const std::string& dst, const std::vector<std::string>& keys) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return sdiffstore(dst, keys, cb); });
  }
  
  std::future<reply>
  client::select(int index) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return select(index, cb); });
  }
  
  std::future<reply>
  client::set(const std::string& key, const std::string& value) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return set(key, value, cb); });
  }
  
  std::future<reply>
  client::set_advanced(const std::string& key, const std::string& value, bool ex, int ex_sec, bool px, int px_milli,
    bool nx, bool xx) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return set_advanced(key, value, ex, ex_sec, px, px_milli, nx, xx, cb);
    });
  }
  
  std::future<reply>
  client::setbit_(const std::string& key, int offset, const std::string& value) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return setbit_(key, offset, value, cb); });
  }
  
  std::future<reply>
  client::setex(const std::string& key, int64_t seconds, const std::string& value) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return setex(key, seconds, value, cb); });
  }
  
  std::future<reply>
  client::setnx(const std::string& key, const std::string& value) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return setnx(key, value, cb); });
  }
  
  std::future<reply>
  client::setrange(const std::string& key, int offset, const std::string& value) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return setrange(key, offset, value, cb); });
  }
  
  std::future<reply>
  client::shutdown() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return shutdown(cb); });
  }
  
  std::future<reply>
  client::shutdown(const std::string& save) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return shutdown(save, cb); });
  }
  
  std::future<reply>
  client::sinter(const std::vector<std::string>& keys) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return sinter(keys, cb); });
  }
  
  std::future<reply>
  client::sinterstore(const std::string& dst, const std::vector<std::string>& keys) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return sinterstore(dst, keys, cb); });
  }
  
  std::future<reply>
  client::sismember(const std::string& key, const std::string& member) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return sismember(key, member, cb); });
  }
  
  std::future<reply>
  client::slaveof(const std::string& host, int port) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return slaveof(host, port, cb); });
  }
  
  std::future<reply>
  client::slowlog(const std::string& subcommand) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return slowlog(subcommand, cb); });
  }
  
  std::future<reply>
  client::slowlog(const std::string& subcommand, const std::string& argument) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return slowlog(subcommand, argument, cb); });
  }
  
  std::future<reply>
  client::smembers(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return smembers(key, cb); });
  }
  
  std::future<reply>
  client::smove(const std::string& src, const std::string& dst, const std::string& member) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return smove(src, dst, member, cb); });
  }
  
  std::future<reply>
  client::sort(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return sort(key, cb); });
  }
  
  std::future<reply>
  client::sort(const std::string& key, const std::vector<std::string>& get_patterns, bool asc_order, bool alpha) {
    return exec_cmd(
      [=](const reply_callback_t& cb) -> client& { return sort(key, get_patterns, asc_order, alpha, cb); });
  }
  
  std::future<reply>
  client::sort(const std::string& key, std::size_t offset, std::size_t count,
    const std::vector<std::string>& get_patterns, bool asc_order, bool alpha) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return sort(key, offset, count, get_patterns, asc_order, alpha, cb);
    });
  }
  
  std::future<reply>
  client::sort(const std::string& key, const std::string& by_pattern, const std::vector<std::string>& get_patterns,
    bool asc_order, bool alpha) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return sort(key, by_pattern, get_patterns, asc_order, alpha, cb);
    });
  }
  
  std::future<reply>
  client::sort(const std::string& key, const std::vector<std::string>& get_patterns, bool asc_order, bool alpha,
    const std::string& store_dest) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return sort(key, get_patterns, asc_order, alpha, store_dest, cb);
    });
  }
  
  std::future<reply>
  client::sort(const std::string& key, std::size_t offset, std::size_t count,
    const std::vector<std::string>& get_patterns, bool asc_order, bool alpha,
    const std::string& store_dest) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return sort(key, offset, count, get_patterns, asc_order, alpha, store_dest, cb);
    });
  }
  
  std::future<reply>
  client::sort(const std::string& key, const std::string& by_pattern, const std::vector<std::string>& get_patterns,
    bool asc_order, bool alpha, const std::string& store_dest) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return sort(key, by_pattern, get_patterns, asc_order, alpha, store_dest, cb);
    });
  }
  
  std::future<reply>
  client::sort(const std::string& key, const std::string& by_pattern, std::size_t offset, std::size_t count,
    const std::vector<std::string>& get_patterns, bool asc_order, bool alpha) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return sort(key, by_pattern, offset, count, get_patterns, asc_order, alpha, cb);
    });
  }
  
  std::future<reply>
  client::sort(const std::string& key, const std::string& by_pattern, std::size_t offset, std::size_t count,
    const std::vector<std::string>& get_patterns, bool asc_order, bool alpha,
    const std::string& store_dest) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return sort(key, by_pattern, offset, count, get_patterns, asc_order, alpha, store_dest, cb);
    });
  }
  
  std::future<reply>
  client::spop(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return spop(key, cb); });
  }
  
  std::future<reply>
  client::spop(const std::string& key, int count) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return spop(key, count, cb); });
  }
  
  std::future<reply>
  client::srandmember(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return srandmember(key, cb); });
  }
  
  std::future<reply>
  client::srandmember(const std::string& key, int count) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return srandmember(key, count, cb); });
  }
  
  std::future<reply>
  client::srem(const std::string& key, const std::vector<std::string>& members) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return srem(key, members, cb); });
  }
  
  std::future<reply>
  client::sscan(const std::string& key, std::size_t cursor) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return sscan(key, cursor, cb); });
  }
  
  std::future<reply>
  client::sscan(const std::string& key, std::size_t cursor, const std::string& pattern) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return sscan(key, cursor, pattern, cb); });
  }
  
  std::future<reply>
  client::sscan(const std::string& key, std::size_t cursor, std::size_t count) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return sscan(key, cursor, count, cb); });
  }
  
  std::future<reply>
  client::sscan(const std::string& key, std::size_t cursor, const std::string& pattern, std::size_t count) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return sscan(key, cursor, pattern, count, cb); });
  }
  
  std::future<reply>
  client::strlen(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return strlen(key, cb); });
  }
  
  std::future<reply>
  client::sunion(const std::vector<std::string>& keys) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return sunion(keys, cb); });
  }
  
  std::future<reply>
  client::sunionstore(const std::string& dst, const std::vector<std::string>& keys) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return sunionstore(dst, keys, cb); });
  }
  
  std::future<reply>
  client::sync() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return sync(cb); });
  }
  
  std::future<reply>
  client::time() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return time(cb); });
  }
  
  std::future<reply>
  client::ttl(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return ttl(key, cb); });
  }
  
  std::future<reply>
  client::type(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return type(key, cb); });
  }
  
  std::future<reply>
  client::unwatch() {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return unwatch(cb); });
  }
  
  std::future<reply>
  client::wait(int numslaves, int timeout) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return wait(numslaves, timeout, cb); });
  }
  
  std::future<reply>
  client::watch(const std::vector<std::string>& keys) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return watch(keys, cb); });
  }
  
  std::future<reply>
  client::xack(const std::string& key, const std::string& group, const std::vector<std::string>& id_members) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return xack(key, group, id_members, cb); });
  }
  
  std::future<reply>
  client::xadd(const std::string& key, const std::string& id,
    const std::multimap<std::string, std::string>& field_members) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return xadd(key, id, field_members, cb);
    });
  }
  
  std::future<reply>
  client::xclaim(const std::string& key, const std::string& group, const std::string& consumer,
    const int& min_idle_time, const std::vector<std::string>& id_members, const xclaim_options_t& options) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return xclaim(key, group, consumer, min_idle_time, id_members, options, cb);
    });
  }
  
  std::future<reply>
  client::xdel(const std::string& key, const std::vector<std::string>& id_members) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return xdel(key, id_members, cb);
    });
  }
  
  std::future<reply>
  client::xgroup_create(const std::string& key, const std::string& group_name, const std::string& id) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return xgroup_create(key, group_name, id, cb);
    });
  }
  
  std::future<reply>
  client::xgroup_set_id(const std::string& key, const std::string& group_name, const std::string& id) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return xgroup_set_id(key, group_name, id, cb);
    });
  }
  
  std::future<reply>
  client::xgroup_destroy(const std::string& key, const std::string& group_name) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return xgroup_destroy(key, group_name, cb);
    });
  }
  
  std::future<reply>
  client::xgroup_del_consumer(const std::string& key, const std::string& group_name, const std::string& consumer_name) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return xgroup_del_consumer(key, group_name, consumer_name, cb);
    });
  }
  
  /** @htmlinclude https://redis.io/commands/xdel
  	 * @brief introspection command used in order to retrieve different information about the consumer groups
  	 * @param key
  	 * @param group_name stream consumer group name
  	 * @return
  	 */
  std::future<reply>
  client::xinfo_consumers(const std::string& key, const std::string& group_name) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return xinfo_consumers(key, group_name, cb);
    });
  }
  
  std::future<reply>
  client::xinfo_groups(const std::string& stream) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return xinfo_groups(stream, cb);
    });
  }
  
  std::future<reply>
  client::xinfo_stream(const std::string& stream) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return xinfo_stream(stream, cb);
    });
  }
  
  std::future<reply>
  client::xlen(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return xlen(key, cb);
    });
  }
  
  std::future<reply>
  client::xpending(const std::string& stream,
    const std::string& group,
    const xpending_options_t& options) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return xpending(stream, group, options, cb);
    });
  }
  
  std::future<reply>
  client::xrange(const std::string& stream, const range_options_t& range_args) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return xrange(stream, range_args, cb);
    });
  }
  
  std::future<reply>
  client::xreadgroup(const xreadgroup_options_t& a) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return xreadgroup(a, cb);
    });
  }
  
  std::future<reply>
  client::xread(const xread_options_t& a) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return xread(a, cb);
    });
  }
  
  std::future<reply>
  client::xrevrange(const std::string& key, const range_options_t& range_args) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return xrevrange(key, range_args, cb);
    });
  }
  
  std::future<reply>
  client::xtrim(const std::string& key, int max_len) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return xtrim(key, max_len, cb);
    });
  }
  std::future<reply>
  client::xtrim_approx(const std::string& key, int max_len) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return xtrim_approx(key, max_len, cb);
    });
  }
  
  std::future<reply>
  client::zadd(const std::string& key, const std::vector<std::string>& options,
    const std::multimap<std::string, std::string>& score_members) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zadd(key, options, score_members, cb); });
  }
  
  std::future<reply>
  client::zcard(const std::string& key) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zcard(key, cb); });
  }
  
  std::future<reply>
  client::zcount(const std::string& key, int min, int max) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zcount(key, min, max, cb); });
  }
  
  std::future<reply>
  client::zcount(const std::string& key, double min, double max) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zcount(key, min, max, cb); });
  }
  
  std::future<reply>
  client::zcount(const std::string& key, const std::string& min, const std::string& max) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zcount(key, min, max, cb); });
  }
  
  std::future<reply>
  client::zincrby(const std::string& key, int incr, const std::string& member) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zincrby(key, incr, member, cb); });
  }
  
  std::future<reply>
  client::zincrby(const std::string& key, double incr, const std::string& member) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zincrby(key, incr, member, cb); });
  }
  
  std::future<reply>
  client::zincrby(const std::string& key, const std::string& incr, const std::string& member) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zincrby(key, incr, member, cb); });
  }
  
  std::future<reply>
  client::zinterstore(const std::string& destination, std::size_t numkeys, const std::vector<std::string>& keys,
    const std::vector<std::size_t> weights, aggregate_method method) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return zinterstore(destination, numkeys, keys, weights, method, cb);
    });
  }
  
  std::future<reply>
  client::zlexcount(const std::string& key, int min, int max) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zlexcount(key, min, max, cb); });
  }
  
  std::future<reply>
  client::zlexcount(const std::string& key, double min, double max) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zlexcount(key, min, max, cb); });
  }
  
  std::future<reply>
  client::zlexcount(const std::string& key, const std::string& min, const std::string& max) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zlexcount(key, min, max, cb); });
  }
  
  std::future<reply>
  client::zpopmin(const std::string& key, int count) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zpopmin(key, count, cb); });
  }
  std::future<reply>
  client::zpopmax(const std::string& key, int count) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zpopmax(key, count, cb); });
  }
  
  std::future<reply>
  client::zrange(const std::string& key, int start, int stop, bool withscores) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zrange(key, start, stop, withscores, cb); });
  }
  
  std::future<reply>
  client::zrange(const std::string& key, double start, double stop, bool withscores) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zrange(key, start, stop, withscores, cb); });
  }
  
  std::future<reply>
  client::zrange(const std::string& key, const std::string& start, const std::string& stop, bool withscores) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zrange(key, start, stop, withscores, cb); });
  }
  
  std::future<reply>
  client::zrangebylex(const std::string& key, int min, int max, bool withscores) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zrangebylex(key, min, max, withscores, cb); });
  }
  
  std::future<reply>
  client::zrangebylex(const std::string& key, double min, double max, bool withscores) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zrangebylex(key, min, max, withscores, cb); });
  }
  
  std::future<reply>
  client::zrangebylex(const std::string& key, const std::string& min, const std::string& max, bool withscores) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zrangebylex(key, min, max, withscores, cb); });
  }
  
  std::future<reply>
  client::zrangebylex(const std::string& key, int min, int max, std::size_t offset, std::size_t count,
    bool withscores) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return zrangebylex(key, min, max, offset, count, withscores, cb);
    });
  }
  
  std::future<reply>
  client::zrangebylex(const std::string& key, double min, double max, std::size_t offset, std::size_t count,
    bool withscores) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return zrangebylex(key, min, max, offset, count, withscores, cb);
    });
  }
  
  std::future<reply>
  client::zrangebylex(const std::string& key, const std::string& min, const std::string& max, std::size_t offset,
    std::size_t count, bool withscores) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return zrangebylex(key, min, max, offset, count, withscores, cb);
    });
  }
  
  std::future<reply>
  client::zrangebyscore(const std::string& key, int min, int max, bool withscores) {
    return exec_cmd(
      [=](const reply_callback_t& cb) -> client& { return zrangebyscore(key, min, max, withscores, cb); });
  }
  
  std::future<reply>
  client::zrangebyscore(const std::string& key, double min, double max, bool withscores) {
    return exec_cmd(
      [=](const reply_callback_t& cb) -> client& { return zrangebyscore(key, min, max, withscores, cb); });
  }
  
  std::future<reply>
  client::zrangebyscore(const std::string& key, const std::string& min, const std::string& max, bool withscores) {
    return exec_cmd(
      [=](const reply_callback_t& cb) -> client& { return zrangebyscore(key, min, max, withscores, cb); });
  }
  
  std::future<reply>
  client::zrangebyscore(const std::string& key, int min, int max, std::size_t offset, std::size_t count,
    bool withscores) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return zrangebyscore(key, min, max, offset, count, withscores, cb);
    });
  }
  
  std::future<reply>
  client::zrangebyscore(const std::string& key, double min, double max, std::size_t offset, std::size_t count,
    bool withscores) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return zrangebyscore(key, min, max, offset, count, withscores, cb);
    });
  }
  
  std::future<reply>
  client::zrangebyscore(const std::string& key, const std::string& min, const std::string& max, std::size_t offset,
    std::size_t count, bool withscores) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return zrangebyscore(key, min, max, offset, count, withscores, cb);
    });
  }
  
  std::future<reply>
  client::zrank(const std::string& key, const std::string& member) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zrank(key, member, cb); });
  }
  
  std::future<reply>
  client::zrem(const std::string& key, const std::vector<std::string>& members) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zrem(key, members, cb); });
  }
  
  std::future<reply>
  client::zremrangebylex(const std::string& key, int min, int max) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zremrangebylex(key, min, max, cb); });
  }
  
  std::future<reply>
  client::zremrangebylex(const std::string& key, double min, double max) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zremrangebylex(key, min, max, cb); });
  }
  
  std::future<reply>
  client::zremrangebylex(const std::string& key, const std::string& min, const std::string& max) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zremrangebylex(key, min, max, cb); });
  }
  
  std::future<reply>
  client::zremrangebyrank(const std::string& key, int start, int stop) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zremrangebyrank(key, start, stop, cb); });
  }
  
  std::future<reply>
  client::zremrangebyrank(const std::string& key, double start, double stop) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zremrangebyrank(key, start, stop, cb); });
  }
  
  std::future<reply>
  client::zremrangebyrank(const std::string& key, const std::string& start, const std::string& stop) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zremrangebyrank(key, start, stop, cb); });
  }
  
  std::future<reply>
  client::zremrangebyscore(const std::string& key, int min, int max) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zremrangebyscore(key, min, max, cb); });
  }
  
  std::future<reply>
  client::zremrangebyscore(const std::string& key, double min, double max) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zremrangebyscore(key, min, max, cb); });
  }
  
  std::future<reply>
  client::zremrangebyscore(const std::string& key, const std::string& min, const std::string& max) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zremrangebyscore(key, min, max, cb); });
  }
  
  std::future<reply>
  client::zrevrange(const std::string& key, int start, int stop, bool withscores) {
    return exec_cmd(
      [=](const reply_callback_t& cb) -> client& { return zrevrange(key, start, stop, withscores, cb); });
  }
  
  std::future<reply>
  client::zrevrange(const std::string& key, double start, double stop, bool withscores) {
    return exec_cmd(
      [=](const reply_callback_t& cb) -> client& { return zrevrange(key, start, stop, withscores, cb); });
  }
  
  std::future<reply>
  client::zrevrange(const std::string& key, const std::string& start, const std::string& stop, bool withscores) {
    return exec_cmd(
      [=](const reply_callback_t& cb) -> client& { return zrevrange(key, start, stop, withscores, cb); });
  }
  
  std::future<reply>
  client::zrevrangebylex(const std::string& key, int max, int min, bool withscores) {
    return exec_cmd(
      [=](const reply_callback_t& cb) -> client& { return zrevrangebylex(key, max, min, withscores, cb); });
  }
  
  std::future<reply>
  client::zrevrangebylex(const std::string& key, double max, double min, bool withscores) {
    return exec_cmd(
      [=](const reply_callback_t& cb) -> client& { return zrevrangebylex(key, max, min, withscores, cb); });
  }
  
  std::future<reply>
  client::zrevrangebylex(const std::string& key, const std::string& max, const std::string& min, bool withscores) {
    return exec_cmd(
      [=](const reply_callback_t& cb) -> client& { return zrevrangebylex(key, max, min, withscores, cb); });
  }
  
  std::future<reply>
  client::zrevrangebylex(const std::string& key, int max, int min, std::size_t offset, std::size_t count,
    bool withscores) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return zrevrangebylex(key, max, min, offset, count, withscores, cb);
    });
  }
  
  std::future<reply>
  client::zrevrangebylex(const std::string& key, double max, double min, std::size_t offset, std::size_t count,
    bool withscores) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return zrevrangebylex(key, max, min, offset, count, withscores, cb);
    });
  }
  
  std::future<reply>
  client::zrevrangebylex(const std::string& key, const std::string& max, const std::string& min, std::size_t offset,
    std::size_t count, bool withscores) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return zrevrangebylex(key, max, min, offset, count, withscores, cb);
    });
  }
  
  std::future<reply>
  client::zrevrangebyscore(const std::string& key, int max, int min, bool withscores) {
    return exec_cmd(
      [=](const reply_callback_t& cb) -> client& { return zrevrangebyscore(key, max, min, withscores, cb); });
  }
  
  std::future<reply>
  client::zrevrangebyscore(const std::string& key, double max, double min, bool withscores) {
    return exec_cmd(
      [=](const reply_callback_t& cb) -> client& { return zrevrangebyscore(key, max, min, withscores, cb); });
  }
  
  std::future<reply>
  client::zrevrangebyscore(const std::string& key, const std::string& max, const std::string& min, bool withscores) {
    return exec_cmd(
      [=](const reply_callback_t& cb) -> client& { return zrevrangebyscore(key, max, min, withscores, cb); });
  }
  
  std::future<reply>
  client::zrevrangebyscore(const std::string& key, int max, int min, std::size_t offset, std::size_t count,
    bool withscores) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return zrevrangebyscore(key, max, min, offset, count, withscores, cb);
    });
  }
  
  std::future<reply>
  client::zrevrangebyscore(const std::string& key, double max, double min, std::size_t offset, std::size_t count,
    bool withscores) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return zrevrangebyscore(key, max, min, offset, count, withscores, cb);
    });
  }
  
  std::future<reply>
  client::zrevrangebyscore(const std::string& key, const std::string& max, const std::string& min, std::size_t offset,
    std::size_t count, bool withscores) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return zrevrangebyscore(key, max, min, offset, count, withscores, cb);
    });
  }
  
  std::future<reply>
  client::zrevrank(const std::string& key, const std::string& member) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zrevrank(key, member, cb); });
  }
  
  std::future<reply>
  client::zscan(const std::string& key, std::size_t cursor) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zscan(key, cursor, cb); });
  }
  
  std::future<reply>
  client::zscan(const std::string& key, std::size_t cursor, const std::string& pattern) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zscan(key, cursor, pattern, cb); });
  }
  
  std::future<reply>
  client::zscan(const std::string& key, std::size_t cursor, std::size_t count) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zscan(key, cursor, count, cb); });
  }
  
  std::future<reply>
  client::zscan(const std::string& key, std::size_t cursor, const std::string& pattern, std::size_t count) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zscan(key, cursor, pattern, count, cb); });
  }
  
  std::future<reply>
  client::zscore(const std::string& key, const std::string& member) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& { return zscore(key, member, cb); });
  }
  
  std::future<reply>
  client::zunionstore(const std::string& destination, std::size_t numkeys, const std::vector<std::string>& keys,
    const std::vector<std::size_t> weights, aggregate_method method) {
    return exec_cmd([=](const reply_callback_t& cb) -> client& {
      return zunionstore(destination, numkeys, keys, weights, method, cb);
    });
  }
  
  } // namespace cpp_redis
  