/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
#ifndef COMMON_H
#define COMMON_H
#include <glew/glew.h>
#include <glm/glm.hpp>
#include <string>
#include <vector>
#include <map>
#include "Texture.h"
#include "Shaders.h"

namespace glTF
{
    typedef struct Vector3D
    {
    public:
        Vector3D () : x(), y(), z() {}
        Vector3D (float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}
        float x, y, z;
    } Vector3D;

    typedef struct TechniqueState
    {
        unsigned int blendEnable;
        unsigned int blendEquation;
        unsigned int blendFuncSfactor;
        unsigned int blendFuncDfactor;
        unsigned int cullFaceEnable;
        unsigned int depthMask;
        unsigned int depthTestEnable;
    }TechniqueState;

    enum glTFFileType { GLTF_JSON, GLTF_BINARY, GLTF_IMAGE, GLTF_GLSL };
    typedef struct {
        int x;
        int y;
        int width;
        int height;
    } glTFViewport;

    typedef struct {
        glTFFileType type;
        char *filename;
        char *buffer;
        int imageheight;
        int imagewidth;
        size_t size;
    } glTFFile;

    typedef struct {
        glTFFile**   files;   
        size_t       size;
        glTFViewport viewport;
        void*        renderer;
    } glTFHandle;
    int gltf_get_file_index_by_name(glTFHandle* handle, const char* name);
    glTFFile * gltf_get_json_string(glTFHandle* handle);
    typedef enum DataType
    {
        DataType_UNKNOW                         = 0x0,
        DataType_BYTE                           = 0x1400,
        DataType_UNSIGNED_BYTE                  = 0x1401,
        DataType_SHORT                          = 0x1402,
        DataType_UNSIGNED_SHORT                 = 0x1403,
        DataType_INT                            = 0x1404,
        DataType_UNSIGNED_INT                   = 0x1405,
        DataType_FLOAT                          = 0x1406,

        DataType_ARRAY_BUFFER                   = 0x8892,
        DataType_ELEMENT_ARRAY_BUFFER           = 0x8893,
        DataType_ARRAY_BUFFER_BINDING           = 0x8894,
        DataType_ELEMENT_ARRAY_BUFFER_BINDING   = 0x8895,

        DataType_FLOAT_VEC2                     = 0x8B50,
        DataType_FLOAT_VEC3                     = 0x8B51,
        DataType_FLOAT_VEC4                     = 0x8B52,
        DataType_INT_VEC2                       = 0x8B53,
        DataType_INT_VEC3                       = 0x8B54,
        DataType_INT_VEC4                       = 0x8B55,
        DataType_BOOL                           = 0x8B56,
        DataType_BOOL_VEC2                      = 0x8B57,
        DataType_BOOL_VEC3                      = 0x8B58,
        DataType_BOOL_VEC4                      = 0x8B59,
        DataType_FLOAT_MAT2                     = 0x8B5A,
        DataType_FLOAT_MAT3                     = 0x8B5B,
        DataType_FLOAT_MAT4                     = 0x8B5C,
        DataType_SAMPLER_2D                     = 0x8B5E,
        DataType_SAMPLER_CUBE                   = 0x8B60,
    } DataType_E;

    class Attribute
    {
    public:
        void setDataCount(unsigned int count);
        unsigned int getDataCount() const;

        void setDataType(DataType_E type);
        DataType_E getDataType() const;

        void setByteStride(unsigned int byteStride);
        unsigned int getByteStride() const;

        void setAttributeData(char* pValue, unsigned int length);
        const char* getAttributeData() const;

        void setAttributeMin(glm::vec3* pMin);
        glm::vec3* getAttributeMin();

        void setAttributeMax(glm::vec3* pMax);
        glm::vec3* getAttributeMax();

        Attribute();
        ~Attribute();
    private:
        unsigned int mByteStride;
        DataType_E mDataType;
        unsigned int count;
        char* pData;
    };

    typedef enum LightSourceType
    {
        LightSource_UNDEFINED     = 0x0,
        LightSource_DIRECTIONAL   = 0x1,
        LightSource_POINT         = 0x2,
        LightSource_SPOT          = 0x3,
    } LightSourceType_E;

    class Light
    {
    public:
        Light();
        ~Light();
        void setName(std::string Name);
        const std::string& getName();

        void setType(LightSourceType_E Type);
        const  LightSourceType_E& getLightSourceType() ;

