#ifndef ANDROID_DVR_DISPLAY_PROTOCOL_H_
#define ANDROID_DVR_DISPLAY_PROTOCOL_H_

#include <sys/types.h>

#include <array>
#include <map>

#include <dvr/dvr_display_types.h>

#include <pdx/rpc/remote_method.h>
#include <pdx/rpc/serializable.h>
#include <pdx/rpc/variant.h>
#include <private/dvr/bufferhub_rpc.h>

// RPC protocol definitions for DVR display services (VrFlinger).

namespace android {
namespace dvr {
namespace display {

// Native display metrics.
struct Metrics {
  // Basic display properties.
  uint32_t display_width;
  uint32_t display_height;
  uint32_t display_x_dpi;
  uint32_t display_y_dpi;
  uint32_t vsync_period_ns;

  // HMD metrics.
  // TODO(eieio): Determine how these fields should be populated. On phones
  // these values are determined at runtime by VrCore based on which headset the
  // phone is in. On dedicated hardware this needs to come from somewhere else.
  // Perhaps these should be moved to a separate structure that is returned by a
  // separate runtime call.
  uint32_t distorted_width;
  uint32_t distorted_height;
  uint32_t hmd_ipd_mm;
  float inter_lens_distance_m;
  std::array<float, 4> left_fov_lrbt;
  std::array<float, 4> right_fov_lrbt;

 private:
  PDX_SERIALIZABLE_MEMBERS(Metrics, display_width, display_height,
                           display_x_dpi, display_y_dpi, vsync_period_ns,
                           distorted_width, distorted_height, hmd_ipd_mm,
                           inter_lens_distance_m, left_fov_lrbt,
                           right_fov_lrbt);
};

// Serializable base type for enum structs. Enum structs are easier to use than
// enum classes, especially for bitmasks. This base type provides common
// utilities for flags types.
template <typename Integer>
class Flags {
 public:
  using Base = Flags<Integer>;
  using Type = Integer;

  Flags(const Integer& value) : value_{value} {}
  Flags(const Flags&) = default;
  Flags& operator=(const Flags&) = default;

  Integer value() const { return value_; }
  operator Integer() const { return value_; }

  bool IsSet(Integer bits) const { return (value_ & bits) == bits; }
  bool IsClear(Integer bits) const { return (value_ & bits) == 0; }

  void Set(Integer bits) { value_ |= bits; }
  void Clear(Integer bits) { value_ &= ~bits; }

  Integer operator|(Integer bits) const { return value_ | bits; }
  Integer operator&(Integer bits) const { return value_ & bits; }

  Flags& operator|=(Integer bits) {
    value_ |= bits;
    return *this;
  }
  Flags& operator&=(Integer bits) {
    value_ &= bits;
    return *this;
  }

 private:
  Integer value_;

  PDX_SERIALIZABLE_MEMBERS(Flags<Integer>, value_);
};

// Flags indicating what changed since last update.
struct SurfaceUpdateFlags : public Flags<uint32_t> {
  enum : Type {
    None = DVR_SURFACE_UPDATE_FLAGS_NONE,
    NewSurface = DVR_SURFACE_UPDATE_FLAGS_NEW_SURFACE,
    BuffersChanged = DVR_SURFACE_UPDATE_FLAGS_BUFFERS_CHANGED,
    VisibilityChanged = DVR_SURFACE_UPDATE_FLAGS_VISIBILITY_CHANGED,
    AttributesChanged = DVR_SURFACE_UPDATE_FLAGS_ATTRIBUTES_CHANGED,
  };

  SurfaceUpdateFlags() : Base{None} {}
  using Base::Base;
};

// Surface attribute key/value types.
using SurfaceAttributeKey = int32_t;
using SurfaceAttributeValue =
    pdx::rpc::Variant<int32_t, int64_t, bool, float, std::array<float, 2>,
                      std::array<float, 3>, std::array<float, 4>,
                      std::array<float, 8>, std::array<float, 16>>;

// Defined surface attribute keys.
struct SurfaceAttribute : public Flags<SurfaceAttributeKey> {
  enum : Type {
    // Keys in the negative integer space are interpreted by VrFlinger for
    // direct surfaces.
    Direct = DVR_SURFACE_ATTRIBUTE_DIRECT,
    ZOrder = DVR_SURFACE_ATTRIBUTE_Z_ORDER,
    Visible = DVR_SURFACE_ATTRIBUTE_VISIBLE,

    // Invalid key. May be used to terminate C style lists in public API code.
    Invalid = DVR_SURFACE_ATTRIBUTE_INVALID,

    // Positive keys are interpreted by the compositor only.
    FirstUserKey = DVR_SURFACE_ATTRIBUTE_FIRST_USER_KEY,
  };

  SurfaceAttribute() : Base{Invalid} {}
  using Base::Base;
};

// Collection of surface attribute key/value pairs.
using SurfaceAttributes = std::map<SurfaceAttributeKey, SurfaceAttributeValue>;

struct SurfaceState {
  int32_t surface_id;
  int32_t process_id;
  int32_t user_id;

