#pragma once

#include "Array.h"
#include "Mat4.h"
#include "Vec2.h"
#include "Drawing3D.h"

struct ID3D11Buffer;

namespace Framework
{
    struct Polygon2D;    //! Model2D.h
    class Texture;        //! Texture.h
    class Model2DData;   //! Model2D.h
    class DXBuffer;      //! DXBuffer.h
    class Render3D;      //! Render3D.h
    class Model3DTexture; //! Model3D.h
    class Model3DList;   //! Model3DList.h
    class World3D;        //! World3D.h
    class DXBuffer;
    class Shader;
    class GraphicsApi;
    class M3File;

    //! Represents a bone of a 3D model. Can be animated
    class Bone
    {
    private:
        Vec3<float> pos;
        Vec3<float> rot;
        Bone* sibling;
        Bone* child;
        int id;

    public:
        //! Constructor
        //! \param id the id of the bone
        DLLEXPORT Bone(int id);
        //! Destructor
        DLLEXPORT ~Bone();
        //! set the position of the bone relative to the parent bone
        //! \param pos the position
        DLLEXPORT void setPosition(const Vec3<float>& pos);
        //! Set the rotation of the bone relative to the parent bone
        //! \param rot thr rotation
        DLLEXPORT void setRotation(const Vec3<float>& rot);
        //! add a sibling bone to this bone that shares the same parent bone
        //! \param b The bone to add
        void addSiblingBone(Bone* b);
        //! add a child bone to a specific child bone
        //! \param id the id of the bone the new bone should be a child of
        //! \param b the bone that should be added
        //! \return true if the bone was added, false if the bone with the given
        //! parent id was not found
        DLLEXPORT bool addChildBone(int id, Bone* b);
        //! calculates the matrixes of this bone, all child bones and sibling
        //! bones
        //! \param elternMat the already calculated matrix of the parent bone
        //! \param matBuffer the array to store the calculated matrixes
        //! \param scaleFactor the scaling of the object
        //! \param camMatrix the view-projection matrix of the used camera
        DLLEXPORT void calculateMatrix(const Mat4<float>& elternMat,
            Mat4<float>* matBuffer,
            float scaleFactor,
            const Mat4<float>& camMatrix);
        //! \return the first sibling bone
        DLLEXPORT Bone* zFirstSibling() const;
        //! \return the first child bone
        DLLEXPORT Bone* zFirstChild() const;
        //! returns a copy of this bone with copies of all child and
        //! sibling bones
        DLLEXPORT Bone* copyBone() const;
        //! \return the id of this bone
        DLLEXPORT int getId() const;
        //! \return the rotation of this bone
        DLLEXPORT Vec3<float> getRotation() const;
        //! \return the position of this bone
        DLLEXPORT Vec3<float> getPosition() const;
        //! \return the radius of this bone
        float getRadius() const;
    };

    //! Represents all bones of a model that can be used for animation
    class Skeleton : public virtual ReferenceCounter
    {
    private:
        Bone* rootBone;
        int nextId;

        Bone* zBone(Bone* zCurrent, int id) const;

    public:
        //! Constructor
        DLLEXPORT Skeleton();
        //! Destructor
        DLLEXPORT ~Skeleton();
        //! add a bone to the sceleton
        //! \param pos the position of the bone
        //! \param rot the rotation of the bone
        //! \param the id of the parent bone where the new bone should be added
        //! as a child
        //! \return the id of the added bone or -1 if the bone could not be
        //! added
        DLLEXPORT int addBone(
            Vec3<float> pos, Vec3<float> rot, int parentId = -1);
        //! calculates the matrices of all bones in this sceleton
        //! \param modelMatrix the already calculated matrix of the used 3d
        //! model \param matBuffer the array to store the calculated matrixes
        //! \param scaleFactor the scaling of the object
        //! \param camMatrix the view-projection matrix of the used camera
        DLLEXPORT int calculateMatrix(const Mat4<float>& modelMatrix,
            Mat4<float>* matBuffer,
            float scaleFactor,
            const Mat4<float>& camMatrix);
        //! \return the radius of the sceleton
        DLLEXPORT float getRadius() const;
        //! \return the root bone of the sceleton
        DLLEXPORT Bone* zRootBone() const;
        //! \return the bone with a specific id
        DLLEXPORT Bone* zBone(int id) const;
        //! \return a deep copy of the sceleton
        DLLEXPORT Skeleton* copySceleton() const;
        //! \return the next id for a bone ther can be only MAX_KNOCHEN_ANZ
        //! bones in a sceleton. if the sceleton is full -1 is returned
        DLLEXPORT int getNextBoneId() const;

