/**
 ******************************************************************************
 * Xenia : Xbox 360 Emulator Research Project                                 *
 ******************************************************************************
 * Copyright 2014 Ben Vanik. All rights reserved.                             *
 * Released under the BSD license - see LICENSE in the root for more details. *
 ******************************************************************************
 */

#ifndef XENIA_GPU_GL4_COMMAND_PROCESSOR_H_
#define XENIA_GPU_GL4_COMMAND_PROCESSOR_H_

#include <atomic>
#include <cstring>
#include <functional>
#include <memory>
#include <mutex>
#include <queue>
#include <string>
#include <unordered_map>
#include <vector>

#include "xenia/base/threading.h"
#include "xenia/gpu/gl4/draw_batcher.h"
#include "xenia/gpu/gl4/gl4_shader.h"
#include "xenia/gpu/gl4/gl4_shader_translator.h"
#include "xenia/gpu/gl4/texture_cache.h"
#include "xenia/gpu/register_file.h"
#include "xenia/gpu/tracing.h"
#include "xenia/gpu/xenos.h"
#include "xenia/kernel/xthread.h"
#include "xenia/memory.h"
#include "xenia/ui/gl/circular_buffer.h"
#include "xenia/ui/gl/gl_context.h"

namespace xe {
namespace kernel {
class XHostThread;
}  // namespace kernel
}  // namespace xe

namespace xe {
namespace gpu {
namespace gl4 {

class GL4GraphicsSystem;

struct SwapState {
  // Lock must be held when changing data in this structure.
  std::mutex mutex;
  // Dimensions of the framebuffer textures. Should match window size.
  uint32_t width = 0;
  uint32_t height = 0;
  // Current front buffer, being drawn to the screen.
  GLuint front_buffer_texture = 0;
  // Current back buffer, being updated by the CP.
  GLuint back_buffer_texture = 0;
  // Whether the back buffer is dirty and a swap is pending.
  bool pending = false;
};

enum class SwapMode {
  kNormal,
  kIgnored,
};

class CommandProcessor {
 public:
  explicit CommandProcessor(GL4GraphicsSystem* graphics_system);
  ~CommandProcessor();

  uint32_t counter() const { return counter_; }
  void increment_counter() { counter_++; }

  bool Initialize(std::unique_ptr<xe::ui::GraphicsContext> context);
  void Shutdown();
  void CallInThread(std::function<void()> fn);

  void ClearCaches();

  SwapState& swap_state() { return swap_state_; }
  void set_swap_mode(SwapMode swap_mode) { swap_mode_ = swap_mode; }
  void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
                 uint32_t frontbuffer_height);

  void set_swap_request_handler(std::function<void()> fn) {
    swap_request_handler_ = fn;
  }

  void RequestFrameTrace(const std::wstring& root_path);
  void BeginTracing(const std::wstring& root_path);
  void EndTracing();

  void InitializeRingBuffer(uint32_t ptr, uint32_t page_count);
  void EnableReadPointerWriteBack(uint32_t ptr, uint32_t block_size);

  void UpdateWritePointer(uint32_t value);

  void ExecutePacket(uint32_t ptr, uint32_t count);

  // HACK: for debugging; would be good to have this in a base type.
  TextureCache* texture_cache() { return &texture_cache_; }
  GL4Shader* active_vertex_shader() const { return active_vertex_shader_; }
  GL4Shader* active_pixel_shader() const { return active_pixel_shader_; }

  GLuint GetColorRenderTarget(uint32_t pitch, xenos::MsaaSamples samples,
                              uint32_t base,
                              xenos::ColorRenderTargetFormat format);
  GLuint GetDepthRenderTarget(uint32_t pitch, xenos::MsaaSamples samples,
                              uint32_t base,
                              xenos::DepthRenderTargetFormat format);

 private:
  class RingbufferReader;

  enum class UpdateStatus {
    kCompatible,
    kMismatch,
    kError,
  };

  struct CachedFramebuffer {
    GLuint color_targets[4];
    GLuint depth_target;
    GLuint framebuffer;
  };
  struct CachedColorRenderTarget {
    uint32_t base;
    uint32_t width;
    uint32_t height;
    xenos::ColorRenderTargetFormat format;
    GLuint texture;
  };
  struct CachedDepthRenderTarget {
    uint32_t base;
    uint32_t width;
    uint32_t height;
    xenos::DepthRenderTargetFormat format;
    GLuint texture;
  };
  struct CachedPipeline {
    CachedPipeline();
    ~CachedPipeline();
    GLuint vertex_program;
    GLuint fragment_program;
    struct {
      GLuint default_pipeline;
      GLuint point_list_pipeline;
      GLuint rect_list_pipeline;
      GLuint quad_list_pipeline;
      GLuint line_quad_list_pipeline;
      // TODO(benvanik): others with geometry shaders.
    } handles;
  };

  void WorkerThreadMain();
  bool SetupGL();
  void ShutdownGL();
  GLuint CreateGeometryProgram(const std::string& source);

