#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 Animation3D;   //! Animation3D.h
    class Welt3D;        //! Welt3D.h
    class DXBuffer;
    class Shader;
    class GraphicsApi;

    //! Repräsentiert einen Knochen eines 3D Models. Kann annimiert werden
    class Knochen
    {
    private:
        Vec3<float> pos;
        Vec3<float> winkel;
        Knochen* geschwister;
        Knochen* kinder;
        int id;

    public:
        //! Konstruktor
        DLLEXPORT Knochen(int id);
        //! Destruktor
        DLLEXPORT ~Knochen();
        //! Setzt die Position des Knochens relativ zum Model Ursprung
        //! \param pos Die Position
        DLLEXPORT void setPosition(Vec3<float>& pos);
        //! Setzt die Drehung des Knochens relativ zum Model Ursprung
        //! \param winkel Ein Vektor der die Drehung um die verschiedenen Achsen
        //! als Komponenten hat
        DLLEXPORT void setDrehung(Vec3<float>& winkel);
        //! Fügt dem Knochen ein Geschwister Knochen hinzu
        //! \param k Der Knochen, der hinzugefügt werden soll
        void addGeschwisterKnochen(Knochen* k);
        //! Fügt einem bestimmten Knochen ein Kind Knochen hinzu
        //! \param id Die id des Knochens, wo der Knochen als Kind hinzugefügt
        //! werden soll \param k Der Knochen, der hinzugefügt werden soll
        DLLEXPORT void addKind(int id, Knochen* k);
        //! Berechnet die Matrizen des Knochen und die von all seinen
        //! Geschwister Knochen und Kind Knochen \param elternMat Die fertig
        //! berechnete Matrix des Elternknochens \param matBuffer Ein Array, in
        //! dem alle berechneten Matrizen gespeichert werden sollen \param
        //! scaleFactor Die skallierung des Objektes \param kamMatrix Die
        //! vereiniegung der view und projektions Matrizen
        DLLEXPORT void kalkulateMatrix(Mat4<float>& elternMat,
            Mat4<float>* matBuffer,
            float scaleFactor,
            Mat4<float>& kamMat);
        //! Gibt den ersten Geschwisterknochen zurück
        DLLEXPORT Knochen* zGeschwister() const;
        //! Gibt den ersten KindKnochen zurück
        DLLEXPORT Knochen* zKind() const;
        //! Kopiert den Knochen mit allen Geschwister Knochen und Kind Knochen
        DLLEXPORT Knochen* kopiereKnochen() const;
        //! Gibt die Id des Knochens zurück
        DLLEXPORT int getId() const;
        //! Gibt die Drehung des Knochens zurück
        DLLEXPORT Vec3<float> getDrehung() const;
        //! Gibt die Position des Knochens zurück
        DLLEXPORT Vec3<float> getPosition() const;
        //! Gibt den Radius des Knochens zurück
        float getRadius() const;

        friend Animation3D;
    };

    //! Repräsentiert alle Knochen eines Models, mit denen es Annimiert werden
    //! kann
    class Skelett : public virtual ReferenceCounter
    {
    private:
        Knochen* k;
        int nextId;

    public:
        //! Konstruktor
        DLLEXPORT Skelett();
        //! Destruktor
        DLLEXPORT ~Skelett();
        //! Gibt die Id des nächsten Knochens zurück und berechnet die neue Id
        //! für den Knochen danach Es können maximal MAX_KNOCHEN_ANZ Knochen für
        //! ein Skelett existieren. Wenn diese Zahl überschritten wird, so wird
        //! -1 zurückgegeben
        DLLEXPORT int getNextKnochenId();
        //! setzt die Id des nächsten Knochens
        //! \param id die nächste id
        DLLEXPORT void setNextKnochenId(int id);
        //! Fügt dem Skellet einen Knochen hinzu
        //! \param k Der Knochen
        //! \param elternId Die Id des Eltern Knochens. Wenn der Knochen kein
        //! Elternknochen besitzt, kannder Parameter weggelassen werden.
        DLLEXPORT void addKnochen(Knochen* k, int elternId = -1);
        //! Berechnet die Matrizen der Knochen
        //! \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
        //! Objektes \param kamMatrix Die vereiniegung der view und projektions
        //! Matrizen \return gibt die Anzahl der verwendeten Matrizen zurück
        DLLEXPORT int kalkulateMatrix(Mat4<float>& modelMatrix,
            Mat4<float>* matBuffer,
            float scaleFactor,
            Mat4<float>& kamMatrix);
        //! Berechnet den Radius des Skeletts
        DLLEXPORT float getRadius() const;
        //! gibt den Wurzel Knochen zurück
        DLLEXPORT Knochen* zKnochen() const;
        //! Kopiert das Skelett
        DLLEXPORT Skelett* kopiereSkelett() const;
        //! Gibt die id des nächsten Knochens zurück ohne sie zu erhöhen
        DLLEXPORT int zNextKnochenId() const;

        friend Animation3D;
    };

    //! 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:
        Skelett* 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(Skelett* 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(Mat4<float>& modelMatrix,
            Mat4<float>* matBuffer,
            float scaleFactor,
            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 Iterator<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 Skelett* 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:
        struct AnimationData
        {
            Animation3D* a;
            double speed;
            double offset;

            AnimationData* getThis();
            AnimationData* release();
        };

        Skelett* skelett;
        Model3DData* model;
        Model3DTextur* textur;
        RCArray<AnimationData>* animations;
        float ambientFactor;
        float diffusFactor;
        float specularFactor;

    public:
        //! Konstruktor
        DLLEXPORT Model3D();
        //! Destruktor
        DLLEXPORT virtual ~Model3D();
        //! Fügt eine Animation hinzu
        //! \param a Die neue Animation
        DLLEXPORT void addAnimation(Animation3D* a, double speed = 1);
        //! Entfernt eine Animation
        //! \param zA Die zu entfernende Animation
        DLLEXPORT void removeAnimation(Animation3D* zA);
        //! 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(
            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(Vec3<float>& point,
            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