        friend M3File;
    };

    //! A structure that stores the spatial position, texture coordinates,
    //! and associated bone for a vertex of a 3D model
    struct Vertex3D
    {
        Vec3<float>
            pos; //! The position of the vertex based on the bone's position
        Vec2<float> tPos;   //! The texture coordinates of the vertex
        Vec3<float> normal; //! The normal (points outward and is perpendicular
                            //! to the model's surface)
        int knochenId; //! The ID of the bone with which the vertex moves
                       //! during animation
        int id;        //! The index of the vertex in the vertex buffer
    };

    //! A structure that stores all triangles of a 3D polygon
    struct Polygon3D
    {
        int* indexList; //! The list of vertex IDs
        int indexAnz;   //! The length of the list of vertex IDs

        //! Constructor
        DLLEXPORT Polygon3D();
        //! Destructor
        DLLEXPORT ~Polygon3D();
    };

    //! Stores all geometric data of a model, including spatial and texture
    //! coordinates and bone associations of all vertices
    class Model3DData : public virtual ReferenceCounter
    {
    private:
        Skeleton* skelett;
        Vertex3D* vertexList;
        int vertexCount;
        Array<Polygon3D*>* polygons;
        float ambientFactor;
        float diffusFactor;
        float specularFactor;
        float radius;
        int* indexBuffer;
        int indexCount;
        DXBuffer* dxIndexBuffer;
        DXBuffer* dxVertexBuffer;
        Vec3<float> minPos;
        Vec3<float> maxPos;
        int id;