  void WriteRegister(uint32_t index, uint32_t value);
  void MakeCoherent();
  void PrepareForWait();
  void ReturnFromWait();

  void ExecutePrimaryBuffer(uint32_t start_index, uint32_t end_index);
  void ExecuteIndirectBuffer(uint32_t ptr, uint32_t length);
  bool ExecutePacket(RingbufferReader* reader);
  bool ExecutePacketType0(RingbufferReader* reader, uint32_t packet);
  bool ExecutePacketType1(RingbufferReader* reader, uint32_t packet);
  bool ExecutePacketType2(RingbufferReader* reader, uint32_t packet);
  bool ExecutePacketType3(RingbufferReader* reader, uint32_t packet);
  bool ExecutePacketType3_ME_INIT(RingbufferReader* reader, uint32_t packet,
                                  uint32_t count);
  bool ExecutePacketType3_NOP(RingbufferReader* reader, uint32_t packet,
                              uint32_t count);
  bool ExecutePacketType3_INTERRUPT(RingbufferReader* reader, uint32_t packet,
                                    uint32_t count);
  bool ExecutePacketType3_XE_SWAP(RingbufferReader* reader, uint32_t packet,
                                  uint32_t count);
  bool ExecutePacketType3_INDIRECT_BUFFER(RingbufferReader* reader,
                                          uint32_t packet, uint32_t count);
  bool ExecutePacketType3_WAIT_REG_MEM(RingbufferReader* reader,
                                       uint32_t packet, uint32_t count);
  bool ExecutePacketType3_REG_RMW(RingbufferReader* reader, uint32_t packet,
                                  uint32_t count);
  bool ExecutePacketType3_COND_WRITE(RingbufferReader* reader, uint32_t packet,
                                     uint32_t count);
  bool ExecutePacketType3_EVENT_WRITE(RingbufferReader* reader, uint32_t packet,
                                      uint32_t count);
  bool ExecutePacketType3_EVENT_WRITE_SHD(RingbufferReader* reader,
                                          uint32_t packet, uint32_t count);
  bool ExecutePacketType3_EVENT_WRITE_EXT(RingbufferReader* reader,
                                          uint32_t packet, uint32_t count);
  bool ExecutePacketType3_DRAW_INDX(RingbufferReader* reader, uint32_t packet,
                                    uint32_t count);
  bool ExecutePacketType3_DRAW_INDX_2(RingbufferReader* reader, uint32_t packet,
                                      uint32_t count);
  bool ExecutePacketType3_SET_CONSTANT(RingbufferReader* reader,
                                       uint32_t packet, uint32_t count);
  bool ExecutePacketType3_SET_CONSTANT2(RingbufferReader* reader,
                                        uint32_t packet, uint32_t count);
  bool ExecutePacketType3_LOAD_ALU_CONSTANT(RingbufferReader* reader,
                                            uint32_t packet, uint32_t count);
  bool ExecutePacketType3_SET_SHADER_CONSTANTS(RingbufferReader* reader,
                                               uint32_t packet, uint32_t count);
  bool ExecutePacketType3_IM_LOAD(RingbufferReader* reader, uint32_t packet,
                                  uint32_t count);
  bool ExecutePacketType3_IM_LOAD_IMMEDIATE(RingbufferReader* reader,

                                            uint32_t packet, uint32_t count);
  bool ExecutePacketType3_INVALIDATE_STATE(RingbufferReader* reader,
                                           uint32_t packet, uint32_t count);

  bool LoadShader(ShaderType shader_type, uint32_t guest_address,
                  const uint32_t* host_address, uint32_t dword_count);

  bool IssueDraw();
  UpdateStatus UpdateShaders(PrimitiveType prim_type);
  UpdateStatus UpdateRenderTargets();
  UpdateStatus UpdateState();
  UpdateStatus UpdateViewportState();
  UpdateStatus UpdateRasterizerState();
  UpdateStatus UpdateBlendState();
  UpdateStatus UpdateDepthStencilState();
  UpdateStatus PopulateIndexBuffer();
  UpdateStatus PopulateVertexBuffers();
  UpdateStatus PopulateSamplers();
  UpdateStatus PopulateSampler(const Shader::SamplerDesc& desc);
  bool IssueCopy();

  CachedFramebuffer* GetFramebuffer(GLuint color_targets[4],
                                    GLuint depth_target);

  Memory* memory_;
  GL4GraphicsSystem* graphics_system_;
  RegisterFile* register_file_;

  TraceWriter trace_writer_;
  enum class TraceState {
    kDisabled,
    kStreaming,
    kSingleFrame,
  };
  TraceState trace_state_;
  std::wstring trace_frame_path_;

  std::atomic<bool> worker_running_;
  kernel::object_ref<kernel::XHostThread> worker_thread_;

  std::unique_ptr<xe::ui::GraphicsContext> context_;
  SwapMode swap_mode_;
  SwapState swap_state_;
  std::function<void()> swap_request_handler_;
  std::queue<std::function<void()>> pending_fns_;

  uint32_t counter_;