  SurfaceAttributes surface_attributes;
  SurfaceUpdateFlags update_flags;
  std::vector<int32_t> queue_ids;

  // Convenience accessors.
  bool GetVisible() const {
    bool bool_value = false;
    GetAttribute(SurfaceAttribute::Visible, &bool_value,
                 ValidTypes<int32_t, int64_t, bool, float>{});
    return bool_value;
  }

  int GetZOrder() const {
    int int_value = 0;
    GetAttribute(SurfaceAttribute::ZOrder, &int_value,
                 ValidTypes<int32_t, int64_t, float>{});
    return int_value;
  }

 private:
  template <typename... Types>
  struct ValidTypes {};

  template <typename ReturnType, typename... Types>
  bool GetAttribute(SurfaceAttributeKey key, ReturnType* out_value,
                    ValidTypes<Types...>) const {
    auto search = surface_attributes.find(key);
    if (search != surface_attributes.end())
      return pdx::rpc::IfAnyOf<Types...>::Get(&search->second, out_value);
    else
      return false;
  }

  PDX_SERIALIZABLE_MEMBERS(SurfaceState, surface_id, process_id,
                           surface_attributes, update_flags, queue_ids);
};

struct SurfaceInfo {
  int surface_id;
  bool visible;
  int z_order;

 private:
  PDX_SERIALIZABLE_MEMBERS(SurfaceInfo, surface_id, visible, z_order);
};

struct DisplayProtocol {
  // Service path.
  static constexpr char kClientPath[] = "system/vr/display/client";

  // Op codes.
  enum {
    kOpGetMetrics = 0,
    kOpGetNamedBuffer,
    kOpIsVrAppRunning,
    kOpCreateSurface,
    kOpGetSurfaceInfo,
    kOpCreateQueue,
    kOpSetAttributes,
  };

  // Aliases.
  using LocalChannelHandle = pdx::LocalChannelHandle;
  using Void = pdx::rpc::Void;

  // Methods.
  PDX_REMOTE_METHOD(GetMetrics, kOpGetMetrics, Metrics(Void));
  PDX_REMOTE_METHOD(GetNamedBuffer, kOpGetNamedBuffer,
                    LocalNativeBufferHandle(std::string name));
  PDX_REMOTE_METHOD(IsVrAppRunning, kOpIsVrAppRunning, bool(Void));
  PDX_REMOTE_METHOD(CreateSurface, kOpCreateSurface,
                    SurfaceInfo(const SurfaceAttributes& attributes));
  PDX_REMOTE_METHOD(GetSurfaceInfo, kOpGetSurfaceInfo, SurfaceInfo(Void));
  PDX_REMOTE_METHOD(CreateQueue, kOpCreateQueue,
                    LocalChannelHandle(size_t meta_size_bytes));
  PDX_REMOTE_METHOD(SetAttributes, kOpSetAttributes,
                    void(const SurfaceAttributes& attributes));
};

struct DisplayManagerProtocol {
  // Service path.
  static constexpr char kClientPath[] = "system/vr/display/manager";

  // Op codes.
  enum {
    kOpGetSurfaceState = 0,
    kOpGetSurfaceQueue,
    kOpSetupNamedBuffer,
  };

  // Aliases.
  using LocalChannelHandle = pdx::LocalChannelHandle;
  using Void = pdx::rpc::Void;

  // Methods.
  PDX_REMOTE_METHOD(GetSurfaceState, kOpGetSurfaceState,
                    std::vector<SurfaceState>(Void));
  PDX_REMOTE_METHOD(GetSurfaceQueue, kOpGetSurfaceQueue,
                    LocalChannelHandle(int surface_id, int queue_id));
  PDX_REMOTE_METHOD(SetupNamedBuffer, kOpSetupNamedBuffer,
                    LocalNativeBufferHandle(const std::string& name,
                                            size_t size, uint64_t usage));
};

struct VSyncSchedInfo {
  int64_t vsync_period_ns;
  int64_t timestamp_ns;
  uint32_t next_vsync_count;

 private:
  PDX_SERIALIZABLE_MEMBERS(VSyncSchedInfo, vsync_period_ns, timestamp_ns,
                           next_vsync_count);
};

struct VSyncProtocol {
  // Service path.
  static constexpr char kClientPath[] = "system/vr/display/vsync";

  // Op codes.
  enum {
    kOpWait = 0,
    kOpAck,
    kOpGetLastTimestamp,
    kOpGetSchedInfo,
    kOpAcknowledge,
  };

  // Aliases.
  using Void = pdx::rpc::Void;
  using Timestamp = int64_t;

  // Methods.
  PDX_REMOTE_METHOD(Wait, kOpWait, Timestamp(Void));
  PDX_REMOTE_METHOD(GetLastTimestamp, kOpGetLastTimestamp, Timestamp(Void));
  PDX_REMOTE_METHOD(GetSchedInfo, kOpGetSchedInfo, VSyncSchedInfo(Void));
  PDX_REMOTE_METHOD(Acknowledge, kOpAcknowledge, void(Void));
};

}  // namespace display
}  // namespace dvr
}  // namespace android

#endif  // ANDROID_DVR_DISPLAY_PROTOCOL_H_
