//  BlockyFroggy
//  Copyright © 2017 John Ryland.
//  All rights reserved.
#pragma once
#ifndef GAME_GRAPHICS_H
#define GAME_GRAPHICS_H


#include "OpenGL.h"
#include "GLProgram.h"
#include "Dialog.h"
#include "GameSim.h"
#include "VoxelModel.h"
#include "Math.h"
#include "Camera.h"
#include "VertexArray.h"
#include "Cube.h"
#include "Scene.h"
#include "PolyVertex.h"
#include "Log.h"


#define MODEL_COUNT         27
#define PLAYER_MODEL_COUNT  16


DECLARE_PROGRAM_UNIFORMS(PolyProgram)
  DECLARE_UNIFORM(mat4, modelViewProjectionMatrix)
  DECLARE_UNIFORM(mat4, shadowMapMVP)
  DECLARE_UNIFORM(GLint, shadowMap)
  DECLARE_UNIFORM(GLint, texture)
  DECLARE_UNIFORM(float, time)
  DECLARE_UNIFORM(float, shadowStrength)
  DECLARE_UNIFORM(bool, mixNoise)
  DECLARE_UNIFORM(vec4attrib, translation) // may not need
DECLARE_PROGRAM_UNIFORMS_END


DECLARE_PROGRAM_UNIFORMS(ShadowProgram)
  DECLARE_UNIFORM(mat4, modelViewProjectionMatrix)
  DECLARE_UNIFORM(float, time)
  DECLARE_UNIFORM(vec4attrib, translation) // may not need
DECLARE_PROGRAM_UNIFORMS_END


template <class UniformsType, class VertexType>
class ProgramContext
{
public:
  void setup(const char *a_vertexShader, const char *a_fragmentShader,
                   Precision a_defaultPrecision, const OptionsType& a_options)
  {
    // Setup main program
    auto bindUniformsFunc = [this](GLuint a_program) { m_uniforms.getLocations(a_program); };
    m_program.deleteProgram();
    VertexArray<VertexType> v;
    m_program.loadShaders(a_vertexShader, a_fragmentShader, a_defaultPrecision, a_options, v.bindAttributesFunction(), bindUniformsFunc);
  }
  void shutdown()
  {
    m_program.deleteProgram();
  }
  
//private:
  GLProgram     m_program;
  UniformsType  m_uniforms;
  GLuint        m_framebufferId;
};


class DrawableModels
{
public:
  void loadVoxelModel(int index, const char* name, int x, int y, int w, int h, int d, bool direction, int scaling, bool asCubes = false, int yClamp0 = 0, int yClamp1 = 256);

  VoxelModels                            m_models;
  std::vector<VertexArray<PolyVertex> >  m_modelsVertexArrays;
  GLuint                                 m_axisTextureIds[3];
};
// 64 256x256 tiles in a 2048x2048 texture


/*
class TextureManager
{
public:
  TextureManager();
  ~TextureManager();

private:
  void LoadPendingTexture(GameUi::TextureLoadJob& job);
};
*/


class GameGraphics
{
public:
  DECLARE_LOG_CONTEXT(GGX)

  GameGraphics();
  ~GameGraphics();

  void resetVertexBuffers(const std::vector<GameSim::GameObjectList*>& a_objectLists);

  void loadVoxelModel(VertexArray<PolyVertex>& vertexArray, const char* name, int x, int y, int w, int h, int d, bool direction, int scaling, bool asCubes = false, int yClamp0 = 0, int yClamp1 = 256);

  void reloadShaders();
  void initialize();
  void shutdown();
  void update(float a_elapsed, float a_aspect, const GameUi::UpdateState& a_state, float a_playerPos[3], float a_cameraStyle[8], float a_light[8], float a_debugCamera[8]);

  void drawDebug(GameUi::DrawItems& a_state);

  void preDraw(float a_time);

  void prepareState(GameUi::UpdateState& a_state);

  void prepareShadowContext();
  void renderShadows();

  void prepareScreenContext();
  void renderScreen();

  void prepareHUDContext();


  void setEditModel(int modelIndex)  { m_editModel = modelIndex; m_editModelDirty = true; }
  void setEditLayer(float editLayer) { m_editLayer = editLayer;  m_editModelDirty = true; }

  void draw();

  // Common code for drawing object lists
  void drawObjectListHelper(const GameSim::GameObjectList& a_objs, GLsizei siz, bool direction);