  uint32_t primary_buffer_ptr_;
  uint32_t primary_buffer_size_;

  uint32_t read_ptr_index_;
  uint32_t read_ptr_update_freq_;
  uint32_t read_ptr_writeback_ptr_;

  std::unique_ptr<xe::threading::Event> write_ptr_index_event_;
  std::atomic<uint32_t> write_ptr_index_;

  uint64_t bin_select_;
  uint64_t bin_mask_;

  GL4ShaderTranslator shader_translator_;
  std::vector<std::unique_ptr<GL4Shader>> all_shaders_;
  std::unordered_map<uint64_t, GL4Shader*> shader_cache_;
  GL4Shader* active_vertex_shader_;
  GL4Shader* active_pixel_shader_;
  CachedFramebuffer* active_framebuffer_;
  GLuint last_framebuffer_texture_;

  std::vector<CachedFramebuffer> cached_framebuffers_;
  std::vector<CachedColorRenderTarget> cached_color_render_targets_;
  std::vector<CachedDepthRenderTarget> cached_depth_render_targets_;
  std::vector<std::unique_ptr<CachedPipeline>> all_pipelines_;
  std::unordered_map<uint64_t, CachedPipeline*> cached_pipelines_;
  GLuint point_list_geometry_program_;
  GLuint rect_list_geometry_program_;
  GLuint quad_list_geometry_program_;
  GLuint line_quad_list_geometry_program_;
  struct {
    xenos::IndexFormat format;
    xenos::Endian endianness;
    uint32_t count;
    uint32_t guest_base;
    size_t length;
  } index_buffer_info_;
  uint32_t draw_index_count_;

  TextureCache texture_cache_;

  DrawBatcher draw_batcher_;
  xe::ui::gl::CircularBuffer scratch_buffer_;

 private:
  bool SetShadowRegister(uint32_t* dest, uint32_t register_name);
  bool SetShadowRegister(float* dest, uint32_t register_name);
  struct UpdateRenderTargetsRegisters {
    uint32_t rb_modecontrol;
    uint32_t rb_surface_info;
    uint32_t rb_color_info;
    uint32_t rb_color1_info;
    uint32_t rb_color2_info;
    uint32_t rb_color3_info;
    uint32_t rb_color_mask;
    uint32_t rb_depthcontrol;
    uint32_t rb_stencilrefmask;
    uint32_t rb_depth_info;

    UpdateRenderTargetsRegisters() { Reset(); }
    void Reset() { std::memset(this, 0, sizeof(*this)); }
  } update_render_targets_regs_;
  struct UpdateViewportStateRegisters {
    // uint32_t pa_cl_clip_cntl;
    uint32_t rb_surface_info;
    uint32_t pa_cl_vte_cntl;
    uint32_t pa_su_sc_mode_cntl;
    uint32_t pa_sc_window_offset;
    uint32_t pa_sc_window_scissor_tl;
    uint32_t pa_sc_window_scissor_br;
    float pa_cl_vport_xoffset;
    float pa_cl_vport_yoffset;
    float pa_cl_vport_zoffset;
    float pa_cl_vport_xscale;
    float pa_cl_vport_yscale;
    float pa_cl_vport_zscale;

    UpdateViewportStateRegisters() { Reset(); }
    void Reset() { std::memset(this, 0, sizeof(*this)); }
  } update_viewport_state_regs_;
  struct UpdateRasterizerStateRegisters {
    uint32_t pa_su_sc_mode_cntl;
    uint32_t pa_sc_screen_scissor_tl;
    uint32_t pa_sc_screen_scissor_br;
    uint32_t multi_prim_ib_reset_index;

    UpdateRasterizerStateRegisters() { Reset(); }
    void Reset() { std::memset(this, 0, sizeof(*this)); }
  } update_rasterizer_state_regs_;
  struct UpdateBlendStateRegisters {
    uint32_t rb_blendcontrol[4];
    float rb_blend_rgba[4];

    UpdateBlendStateRegisters() { Reset(); }
    void Reset() { std::memset(this, 0, sizeof(*this)); }
  } update_blend_state_regs_;
  struct UpdateDepthStencilStateRegisters {
    uint32_t rb_depthcontrol;
    uint32_t rb_stencilrefmask;

    UpdateDepthStencilStateRegisters() { Reset(); }
    void Reset() { std::memset(this, 0, sizeof(*this)); }
  } update_depth_stencil_state_regs_;
  struct UpdateShadersRegisters {
    PrimitiveType prim_type;
    uint32_t pa_su_sc_mode_cntl;
    uint32_t sq_program_cntl;
    GL4Shader* vertex_shader;
    GL4Shader* pixel_shader;

    UpdateShadersRegisters() { Reset(); }
    void Reset() {
      sq_program_cntl = 0;
      vertex_shader = pixel_shader = nullptr;
    }
  } update_shaders_regs_;
};

}  // namespace gl4
}  // namespace gpu
}  // namespace xe

#endif  // XENIA_GPU_GL4_COMMAND_PROCESSOR_H_