        void setPosition(Vector3D Position);
        const Vector3D& getPosition();

        void setDirection(Vector3D Direction);
        const Vector3D& getDirection();

        void setAttenuationConstant(float AttenuationConstant);
        const float getAttenuationConstant();

        void setAttenuationLinear(float AttenuationLinear);
        const float getAttenuationLinear() ;

        void setAttenuationQuadratic(float AttenuationQuadratic);
        const float getAttenuationQuadratic() ;

        void setColor(Vector3D color);
        const Vector3D getColor() ;

        void setColorDiffuse(GLfloat* ColorDiffuse);
        const GLfloat* getColorDiffuse() ;

        void setColorSpecular(GLfloat* ColorSpecular);
        const GLfloat* getColorSpecular() ;

        void setColorAmbient(GLfloat* ColorAmbient);
        const GLfloat* getColorAmbient() ;

        void setPositions(GLfloat *);
        const GLfloat* getPositions();

    private:
        std::string mName;
        LightSourceType_E mType;
        Vector3D mPosition;
        Vector3D mDirection;
        float mAttenuationConstant;
        float mAttenuationLinear;
        float mAttenuationQuadratic;
        Vector3D mColor;

        GLfloat* mPositions;
        GLfloat* mColorDiffuse;
        GLfloat* mColorSpecular;
        GLfloat* mColorAmbient;
    };

    class Camera
    {
    public:
        void setXFov(float x);
        float getXFov();

        void setYFov(float y);
        float getYFov();

        void setFar(float f);
        float getFar();

        void setNear(float n);
        float getNear();

        void setAspectRatio(float a);
        float getAspectRatio();

        void setPosition(Vector3D& vPosition);
        Vector3D& getPosition();

        void setLookAt(Vector3D& vLookAt);
        Vector3D& getLookAt();

        void setUp(Vector3D& vUp);
        Vector3D& getUp();

        void getCameraMatrix() const {};

        Camera();
        ~Camera();

    private:
        std::string mName;
        Vector3D mPosition;
        Vector3D mUp;
        Vector3D mLookAt;
        float mXFov;
        float mYFov;
        float mNear;
        float mFar;
        float mAspectRatio;
    };

    class TechAttribute
    {
    public:
        void setAttributeName(std::string name);
        const std::string getAttributeName();

        void setAttributeIndex(std::string index);
        const std::string getAttributeIndex();
        TechAttribute();
        ~TechAttribute();
    private:
        std::string mAttributeName;
        std::string mAttributeIndex;
    };

    class TechUniform
    {
    public:
        void setUniformName(std::string name);
        const std::string& getUniformName();

        void setUniformIndex(std::string index);
        const std::string& getUniformIndex();
        TechUniform();
        ~TechUniform();
    private:
        std::string mUniformName;
        std::string mUniformIndex;
    };

    class Technique
    {
    public:
        void setProgramState(bool flag);
        bool getProgramState() const;

        void setProgramId(unsigned int id);
        unsigned int getProgramId() const;

        void setVertexShader(std::string shaderName);
        const std::string getVertexShader();

        void setFragmentShader(std::string shaderName);
        const std::string getFragmentShader();

        void insertTechAttribute(std::string key, class TechAttribute* pTechAttr);
        class TechAttribute* getTechAttribute(const std::string& key);
        unsigned int getTechAttributeSize() const;

        void setTechUniform(class TechUniform* pUniform);
        class TechUniform* getTechUniform(unsigned int index);
        unsigned int getTechUniformSize() const;

        TechniqueState* getTechState();

        void initTechnique(glTFHandle* pGltfHandle);
        void useTechnique();
        void freeTechnique();

        Technique();
        ~Technique();

    private:
        class ShaderProgram mShaderProg;

        std::map<std::string, class TechAttribute*> mTechAttrMap;
        std::vector<class TechUniform*> mTechUnifVec;

        unsigned int mProgramId;
        std::string mVertexShaderName;
        std::string mFragmentShaderName;
        bool mTechniqueState;
        TechniqueState* pState;
    };