    public:
        //! Constructor
        DLLEXPORT Model3DData(
            DXBuffer* dxVertexBuffer, DXBuffer* dxIndexBuffer, int id);
        //! Destructor
        DLLEXPORT ~Model3DData();
        //! updates the DX Buffer gpu memory if changed
        DLLEXPORT void updateGPUMemory();
        //! Deletes all model data
        DLLEXPORT void clearModel();
        //! Calculates the normals for the model's vertices
        DLLEXPORT void calculateNormals();
        //! Creates a buffer for all polygon indices
        DLLEXPORT void buildIndexBuffer();
        //! Sets a pointer to a default skeleton to use
        //! \param s The skeleton to use
        DLLEXPORT void setSkeletonZ(Skeleton* s);
        //! Sets a pointer to a list of all vertices of the model
        //! \param vertexList An array of vertices
        //! \param anz The number of vertices in the array
        DLLEXPORT void setVertecies(Vertex3D* vertexList, int anz);
        //! Adds a polygon to the model
        //! \param polygon The polygon to add
        DLLEXPORT void addPolygon(Polygon3D* polygon);
        //! Sets the factor by which the ambient light (texture color)
        //! is multiplied \param f The new factor (from 0 to 1, ambient +
        //! specular + diffuse = 1)
        DLLEXPORT void setAmbientFactor(float f);
        //! Sets the factor by which the light color of light sources
        //! is multiplied \param f The new factor (from 0 to 1, ambient +
        //! specular + diffuse = 1)
        DLLEXPORT void setDiffusFactor(float f);
        //! Sets the factor by which the reflection of light sources
        //! is multiplied \param f The new factor (from 0 to 1, ambient +
        //! specular + diffuse = 1)
        DLLEXPORT void setSpecularFactor(float f);
        //! Converts a 2D model to 3D
        //! \param model The 2D model to convert to 3D
        //! \param z The z coordinate of all points of the model
        DLLEXPORT void copyModel2D(Model2DData* model, float z);
        //! Removes a polygon
        //! \param index The index of the polygon
        DLLEXPORT void removePolygon(int index);
        //! Calculates the matrices of the default skeleton's bones
        //! \param modelMatrix The matrix that transforms the skeleton into
        //! world space \param matBuffer An array of matrices filled with
        //! bone matrices \param scaleFactor The scaling of the model
        //! \param kamMatrix The combined view and projection matrices
        //! \return The number of matrices used. 0 if no default skeleton was set
        int kalkulateMatrix(const Mat4<float>& modelMatrix,
            Mat4<float>* matBuffer,
            float scaleFactor,
            const Mat4<float>& kamMatrix) const;
        //! Returns the number of polygons
        DLLEXPORT int getPolygonCount() const;
        //! Returns a specific polygon
        //! \param index The index of the polygon
        DLLEXPORT Polygon3D* getPolygon(int index) const;
        //! Returns an iterator to list the polygons
        DLLEXPORT ArrayIterator<Polygon3D*> getPolygons() const;
        //! Returns the radius of a sphere that encloses the entire model
        DLLEXPORT float getRadius() const;
        //! Returns the ID of the data if registered in a Model3DList.
        //! (see Framework::zM3DRegister())
        DLLEXPORT int getId() const;
        //! Returns the factor by which ambient light (texture color)
        //! is multiplied
        DLLEXPORT float getAmbientFactor() const;
        //! Returns the factor by which the light color of light sources
        //! is multiplied
        DLLEXPORT float getDiffusFactor() const;
        //! Returns the factor by which the reflection of light sources
        //! is multiplied
        DLLEXPORT float getSpecularFactor() const;
        //! Returns a copy of the skeleton that can be used for animations
        DLLEXPORT Skeleton* copySkeleton() const;
        //! Returns the number of vertices
        DLLEXPORT int getVertexCount() const;
        //! Returns a buffer with all vertices of the model
        DLLEXPORT const Vertex3D* zVertexBuffer() const;
        //! Returns a reference to the start of the index buffer
        DLLEXPORT const int* getIndexBuffer() const;
        //! Returns the number of indices in the index buffer
        DLLEXPORT int getIndexCount() const;
        //! Returns the index buffer
        DLLEXPORT DXBuffer* zDXIndexBuffer() const;
        //! Returns the vertex buffer
        DLLEXPORT DXBuffer* zDXVertexBuffer() const;
        //! Returns the minimum point of the model's bounding box
        DLLEXPORT Vec3<float> getMinPos() const;
        //! Returns the maximum point of the model's bounding box
        DLLEXPORT Vec3<float> getMaxPos() const;
    };

    //! Stores a list of textures and which texture to use for which polygon
    class Model3DTexture : public virtual ReferenceCounter
    {
    private:
        Texture** textures;
        int textureCount;

    public:
        //! Constructor
        DLLEXPORT Model3DTexture();
        //! Destructor
        DLLEXPORT ~Model3DTexture();
        //! Sets which texture is for which polygon
        //! \param pI The index of the polygon
        //! \param txt The texture of the polygon
        DLLEXPORT void setPolygonTexture(int pI, Texture* txt);
        //! Returns a pointer to the texture of a polygon without increased
        //! reference counter \param i The index of the polygon
        DLLEXPORT Texture* zPolygonTexture(int i) const;
    };

    //! A drawing of the 3D framework that can display a 3D model with
    //! texture and animation
    class Model3D : public Drawable3D
    {
    protected:
        Skeleton* skelett;
        Model3DData* model;
        Model3DTexture* texture;
        float ambientFactor;
        float diffusFactor;
        float specularFactor;

