#ifndef ANDROID_DVR_BUFFERHUBD_BUFFER_HUB_H_
#define ANDROID_DVR_BUFFERHUBD_BUFFER_HUB_H_

#include <memory>
#include <string>
#include <unordered_map>

#include <hardware/gralloc.h>
#include <pdx/service.h>
#include <private/dvr/bufferhub_rpc.h>

namespace android {
namespace dvr {

class BufferHubService;
class ConsumerChannel;
class ProducerChannel;
class ConsumerQueueChannel;
class ProducerQueueChannel;

class BufferHubChannel : public pdx::Channel {
 public:
  enum ChannelType {
    kProducerType,
    kConsumerType,
    kProducerQueueType,
    kConsumerQueueType,
  };

  enum : int { kDetachedId = -1 };

  BufferHubChannel(BufferHubService* service, int buffer_id, int channel_id,
                   ChannelType channel_type)
      : service_(service),
        buffer_id_(buffer_id),
        channel_id_(channel_id),
        channel_type_(channel_type) {}
  virtual ~BufferHubChannel() {}

  virtual bool HandleMessage(pdx::Message& message) = 0;
  virtual void HandleImpulse(pdx::Message& message) = 0;

  // Captures buffer info for use by BufferHubService::DumpState().
  struct BufferInfo {
    // Common data field shared by BufferProducer and ProducerQueue.
    int id = -1;
    int type = -1;
    size_t consumer_count = 0;

    // Data field for buffer producer.
    uint32_t width = 0;
    uint32_t height = 0;
    uint32_t layer_count = 0;
    uint32_t format = 0;
    uint64_t usage = 0;
    std::string name;

    // Data filed for producer queue.
    size_t capacity = 0;
    UsagePolicy usage_policy{0, 0, 0, 0};

    BufferInfo(int id, size_t consumer_count, uint32_t width, uint32_t height,
               uint32_t layer_count, uint32_t format, uint64_t usage, const std::string& name)
        : id(id),
          type(kProducerType),
          consumer_count(consumer_count),
          width(width),
          height(height),
          layer_count(layer_count),
          format(format),
          usage(usage),
          name(name) {}

    BufferInfo(int id, size_t consumer_count, size_t capacity,
               const UsagePolicy& usage_policy)
        : id(id),
          type(kProducerQueueType),
          consumer_count(consumer_count),
          capacity(capacity),
          usage_policy(usage_policy) {}

    BufferInfo() {}
  };

  // Returns the buffer info for this buffer.
  virtual BufferInfo GetBufferInfo() const = 0;

  // Signal the client fd that an ownership change occurred using POLLIN.
  void SignalAvailable();

  // Clear the ownership change event.
  void ClearAvailable();

  // Signal hangup event.
  void Hangup();

  BufferHubService* service() const { return service_; }
  ChannelType channel_type() const { return channel_type_; }
  int buffer_id() const { return buffer_id_; }

  int channel_id() const { return channel_id_; }
  bool IsDetached() const { return channel_id_ == kDetachedId; }

  void Detach() {
    if (channel_type_ == kProducerType)
      channel_id_ = kDetachedId;
  }
  void Attach(int channel_id) {
    if (channel_type_ == kProducerType && channel_id_ == kDetachedId)
      channel_id_ = channel_id;
  }

 private:
  BufferHubService* service_;

  // Static id of the buffer for logging and informational purposes. This id
  // does not change for the life of the buffer.
  // TODO(eieio): Consider using an id allocator instead of the originating
  // channel id; channel ids wrap after 2^31 ids, but this is not a problem in
  // general because channel ids are not used for any lookup in this service.
  int buffer_id_;

  // The channel id of the buffer. This may change for a persistent producer
  // buffer if it is detached and re-attached to another channel.
  int channel_id_;

  ChannelType channel_type_;

  BufferHubChannel(const BufferHubChannel&) = delete;
  void operator=(const BufferHubChannel&) = delete;
};

class BufferHubService : public pdx::ServiceBase<BufferHubService> {
 public:
  BufferHubService();
  ~BufferHubService() override;

  pdx::Status<void> HandleMessage(pdx::Message& message) override;
  void HandleImpulse(pdx::Message& message) override;

  void OnChannelClose(pdx::Message& message,
                      const std::shared_ptr<pdx::Channel>& channel) override;

  bool IsInitialized() const override;
  std::string DumpState(size_t max_length) override;

  bool AddNamedBuffer(const std::string& name,
                      const std::shared_ptr<ProducerChannel>& buffer);
  std::shared_ptr<ProducerChannel> GetNamedBuffer(const std::string& name);
  bool RemoveNamedBuffer(const ProducerChannel& buffer);

 private:
  friend BASE;

  std::unordered_map<std::string, std::shared_ptr<ProducerChannel>>
      named_buffers_;

  pdx::Status<void> OnCreateBuffer(pdx::Message& message, uint32_t width,
                                   uint32_t height, uint32_t format,
                                   uint64_t usage, size_t meta_size_bytes);
  pdx::Status<void> OnCreatePersistentBuffer(pdx::Message& message,
                                             const std::string& name,
                                             int user_id, int group_id,
                                             uint32_t width, uint32_t height,
                                             uint32_t format, uint64_t usage,
                                             size_t meta_size_bytes);
  pdx::Status<void> OnGetPersistentBuffer(pdx::Message& message,
                                          const std::string& name);
  pdx::Status<QueueInfo> OnCreateProducerQueue(pdx::Message& message,
                                               size_t meta_size_bytes,
                                               const UsagePolicy& usage_policy);

  BufferHubService(const BufferHubService&) = delete;
  void operator=(const BufferHubService&) = delete;
};

}  // namespace dvr
}  // namespace android

#endif  // ANDROID_DVR_BUFFERHUBD_BUFFER_HUB_H_