    typedef enum TextureType
    {
        TextureType_NONE = 0x0,
        TextureType_DIFFUSE = 0x1,
        TextureType_SPECULAR = 0x2,
        TextureType_AMBIENT = 0x3,
        TextureType_EMISSIVE = 0x4,
        TextureType_HEIGHT = 0x5,
        TextureType_NORMALS = 0x6,
        TextureType_SHININESS = 0x7,
        TextureType_OPACITY = 0x8,
        TextureType_DISPLACEMENT = 0x9,
        TextureType_LIGHTMAP = 0xA,
        TextureType_REFLECTION = 0xB,
        TextureType_UNKNOWN = 0xC,
    } TextureType_E;

    class MaterialProperty
    {
    public:
        void setPropertyName(std::string key);
        const std::string& getPropertyName() const;

        void setImagePath(std::string index);
        const std::string& getImagePath() const;

        void setDataLength(unsigned int length);
        unsigned int getDataLength();

        void setDataType(DataType_E type);
        DataType_E getDataType() const;

        void setPropertyData(char* pValue);
        const char* getPropertyData() const;

        MaterialProperty();
        ~MaterialProperty();
    private:
        /** Specifies the name of the property (key) */
        std::string mPropertyName;

        /** Specifies their texture type.
        * If non-texture properties, this member is TextureType_NONE(0) 
        */
        TextureType_E mTextureType;

        /** Specifies image if it's exist. */ 
        std::string mImagePath;

        /** Specifies their data type.
        * If non-texture properties, this member is TextureType_NONE(0) 
        */
        DataType_E mDataType;

        /**	Size of the buffer pData is pointing to, in bytes.
        *  This value may not be 0.
        */
        unsigned int mDataLength;

        /**	Binary buffer to hold the property's value.
        * The size of the buffer is always mDataLength.
        */
        char* pData;
    };

    class Material
    {
    public:
        void setTechniqueId(std::string techniqueId);
        const std::string& getTechniqueId() const;

        void pushMaterialProper(class MaterialProperty* pProperty);
        const class MaterialProperty* getMaterialProper(unsigned int index);
        unsigned int getMaterialProperSize();

        Material();
        ~Material();
    private:
        std::string mTechniqueId;
        std::vector<class MaterialProperty*> mPropertyVec;
    };

    class Primitives
    {
    public:
        void setMaterialIndex(std::string materialIndex);
        const std::string getMaterialIndex() const;

        void setIndicesIndex(std::string indicesIndex);
        const std::string getIndicesIndex() const;

        void insertAttribute(std::string key, std::string value);
        const std::string getAttributeIndex(const std::string& key) const;
        unsigned int getAttributeSize() const;

        Primitives();
        ~Primitives();
    private:
        /** Index of attribute map. VerticeIndex NormalIndex TexCoordIndex*/
        std::map<std::string, std::string> mAttributeMap;

        /** Index of attribute IndexIndex*/
        std::string mIndicesIndex;

        /** Index of material map*/
        std::string mMaterialIndex;
    };

    class Mesh
    {
    public:
        void setMeshName(std::string name);
        const std::string getMeshName();

        void setPrimitiveVec(class Primitives* pPrim);
        const class Primitives* getPrimitiveVec(unsigned int index) const;
        unsigned int getPrimitiveVecSize() const;

        Mesh();
        ~Mesh();
    private:
        std::string mName;
        std::vector<class Primitives*> mPrimitiveVec;
    };

    typedef enum NodeType
    {
        NodeType_Init        = 0x0,
        NodeType_Node        = 0x1,
        NodeType_Mesh        = 0x2,
        NodeType_Camera      = 0x4,
        NodeType_Light       = 0x8,
    } NodeType_E;

    class Node
    {
    public:
        void setMatrix(glm::mat4* matrix);
        glm::mat4* getMatrix() const;

        void setNodeType(NodeType_E type);
        NodeType_E getNodeType() const;

        void setNodeName(std::string name);
        const std::string getNodeName() const;

        void setLightIndex(std::string index);
        const std::string getLightIndex() const;

        void setCameraIndex(std::string index);
        const std::string getCameraIndex();

        void pushMeshIndex(std::string& index);
        const std::string getMeshIndex(unsigned int index) const;
        unsigned int getMeshIndexSize() const;

        void pushChildNode(class Node* pNode);
        const class Node* getChildNode(unsigned int index) const;
        class Node* getChildrenNode(unsigned int index);  //todo
        unsigned int getChildNodeSize() const;

        void setParentNode(class Node* pParent);
        const class Node* getParentNode();

        void setLightFlag();
        const int getLightFlag();

