// -*- C++ -*-

#ifndef REDISPUBSUB_H
#define REDISPUBSUB_H

/*!
 * @file   RedisPubSub.h
 * @brief  RedisPubSub is an utility with Publisher/Subscirber
           model based on Redis for J-PARC/MLF. The Redis is an open source
           (BSD licensed), in-memory data structure store,
           used as a database, cache and message broker to offer
           high performance, replication and an unique data model
           supporing several different types of data structures.
           The RedisPubSub was created without using smart pointer, but
           the smart pointer technique was adopted by the recommendation from
           Hosoya-san and then the RedisPubSub was renewed.

 * @date   25-OCT-2017
 * @version 0.9.0
 * @author Yoshiji Yasu (Yoshiji.Yasu@kek.jp)
 *
 */

//!< standard library
#include <iostream>
#include <vector>
#include <stdint.h>
#include <string>
#include <cstring>   // std::memcpy

//!< hiredis
#include "hiredis/hiredis.h"

//!< smart pointer
#include <memory>
//using std::string;
//using std::vector;

//! Redis context deleter using Redis Publisher/Subscriber Package
/*!
  redisConnect() returns the Redis context that is dynamically created.
  RedisContextDeleter() will make it free.
 */
struct RedisContextDeleter
{
  void operator()(redisContext* context) {
    redisFree(context);
  }
};

//! Redis reply object deleter using Redis Publisher/Subscriber Package
/*!
  redisCommand() returns the Redis reply object that is dynamically created.
  RedisReplyObjectDeleter() will make it free.
 */
struct RedisReplyObjectDeleter
{
  void operator()(redisReply* reply) {
    freeReplyObject(reply);
  }
};

//! Publisher using Redis Publisher/Subscriber Package
/*!
  RedisPublisher() publishes data in Redis server(broker).
 */
class RedisPublisher
{
  std::string m_host;  //! Host IP address
  int32_t m_port; //! Port number
  std::shared_ptr<redisContext> m_context; //!< Redis context

public:
  //! Default constructor
  RedisPublisher();
  //! Constructor
  /*!
    @param[in]  host   Host IP address of the broker.
    @param[in]  port   Port number of the broker.
  */
  RedisPublisher(const std::string& host, int32_t port = 6379);
  //! Destructor
  ~RedisPublisher();

  //! Initialize publisher and connect to broker.
  /*!
    @retval     0    Success
    @retval     -1   Redis context was not allocated
    @retval     -2   Redis context has some error condition
  */
  int32_t init();

  //! Publish data to the Redis server.
  /*!
    @param[in]  name     Name of data
    @param[in]  src_buf  Source buffer with data inside
    @param[in]  size     Size of data
    @retval     n(>=0)   number of clients that received the data
    @retval     -1       no Redis context(empty)
    @retval     -2       redisCommand was not executed correctly
    @retval     -3       redisCommand error
  */
  int32_t publish(const std::string& name, const char* src_buf, int32_t size);
};

//! Subscriber using Redis Publisher/Subscriber Package
/*!
  RedisSubscriber() is a superclass of RedisSyncSubscriber(),
  which subscribes data in Redis server(broker).
 */
class RedisSubscriber
{
protected:
  std::string m_host;  //! Host IP address
  int32_t m_port; //! Port number
  std::shared_ptr<redisContext> m_context; //! Redis context

public:
  //! Default constructor
  RedisSubscriber();
  //! Constructor
  /*!
    @param[in]  host   Host IP address of the broker.
    @param[in]  port   Port number of the broker.
  */
  RedisSubscriber(const std::string& host, int32_t port = 6379);
  //! Destructor
  virtual ~RedisSubscriber();

  //! Set host address of the broker
  /*!
    @param[in]  host   Host IP address of the broker.
    @param[in]  port   Port number of the broker.
  */
  void setHost(const std::string& host, int32_t port);

};

//! Synchronous subscriber using Redis Publisher/Subscriber Package
/*!
  RedisSyncSubscriber() inherits from RedisSubscriber() and
  subscribes data in Redis server(broker).
 */
class RedisSyncSubscriber : public RedisSubscriber
{

public:
  //! Default constructor
  RedisSyncSubscriber();

  //! Constructor
  /*!
    @param[in]  host   Host IP address of the broker.
    @param[in]  port   Port number of the broker.
  */
  RedisSyncSubscriber(const std::string& host, int32_t port = 6379);

  //! Destructor
  virtual ~RedisSyncSubscriber();

  //! Initialize subscriber as synchronous mode and connect to the broker.
  /*!
    @retval     0    Success
    @retval     -1   Redis context was not allocated
    @retval     -2   Redis context has some error condition
  */
  int32_t init();

  //! Subscribe data from the Redis broker (sync).
  /*!
    @param[in]  name  Name of data
    @retval      0    Success
    @retval     -1    no Redis context(empty)
    @retval     -2    redisCommand was not executed correctly
    @retval     -3    redisCommand error
  */
  int32_t subscribe(const std::string& name);

  //! Unsubscribe data from the Redis broker (sync).
  /*!
    @param[in]  name      Name of data
    @retval      0    Success
    @retval     -1    no Redis context(empty)
    @retval     -2    redisCommand was not executed correctly
    @retval     -3    redisCommand error
  */
  int32_t unsubscribe(const std::string& name);

  //! Get data from the Redis broker (sync).
  /*!
    @param[out]  name        Name of data
    @param[out]  target_buf  Target buffer
    @param[out]  size        Size of data
    @param[in]   timeout     Timeout milli-seconds
    @retval      1    Time out
    @retval      0    Success
    @retval     -1    no Redis context(empty)
    @retval     -2    redisSetTimeout() error
    @retval     -3    Redis reply context was null
    @retval     -4    Redis reply error
  */
  int32_t get(std::string& name, char* target_buf, int32_t& size,
              int32_t timeout = 2000);

  //! Get name of channels from the Redis broker (sync).
  //! This method should be called before subscribe() or after unsubscribe().
  /*!
    @param[in]  name  Name of data(allow asterisk like ml*)
    @param[out] vec   list of the name
    @retval      0    Success
    @retval     -1    no Redis context(empty)
    @retval     -2    redisCommand was not executed correctly
    @retval     -3    redisCommand error
  */
  int32_t getChannels(std::string name, std::vector<std::string>& vec);

};

#endif // REDISPUBSUB_H
