#pragma once

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

struct ID3D11Buffer;

namespace Framework
{
    struct Polygon2D;    //! Model2D.h
    class Textur;        //! Textur.h
    class Model2DData;   //! Model2D.h
    class DXBuffer;      //! DXBuffer.h
    class Render3D;      //! Render3D.h
    class Model3DTextur; //! Model3D.h
    class Model3DList;   //! Model3DList.h
    class Welt3D;        //! Welt3D.h
    class DXBuffer;
    class Shader;
    class GraphicsApi;
    class M3Datei;

    //! Repr�sentiert einen Knochen eines 3D Models. Kann annimiert werden
    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 Der Knochen, der hinzugef�gt werden soll
        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;
    };

    //! Repr�sentiert alle Knochen eines Models, mit denen es Annimiert werden
    //! kann
    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 M3Datei;
    };

    //! Eine struktor um f�r eine Ecke eines 3D Models die Raum Position, die
    //! Textur Koordinaten und den zugeh�rigen Knochen speichert
    struct Vertex3D
    {
        Vec3<float>
            pos; //! Die Position der Ecke basierend zur Position des Knochens
        Vec2<float> tPos;   //! Die Textur Koordinaten der Ecke
        Vec3<float> normal; //! Die Normale (zeigt nach au�en und steht
                            //! senkrecht auf der Oberfl�che des models)
        int knochenId; //! Die Id des Knochens, mit dem sich die Ecke bei einer
                       //! Annimation mitbewegt
        int id;        //! Der index des vertexes im vertex buffer
    };

    //! Eine Struktur, die alle Dreiecke eines 3D Polygons speichert
    struct Polygon3D
    {
        int* indexList; //! Die Liste mit den IDs der Ecken
        int indexAnz;   //! Die L�nge der Liste mit den Ids der Ecken

        //! Konstruktor
        DLLEXPORT Polygon3D();
        //! Destruktor
        DLLEXPORT ~Polygon3D();
    };

    //! Speichert alle Geometrischen Daten eines Modells, also
    //! Raum - und Textur Koordinaten und Knochenzugeh�rigkeit aller Eckpunkte
    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:
        //! Konstruktor
        DLLEXPORT Model3DData(
            DXBuffer* dxVertexBuffer, DXBuffer* dxIndexBuffer, int id);
        //! Destruktor
        DLLEXPORT ~Model3DData();
        //! updates the DX Buffer gpu memory if changed
        DLLEXPORT void updateGPUMemory();
        //! L�scht alle Model daten
        DLLEXPORT void clearModel();
        //! Berechnet die normalen f�r die Eckpunkte des Modells
        DLLEXPORT void calculateNormals();
        //! Erstellt einen buffer f�r alle polygon indizes
        DLLEXPORT void buildIndexBuffer();
        //! Setzt den Zeiger auf ein standartm��ig verwendete Skelett
        //! \param s Das Skelett, das verwendet werden soll
        DLLEXPORT void setSkelettZ(Skeleton* s);
        //! Setzt einen Zeiger auf eine Liste mit allen Vertecies des Models
        //! \param vertexList Ein Array mit Vertecies
        //! \param anz Die Anzahl der Vertecies im Array
        DLLEXPORT void setVertecies(Vertex3D* vertexList, int anz);
        //! F�gt ein Polygon zum Model hinzu
        //! \param polygon Das Polygon, das hinzugef�gt erden soll
        DLLEXPORT void addPolygon(Polygon3D* polygon);
        //! Git den Factor an, mit dem das umgebungslicht (textur farbe)
        //! multipliziert wird \param f der neue Faktor (von 0 bis 1, ambient +
        //! specular + diffuse = 1)
        DLLEXPORT void setAmbientFactor(float f);
        //! Git den Factor an, mit dem die Lichtfarbe von Lichtquellen
        //! multipliziert wird \param f der neue Faktor (von 0 bis 1, ambient +
        //! specular + diffuse = 1)
        DLLEXPORT void setDiffusFactor(float f);
        //! Git den Factor an, mit dem die Reflektion von Lichtquellen
        //! multipliziert wird \param f der neue Faktor (von 0 bis 1, ambient +
        //! specular + diffuse = 1)
        DLLEXPORT void setSpecularFactor(float f);
        //! Konvertiert ein 2d Model zu 3D
        //! \param model Das 2d Model, das zu 3d konvertiert werden soll
        //! \param z Die z koordinate aller punkte des Models
        DLLEXPORT void copyModel2D(Model2DData* model, float z);
        //! Entfernt ein Polygon
        //! \param index Der Index des Polygons
        DLLEXPORT void removePolygon(int index);
        //! Berechnet die Matrizen der Knochen des Standart Skeletts
        //! \param modelMatrix Die Matrix, die das Skelett in den Raum der Welt
        //! transformiert \param matBuffer Ein Array von Matrizen, der durch die
        //! Knochen Matrizen gef�llt wird \param scaleFactor Die Skallierung des
        //! Modells \param kamMatrix Die vereiniegung der view und projektions
        //! Matrizen \return gibt die Anzahl der verwendeten Matrizen zur�ck. 0,
        //! falls kein Standart Skelett gesetzt wurde
        int kalkulateMatrix(const Mat4<float>& modelMatrix,
            Mat4<float>* matBuffer,
            float scaleFactor,
            const Mat4<float>& kamMatrix) const;
        //! Gibt die Anzahl an Polygonen zur�ck
        DLLEXPORT int getPolygonAnzahl() const;
        //! Gibt ein bestimmtes Polygon zur�ck
        //! \param index Der Index des Polygons
        DLLEXPORT Polygon3D* getPolygon(int index) const;
        //! Gibt einen Iterator zur�ck, mit dem sich die Polygons auflisten
        //! lassen
        DLLEXPORT ArrayIterator<Polygon3D*> getPolygons() const;
        //! Gibt den radius einer Kugel zur�ck, die das gesammte Model
        //! umschlie�t
        DLLEXPORT float getRadius() const;
        //! Gibt die Id der Daten zur�ck, wenn sie in einer Model3DList
        //! registriert wurden. (siehe Framework::zM3DRegister())
        DLLEXPORT int getId() const;
        //! Git den Factor an, mit dem das umgebungslicht (textur farbe)
        //! multipliziert wird
        DLLEXPORT float getAmbientFactor() const;
        //! Git den Factor an, mit dem die Lichtfarbe von Lichtquellen
        //! multipliziert wird
        DLLEXPORT float getDiffusFactor() const;
        //! Git den Factor an, mit dem die Reflektion von Lichtquellen
        //! multipliziert wird
        DLLEXPORT float getSpecularFactor() const;
        //! Gibt eine Kopie des Skeletts zur�ck, welches f�r annimationen
        //! verwendet werden kann
        DLLEXPORT Skeleton* copySkelett() const;
        //! Gibt die Anzahl an Vertices zur�ck
        DLLEXPORT int getVertexAnzahl() const;
        //! Gibt einen Buffer mit allen Vertecies des Models zur�ck
        DLLEXPORT const Vertex3D* zVertexBuffer() const;
        //! Gibt eine refferenz auf den beginn des indexBuffers zur�ck
        DLLEXPORT const int* getIndexBuffer() const;
        //! Gibt eine die Anzahl der indizes im indexBuffer zur�ck
        DLLEXPORT int getIndexCount() const;
        //! Gibt den Index buffer zur�ck;
        DLLEXPORT DXBuffer* zDXIndexBuffer() const;
        //! Gibt den Vertex buffer zur�ck;
        DLLEXPORT DXBuffer* zDXVertexBuffer() const;
        //! gibt den minnimalen Punkt der Bounding box des Models zur�ck
        DLLEXPORT Vec3<float> getMinPos() const;
        //! gibt den maximalen Punkt der bounding box des Mopdels zur�ck
        DLLEXPORT Vec3<float> getMaxPos() const;
    };

    //! Speichert eine Liste mit Texturen und f�r welche Polygone welche Textur
    //! benutzt werden soll
    class Model3DTextur : public virtual ReferenceCounter
    {
    private:
        Textur** textures;
        int textureCount;

    public:
        //! Konstruktor
        DLLEXPORT Model3DTextur();
        //! Destruktor
        DLLEXPORT ~Model3DTextur();
        //! Legt fest, welche Textur f�r welches Polygon ist
        //! \param pI Der Index des Polygons
        //! \param txt Die Textur des Polygons
        DLLEXPORT void setPolygonTextur(int pI, Textur* txt);
        //! Gibt einen Zeiger auf die Textur eines Polygons zur�ck ohne erh�hten
        //! Reference Counter \param i Der Index des Polygons
        DLLEXPORT Textur* zPolygonTextur(int i) const;
    };

    //! Eine Zeichnung des 3D Frameworks, die ein 3D Model mit Textur und
    //! Animation darstellen kann
    class Model3D : public Zeichnung3D
    {
    protected:
        Skeleton* skelett;
        Model3DData* model;
        Model3DTextur* textur;
        float ambientFactor;
        float diffusFactor;
        float specularFactor;

    public:
        //! Konstruktor
        DLLEXPORT Model3D();
        //! Destruktor
        DLLEXPORT virtual ~Model3D();
        //! Setzt die Daten des Models
        //! \param data Die Daten
        DLLEXPORT void setModelDaten(Model3DData* data);
        //! Setzt die zum Zeichnen zu benutzenden Texturen
        //! \param txt Ein Liste mit Texturen zu den verschiedenen Polygonen
        //! zugeordnet
        DLLEXPORT void setModelTextur(Model3DTextur* txt);
        //! Git den Factor an, mit dem das umgebungslicht (textur farbe)
        //! multipliziert wird \param f der neue Faktor (von 0 bis 1, ambient +
        //! specular + diffuse = 1)
        DLLEXPORT void setAmbientFactor(float f);
        //! Git den Factor an, mit dem die Lichtfarbe von Lichtquellen
        //! multipliziert wird \param f der neue Faktor (von 0 bis 1, ambient +
        //! specular + diffuse = 1)
        DLLEXPORT void setDiffusFactor(float f);
        //! Git den Factor an, mit dem die Reflektion von Lichtquellen
        //! multipliziert wird \param f der neue Faktor (von 0 bis 1, ambient +
        //! specular + diffuse = 1)
        DLLEXPORT void setSpecularFactor(float f);
        //! Errechnet die Matrizen aller Knochen des Skeletts des Models
        //! \param viewProj Die miteinander multiplizierten Kameramatrizen
        //! \param matBuffer Ein Array mit Matrizen, der gef�llt werden soll
        //! \return Die Anzahl der Matrizen, die das Model ben�tigt
        DLLEXPORT int errechneMatrizen(
            const Mat4<float>& viewProj, Mat4<float>* matBuffer) override;
        //! Verarbeitet die vergangene Zeit
        //! \param tickval Die zeit in sekunden, die seit dem letzten Aufruf der
        //! Funktion vergangen ist \return true, wenn sich das Objekt ver�ndert
        //! hat, false sonnst.
        DLLEXPORT virtual bool tick(double tickval) override;
        //! zum aktualisieren der shader daten
        DLLEXPORT virtual void beforeRender(
            GraphicsApi* api, Shader* zVertexShader, Shader* zPixelShader);
        DLLEXPORT virtual void afterRender(
            GraphicsApi* api, Shader* zVertexShader, Shader* zPixelShader);
        //! Gibt die Textur zur�ck
        DLLEXPORT Model3DTextur* getTextur();
        //! Gibt die Textur zur�ck (ohne erh�hten Reference Counter)
        DLLEXPORT Model3DTextur* zTextur();
        //! Gibt die ModelDaten zur�ck
        DLLEXPORT Model3DData* getModelData();
        //! Gibt die ModelDaten zur�ck (ohne erh�hten Reference Counter)
        DLLEXPORT Model3DData* zModelData();
        //! pr�ft, ob ein Strahl dieses Objekt trifft
        //! \param point der startpunkt des Strahls in Weltkoordinaten
        //! \param dir die Richtung des Strahls in Weltkoordinaten
        //! \param maxSqDist Die maximale quadratische distanz die erlaubt ist
        //! \param pId die Id des Polygons, zu dem der Schnittpunkt geh�rt
        //! \return den quadratischen Abstand des Schnittpunktes zum Ursprung
        //! des Strahls oder -1, wenn kein schnittpunkt existiert
        DLLEXPORT virtual float traceRay(const Vec3<float>& point,
            const Vec3<float>& dir,
            float maxSqDist,
            int& pId) const;
        //! berechnet die Farbe des Schnittpunktes deines Strahls
        //! \param point der startpunkt des Strahls in Weltkoordinaten
        //! \param dir die Richtung des Strahls in Weltkoordinaten
        //! \param zWelt die Welt, aus der der Strahl kommt
        //! \return die Farbe des Schnittpunktes
        DLLEXPORT virtual int traceRay(
            Vec3<float>& point, Vec3<float>& dir, int pId, Welt3D* zWelt) const;
        //! Gibt die Id der Daten zur�ck, wenn sie in einer Model3DList
        //! registriert wurden. (siehe Framework::zM3DRegister())
        DLLEXPORT int getDatenId() const;
        //! Git den Factor an, mit dem das umgebungslicht (textur farbe)
        //! multipliziert wird
        DLLEXPORT float getAmbientFactor() const;
        //! Git den Factor an, mit dem die Lichtfarbe von Lichtquellen
        //! multipliziert wird
        DLLEXPORT float getDiffusFactor() const;
        //! Git den Factor an, mit dem die Reflektion von Lichtquellen
        //! multipliziert wird
        DLLEXPORT float getSpecularFactor() const;
        //! Gibt die Anzahl an Vertices zur�ck
        DLLEXPORT int getVertexAnzahl() const;
        //! Gibt einen Buffer mit allen Vertecies des Models zur�ck
        DLLEXPORT const Vertex3D* zVertexBuffer() const;
        //! Gibt true zur�ck wenn ein bestimmtes polygon gezeichnet werden muss
        DLLEXPORT virtual bool needRenderPolygon(int index);
        DLLEXPORT virtual Textur* zEffectTextur();
        DLLEXPORT virtual float getEffectPercentage();
    };
} // namespace Framework