        inline bool hasChildren() const
        {
            return mChildrenVec.size() > 0 ? true : false;
        }

        inline bool hasMesh() const
        {
            return mMesheIndexVec.size() > 0 ? true : false;
        }

        void setFlagCamera(bool bECamera)
        {
            bFlagCamera = bECamera;
        }

        bool getFlagCamera() const
        {
            return bFlagCamera;
        }

        Node();
        ~Node();
    private:
        /** Whether analysis the light node*/
        int LightFlag;
        /** There are four types of node, Maybe it's a mesh array, or a camera, light, 
        *   also it could be just a node, children node are others type.
        */
        NodeType_E mNodeType;

        /** The name of this node. */
        std::string mName;

        /** The transformation relative to the node's parent. */
        glm::mat4* pMatrix;

        /** Parent node. NULL if this node is the root node. */
        class Node* pParent;

        /** The child nodes of this node. NULL if mNumChildren is 0. */
        std::vector<class Node*> mChildrenVec;

        /** The meshes of this node. Each entry is an index into the mesh */
        std::vector<std::string> mMesheIndexVec;

        /** The camera of this node. Each entry is an index into the camera */
        std::string mCameraIndex;

        /** The light of this node. Each entry is an index into the light */
        std::string mLightIndex;

        bool bFlagCamera;
    };

    class Scene
    {
    public:
        void insertMeshMap(std::string key, class Mesh* pMesh);
        class Mesh* findMesh(const std::string& key);

        void insertMaterialMap(std::string key, class Material* pMaterial);
        class Material* findMaterial(const std::string& key);

        void insertTechniqueMap(std::string key, class Technique* pTechnique);
        class Technique* findTechnique(const std::string& key);

        void insertAttributeMap(std::string key, class Attribute* pAttribute);
        const class Attribute* findAttribute(const std::string& key);

        void setVertexMax(float x, float y, float z);
        glm::vec3& getVertexMax();
        void setVertexMin(float x, float y, float z);
        glm::vec3& getVertexMin();

        void insertLightMap(std::string key, class Light* pLight);
        class Light* findLight(const std::string& key);
        
        std::string GetLightIndex();
        int getLight();
        glm::mat4* findLightTransformation();

        void setRootNode(class Node* pRootNode);
        const class Node* getRootNode() const;
        void pushNode(class Node* pNode);
        const class Node* getNode(unsigned int index);
        unsigned int getNodeSize();

        void insertCameraMap(std::string key, class Camera* pCamera);
        class Camera* findCamera(const std::string& key);
        const std::string getCameraIndex() const;
        const glm::mat4* getCameraMatrix() const;

        char* getBuffer(unsigned int length);
		bool  setBuffer(const std::string& binName, unsigned int length);

        bool loadTexture(std::string imagePath);
        Texture* findTexture(const std::string& key);
        void insertTextureMap(std::string key, class Texture* pTexture);

        void setGltfHandle(glTFHandle* handle);
        glTFHandle* getGltfHandle();
        glTFFile* getGltfFileByFileName(std::string fileName);

        Scene();
        ~Scene();
    private:
        template<typename M> void freeMap(M& aMap);

        void freeTexture();

        /** The root node of the hierarchy.
        * There will always be at least the root node if the parser
        * was successful done(and no special flags have been set).
        */
        class Node* pRootNode;

        std::vector<class Node*> mNodeVec;

        /** The map of meshes.
        * Use the indices given in the Node structure to access
        * this array. The array is mNumMeshes in size.
        */
        std::map<std::string, class Mesh*> mMeshMap;

        /** The map of material. */
        std::map<std::string, class Material*> mMaterialMap;

        /** The map of light sources. */
        std::map<std::string, class Light*> mLightMap;

        /** The map of cameras.
        * The first camera in the array (if existing)
        * is the default camera view into the scene.
        */
        std::map<std::string, class Camera*> mCameraMap;

        /** The map of attribute. */
        std::map<std::string, class Attribute*> mAttributeMap;

        glm::vec3 mVertexMax;
        glm::vec3 mVertexMin;

        /** The map of technique. */
        std::map<std::string, class Technique*> mTechniqueMap;

        /** Store the data of binary */
        char* pBuffer;

        glTFHandle* pGltfHandle;
	    std::map<std::string, class Texture*> mTexturesMap;
    };
}
#endif