#ifndef DEMO_MODEL_H
#define DEMO_MODEL_H

#include <Aabb.h>
#include <Timer.h>
#include <DX12_ResourceDescTable.h>
#include <SkinnedDecals.h>

#define CURRENT_DEMO_MODEL_VERSION 1
#define SKINNING_THREAD_GROUP_SIZE 64 

class DX12_PipelineState;
class DX12_Buffer;
class DX12_TimerQuery;
class Material;
class Shading;

struct WeightIndex
{
  UINT firstIndex;
  UINT numIndices;
};

struct Weight
{
  UINT jointIndex;
	float weight;
	Vector3 position;
	Vector3 normal;
	Vector3 tangent;
};

struct Joint
{
  Vector3 translation;
  Quat rotation;
};

struct AnimFrame
{
  Joint *joints;
  Aabb bounds;
};

struct DemoSubModel
{
  DemoSubModel():
    baseNoDecalsPS(nullptr),
    baseWithDecalsPS(nullptr),
    material(nullptr),
    firstIndex(0),
    numIndices(0)
  {
  }

  DX12_PipelineState *baseNoDecalsPS;
  DX12_PipelineState *baseWithDecalsPS;
  DX12_ResourceDescTable materialDT;
  Material *material;
  UINT firstIndex;
  UINT numIndices;
};

// DemoModel
//
// Simple model format (".model") for storing animated models (only one animation set is supported).
// Data generated by loading a MD5 model, precalculating all information that is required for culling,
// skinning and rendering the model, and storing these information into a binary file format.
class DemoModel
{
public:
  friend class SkinnedDecals;

  struct ModelConstData
  {
    Matrix4 transformMatrix;
    UINT numVertices;
    UINT debugDecalMask;
  };

  DemoModel():
		subModels(nullptr),
    animFrames(nullptr),
    numSubModels(0),
    numJoints(0),
    numAnimFrames(0),
    currentAnimFrameIndex(0),
    nextAnimFrameIndex(0),
    pauseAnim(false),
    visible(false), 
    useDecals(true),
    debugDecalMask(false),
    indexBuffer(nullptr),
    weightIndexSB(nullptr),
    weightSB(nullptr),
    jointSB(nullptr),
    vertexPositionSB(nullptr),
    vertexNormalSB(nullptr),
    vertexTangentSB(nullptr),
    vertexUvHandednessSB(nullptr),
    modelCB(nullptr),
    skinningPS(nullptr),
    skinningTQ(nullptr),
    basePassTQ(nullptr),
    skinnedDecals(nullptr),
    shadingPP(nullptr)
  {
    interpAnimFrame.joints = nullptr;
  }

  ~DemoModel()
  {
    Release();
  }

  void Release();

  bool Load(const char *filename);

  bool InitSkinnedDecals(const char** materialNames, UINT numMaterials);

	void Render();

  const DemoSubModel* GetSubModel(UINT index) const
  {
    assert(index < numSubModels);
    return &subModels[index];
  }

  UINT GetNumSubModels() const
  {
    return numSubModels;   
  }

  void SetPosition(const Vector3 &position)
  {
    this->position = position;
  }

  Vector3 GetPosition() const
  {
    return position;
  }

  void SetRotation(const Vector3 &rotation)
  {
    this->rotation = rotation;
  }

  Vector3 GetRotation() const
  {
    return rotation;
  }

  const Aabb& GetBounds() const 
  {
    return interpAnimFrame.bounds;
  }

  void PauseAnim(bool pauseAnim)
  {
    this->pauseAnim = pauseAnim;
  }

  bool IsAnimPaused() const
  {
    return pauseAnim;
  }

  void UseDecals(bool useDecals)
  {
    this->useDecals = useDecals;
  }

  bool AreDecalsUsed() const 
  {
    return useDecals;
  }

  void EnableDebugDecalMask(bool enable)
  {
    debugDecalMask = enable;
  }

  bool IsDebugDecalMaskEnabled() const 
  {
    return debugDecalMask;
  }

  float GetSkinningGpuTime() const
  {
    return skinningTQ->GetGpuElapsedTime();
  }

  float GetBasePassGpuTime() const
  {
    return basePassTQ->GetGpuElapsedTime();
  }

  SkinnedDecals *GetSkinnedDecals() 
  {
    return skinnedDecals;
  }

private:	 
  void UpdateSkeleton();

  void UpdateBuffers();

  void PerformSkinning();

  void RenderBasePass();

  DemoSubModel *subModels; 
  AnimFrame *animFrames;
  AnimFrame interpAnimFrame;
  Timer animTimer;
  UINT numSubModels;
  UINT numJoints;
  UINT numAnimFrames;
  UINT currentAnimFrameIndex, nextAnimFrameIndex;

  ModelConstData modelConstData;
  Vector3 position;
  Vector3 rotation;
  bool pauseAnim;
  bool visible;
  bool useDecals;
  bool debugDecalMask;
  
  DX12_Buffer *indexBuffer;
  DX12_Buffer *weightIndexSB;
  DX12_Buffer *weightSB;
  DX12_Buffer *jointSB;
  DX12_Buffer *vertexPositionSB;
  DX12_Buffer *vertexNormalSB;
  DX12_Buffer *vertexTangentSB;
  DX12_Buffer *vertexUvHandednessSB;
  DX12_Buffer *modelCB;

  DX12_PipelineState *skinningPS;

  DX12_ResourceDescTable skinningDT; 
  DX12_ResourceDescTable basePassVertexDT;

  DX12_TimerQuery *skinningTQ;
  DX12_TimerQuery *basePassTQ;

  SkinnedDecals *skinnedDecals;
  Shading *shadingPP;

};

#endif