  void drawObjectTypeList(const GameSim::GameObjectList& a_objs, int a_voxelModelIndex);
  void drawObjectTypeListTiled(const GameSim::GameObjectList& a_objs, int a_voxelModelIndex, int zOff, int wid);
  void drawScene();
  void drawModelEditLayer();
  void drawModelRotatingPreview();
  void drawRotatingFrog();
  void drawCaroselTest();
  void drawHUD();
  void drawAuxItems(const GameUi::DrawItems& a_uiItems);
  void drawViews(const std::vector<GameUi::View*>& a_views);

  void getEditObjectPixels(float editLayer, std::vector<uint32_t>& pixels, int& w, int& h, int& d);
  void setEditObjectPixels(float editLayer, const std::vector<uint32_t>& pixels, int w, int h);
  void getPalettePixels(std::vector<uint32_t>& pixels);

  void LoadPendingTexture(GameUi::TextureLoadJob& job);

  // Retrieved from GL viewport
  uint32_t  m_w = 0, m_h = 0;

private:
  std::map<std::string, std::function<void()> > m_viewMap;

  OptionsType m_options;
  //std::map<std::string,bool>  m_options;

  int            m_editModel = MODEL_COUNT - 1;
  float          m_editLayer = 0.0;

  float          m_cameraX, m_cameraY;
  float          m_playerX, m_playerY, m_playerZ;

  float          m_time = 0.0;

  // HUD
  bool           m_hudEnabled = true;
  GLProgram      m_hudProgram;
  GLuint         m_hudVertexArray;
  GLuint         m_hudVertexBuffer;
  GLuint         m_hudTexId = 0;
  size_t         m_hudItemCount = 0;

  // Aux
  GLuint         m_auxVertexArray;
  GLuint         m_auxVertexBuffer;

  // Shadows
  bool           m_shadowsEnabled = true;
  
  ProgramContext<PolyProgram, PolyVertex>   m_shadowContext;
  //GLProgram      m_shadowProgram;
  GLuint         m_shadowTexId = 0;
  GLuint         m_shadowFramebufferId = 0;
  //PolyProgram    m_shadowUniforms;


  // Polygons
  ProgramContext<PolyProgram, PolyVertex>   m_normalContext;
  //GLProgram      m_program;
  GLuint         m_defaultFramebufferId = 0;
  //PolyProgram    m_polyUniforms;


  // Current Context
  ProgramContext<PolyProgram, PolyVertex>*  m_currentContext = &m_normalContext;



  Camera         m_light;
  Camera         m_camera;
  Camera         m_debugCamera;

  VertexArray<PolyVertex>  m_staticPolyVertexArray;
  VertexArray<PolyVertex>  m_dynamicPolyVertexArray;
  VertexArray<PolyVertex>  m_editModelVertexArray1;
  VertexArray<PolyVertex>  m_editModelVertexArray2;
  bool                     m_editModelDirty = true;
  int                      modelIdx = 0;

  VoxelModels              m_models;
  VertexArray<PolyVertex>  m_modelsVertexArrays[MODEL_COUNT];

  VertexArray<PolyVertex>  m_combinedModelsVertexArrays;//[MODEL_COUNT];
  VertexArray<PolyVertex>  m_modelsVertexArraysCopy[MODEL_COUNT];

  // a 2048x2048 image can contain 1024 voxel models which are 16x16x16
  // or about 341 models when take in to account needing 3 textures for each axis
  // if target 100 different models, need about 19 256x256 texture slots
  // if plan to dynamically convert to PVR at runtime, then probably better to
  // actually use smaller textures, but means needing to switch texture state
  // when rendering

  VoxelModels              m_playerModels;
  VertexArray<PolyVertex>  m_playerModelsVertexArrays[PLAYER_MODEL_COUNT];

  // Scene
  Scene                    m_scene;

  // move to DrawableModels class
  uint32_t *m_model_x_axis = nullptr;
  uint32_t *m_model_z_axis = nullptr;

  void drawCuboid(float frustum[8][4]);
  void drawSolidCuboid(float x, float y, float z, float w, float h, float d, uint32_t col, float scale);
  VertexArray<PolyVertex> m_cuboidVertexList;

  // For debugging
  void drawCube(float x, float y, float z, float w, float h, float d, uint32_t color); // For debugging
};


#endif // GAME_GRAPHICS_H