    public:
        //! Constructor
        DLLEXPORT Model3D();
        //! Destructor
        DLLEXPORT virtual ~Model3D();
        //! Sets the model data
        //! \param data The data
        DLLEXPORT void setModelData(Model3DData* data);
        //! Sets the textures to use for drawing
        //! \param txt A list of textures assigned to the different polygons
        DLLEXPORT void setModelTextur(Model3DTexture* txt);
        //! Sets the factor by which the ambient light (texture color)
        //! is multiplied \param f The new factor (from 0 to 1, ambient +
        //! specular + diffuse = 1)
        DLLEXPORT void setAmbientFactor(float f);
        //! Sets the factor by which the light color of light sources
        //! is multiplied \param f The new factor (from 0 to 1, ambient +
        //! specular + diffuse = 1)
        DLLEXPORT void setDiffusFactor(float f);
        //! Sets the factor by which the reflection of light sources
        //! is multiplied \param f The new factor (from 0 to 1, ambient +
        //! specular + diffuse = 1)
        DLLEXPORT void setSpecularFactor(float f);
        //! Calculates the matrices of all bones in the model's skeleton
        //! \param viewProj The multiplied camera matrices
        //! \param matBuffer An array of matrices to fill
        //! \return The number of matrices the model requires
        DLLEXPORT int calculateMatrices(
            const Mat4<float>& viewProj, Mat4<float>* matBuffer) override;
        //! Processes elapsed time
        //! \param tickval The time in seconds since the last call of this
        //! function \return true if the object changed, false otherwise.
        DLLEXPORT virtual bool tick(double tickval) override;
        //! For updating shader data
        DLLEXPORT virtual void beforeRender(
            GraphicsApi* api, Shader* zVertexShader, Shader* zPixelShader);
        DLLEXPORT virtual void afterRender(
            GraphicsApi* api, Shader* zVertexShader, Shader* zPixelShader);
        //! Returns the texture
        DLLEXPORT Model3DTexture* getTexture();
        //! Returns the texture (without increased reference counter)
        DLLEXPORT Model3DTexture* zTexture();
        //! Returns the model data
        DLLEXPORT Model3DData* getModelData();
        //! Returns the model data (without increased reference counter)
        DLLEXPORT Model3DData* zModelData();
        //! Checks whether a ray hits this object
        //! \param point The starting point of the ray in world coordinates
        //! \param dir The direction of the ray in world coordinates
        //! \param maxSqDist The maximum allowed squared distance
        //! \param pId The ID of the polygon the intersection belongs to
        //! \return The squared distance of the intersection to the ray's
        //! origin, or -1 if no intersection exists
        DLLEXPORT virtual float traceRay(const Vec3<float>& point,
            const Vec3<float>& dir,
            float maxSqDist,
            int& pId) const;
        //! Calculates the color of a ray's intersection point
        //! \param point The starting point of the ray in world coordinates
        //! \param dir The direction of the ray in world coordinates
        //! \param zWorld The world from which the ray originates
        //! \return The color of the intersection point
        DLLEXPORT virtual int traceRay(
            Vec3<float>& point, Vec3<float>& dir, int pId, World3D* zWorld) const;
        //! Returns the ID of the data if registered in a Model3DList.
        //! (see Framework::zM3DRegister())
        DLLEXPORT int getDataId() const;
        //! Returns the factor by which ambient light (texture color)
        //! is multiplied
        DLLEXPORT float getAmbientFactor() const;
        //! Returns the factor by which the light color of light sources
        //! is multiplied
        DLLEXPORT float getDiffusFactor() const;
        //! Returns the factor by which the reflection of light sources
        //! is multiplied
        DLLEXPORT float getSpecularFactor() const;
        //! Returns the number of vertices
        DLLEXPORT int getVertexCount() const;
        //! Returns a buffer with all vertices of the model
        DLLEXPORT const Vertex3D* zVertexBuffer() const;
        //! Returns true if a specific polygon needs to be drawn
        DLLEXPORT virtual bool needRenderPolygon(int index);
        DLLEXPORT virtual Texture* zEffectTexture();
        DLLEXPORT virtual float getEffectPercentage();
    };
} // namespace Framework