#include "Model3D.h"
#include "Model2D.h"
#include "DXBuffer.h"
#include "Textur.h"
#include "Animation3D.h"
#ifdef WIN32
#include "Render3D.h"
#include <d3d11.h>
#endif
#include <stdexcept>

using namespace Framework;

// Inhalt der Knochen Klasse

// Konstruktor
Knochen::Knochen( int id )
{
    pos = Vec3< float >( 0, 0, 0 );
    winkel = Vec3< float >( 0, 0, 0 );
    geschwister = 0;
    kinder = 0;
    this->id = id;
}

// Destruktor
Knochen::~Knochen()
{
    delete geschwister;
    delete kinder;
}

// private

// F�gt dem Knochen ein Geschwister Knochen hinzu
//  k: Der Knochen, der hinzugef�gt werden soll
void Knochen::addGeschwisterKnochen( Knochen *k )
{
    if( !geschwister )
        geschwister = k;
    else
        geschwister->addGeschwisterKnochen( k );
}

// public

// Setzt die Position des Knochens relativ zum Model Ursprung
//  pos: Die Position
void Knochen::setPosition( Vec3< float > &pos )
{
    this->pos = pos;
}

// Setzt die Drehung des Knochens relativ zum Model Ursprung
//  winkel: Ein Vektor der die Drehung um die verschiedenen Achsen als Komponenten hat
void Knochen::setDrehung( Vec3< float > &winkel )
{
    this->winkel = winkel;
}

// F�gt einem bestimmten Knochen ein Kind Knochen hinzu
//  id: Die id des Knochens, wo der Knochen als Kind hinzugef�gt werden soll
//  k: Der Knochen, der hinzugef�gt werden soll
void Knochen::addKind( int id, Knochen *k )
{
    if( this->id == id )
    {
        if( !kinder )
            kinder = k;
        else
            kinder->addGeschwisterKnochen( k );
    }
    else
    {
        if( kinder )
            kinder->addKind( id, k );
        else
        {
            Text err = "Es wurde kein Knochen mit der Id: ";
            err += id;
            err += " im Skelett gefunden, um ein Kind Knochen hinzuzuf�gen. Datei:" __FILE__ ", Zeile: ";
            err += __LINE__;
            err += "!";
            delete k;
            throw std::out_of_range( (const char*)err );
        }
    }
}

// Berechnet die Matrizen des Knochen und die von all seinen Geschwister Knochen und Kind Knochen
//  elternMat: Die fertig berechnete Matrix des Elternknochens
//  matBuffer: Ein Array, in dem alle berechneten Matrizen gespeichert werden sollen
//  kamMatrix: Die vereiniegung der view und projektions Matrizen
void Knochen::kalkulateMatrix( Mat4< float > &elternMat, Mat4< float > *matBuffer, Mat4< float > &kamMat )
{
    if( geschwister )
        geschwister->kalkulateMatrix( elternMat, matBuffer, kamMat );
    matBuffer[ id ] = matBuffer[ id ].translation( pos ) * matBuffer[ id ].rotationZ( winkel.z ) * matBuffer[ id ].rotationX( winkel.x ) * matBuffer[ id ].rotationY( winkel.y );
    matBuffer[ id ] = elternMat * matBuffer[ id ];
    if( kinder )
        kinder->kalkulateMatrix( matBuffer[ id ], matBuffer, kamMat );
    matBuffer[ id ] = kamMat * matBuffer[ id ];
}

// Kopiert den Knochen mit allen Geschwister Knochen und Kind Knochen
Knochen *Knochen::kopiereKnochen() const
{
    Knochen *ret = new Knochen( id );
    ret->pos = pos;
    ret->winkel = winkel;
    if( geschwister )
        ret->geschwister = geschwister->kopiereKnochen();
    if( kinder )
        ret->kinder = kinder->kopiereKnochen();
    return ret;
}

// Gibt die Id des Knochens zur�ck
int Knochen::getId() const
{
    return id;
}

// Gibt die Drehung des Knochens zur�ck
Vec3< float > Knochen::getDrehung() const
{
    return winkel;
}

// Gibt die Position des Knochens zur�ck
Vec3< float > Knochen::getPosition() const
{
    return pos;
}

// Gibt den Radius des Knochens zur�ck
float Knochen::getRadius() const
{
    float r = pos.getLength();
    if( geschwister )
        r = MAX( r, geschwister->getRadius() );
    if( kinder )
        r += kinder->getRadius();
    return r;
}

// Inhalt der Skelett Klasse

// Konstruktor
Skelett::Skelett()
{
    k = 0;
    nextId = 0;
    ref = 1;
}

// Destruktor
Skelett::~Skelett()
{
    if( k )
        delete k;
}

// 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
int Skelett::getNextKnochenId()
{
    return nextId++;
}

// F�gt dem Skellet einen Knochen hinzu
//  k: Der Knochen
//  elternId: Die Id des Eltern Knochens. Wenn der Knochen kein Elternknochen besitzt, kannder Parameter weggelassen werden.
void Skelett::addKnochen( Knochen *k, int elternId )
{
    if( !this->k )
        this->k = k;
    else
        this->k->addKind( elternId, k );
    if( k->getId() >= nextId )
        nextId = k->getId() + 1;
}

// Berechnet die Matrizen der Knochen
//  modelMatrix: Die Matrix, die das Skelett in den Raum der Welt transformiert
//  matBuffer: Ein Array von Matrizen, der durch die Knochen Matrizen gef�llt wird
//  return: gibt die Anzahl der verwendeten Matrizen zur�ck
//  kamMatrix: Die vereiniegung der view und projektions Matrizen
int Skelett::kalkulateMatrix( Mat4< float > &modelMatrix, Mat4< float > *matBuffer, Mat4< float > &kamMatrix )
{
    k->kalkulateMatrix( modelMatrix, matBuffer, kamMatrix );
    return nextId;
}

// Berechnet den Radius des Skeletts
float Skelett::getRadius() const
{
    if( k )
        return k->getRadius();
    return 0;
}

// Kopiert das Skelett
Skelett *Skelett::kopiereSkelett() const
{
    Skelett *ret = new Skelett();
    ret->nextId = nextId;
    if( k )
        ret->addKnochen( k->kopiereKnochen() );
    return ret;
}

// Erh�ht den Reference Counting Z�hler.
Skelett *Skelett::getThis()
{
    ref++;
    return this;
}

// Verringert den Reference Counting Z�hler. Wenn der Z�hler 0 erreicht, wird das Zeichnung automatisch gel�scht.
//  return: 0.
Skelett *Skelett::release()
{
    ref--;
    if( !ref )
        delete this;
    return 0;
}

// Inhalt des Polygon3D Struct

// Konstruktor
Polygon3D::Polygon3D()
{
    indexAnz = 0;
    indexList = 0;
    indexBuffer = new DXIndexBuffer( sizeof( int ) );
}

// Destruktor
Polygon3D::~Polygon3D()
{
    indexBuffer->release();
    delete[] indexList;
}

// Inhalt der Model3DData Klasse

// Konstruktor
Model3DData::Model3DData()
{
    id = -1;
    skelett = 0;
    vertexList = 0;
    polygons = new Array< Polygon3D* >();
    vertexBuffer = new DXVertexBuffer( sizeof( Vertex3D ) );
    radius = 0;
    ref = 1;
}

// Destruktor
Model3DData::~Model3DData()
{
    clearModel();
    vertexBuffer->release();
    polygons->release();
}

// L�scht alle Model daten
void Model3DData::clearModel()
{
    delete[] vertexList;
    vertexList = 0;
    for( auto i = polygons->getIterator(); i; i++ )
        delete i;
    polygons->leeren();
    if( skelett )
        skelett->release();
    skelett = 0;
    radius = 0;
}

// Setzt den Zeiger auf ein standartm��ig verwendete Skelett
//  s: Das Skelett, das verwendet werden soll
void Model3DData::setSkelettZ( Skelett *s )
{
    if( skelett )
        skelett->release();
    skelett = s;
}

// Setzt einen Zeiger auf eine Liste mit allen Vertecies des Models
//  vertexList: Ein Array mit Vertecies
//  anz: Die Anzahl der Vertecies im Array
void Model3DData::setVertecies( Vertex3D *vertexList, int anz )
{
    delete[] this->vertexList;
    this->vertexList = vertexList;
    vertexBuffer->setData( vertexList );
    vertexBuffer->setLength( (int)sizeof( Vertex3D ) * anz );
    radius = 0;
    for( int i = 0; i < anz; i++ )
    {
        float r = vertexList[ i ].pos.getLength();
        if( r > radius )
            radius = r;
    }
}

// F�gt ein Polygon zum Model hinzu
//  polygon: Das Polygon, das hinzugef�gt erden soll
void Model3DData::addPolygon( Polygon3D *polygon )
{
    polygons->add( polygon );
}

// Konvertiert ein 2d Model zu 3D
//  model: Das 2d Model, das zu 3d konvertiert werden soll
//  z: Die z koordinate aller punkte des Models
void Model3DData::copyModel2D( Model2DData *model, float z )
{
    if( model && model->vListen && model->polygons )
    {
        clearModel();
        int vAnz = 0;
        for( auto i = model->polygons->getIterator(); i; i++ )
            vAnz += i._.vertex->getEintragAnzahl();
        vertexList = new Vertex3D[ vAnz ];
        vertexBuffer->setData( vertexList );
        vertexBuffer->setLength( (int)sizeof( Vertex3D ) * vAnz );
        int index = 0;
        for( auto i = model->vListen->getIterator(); i; i++ )
        {
            Polygon3D *p = new Polygon3D();
            p->indexAnz = 0;
            for( auto j = i->getIterator(); j; j++ )
            {
                for( auto k = j->zListe()->getIterator(); k.hasNext() && k.next().hasNext(); k++ )
                    p->indexAnz += 3;
            }
            p->indexList = new int[ p->indexAnz ];
            p->indexBuffer->setData( p->indexList );
            p->indexBuffer->setLength( (int)sizeof( int ) * p->indexAnz );
            p->indexAnz = 0;
            for( auto j = i->getIterator(); j; j++ )
            {
                for( auto k = j->zListe()->getIterator(); k; k++ )
                {
                    vertexList[ index ].pos = Vec3< float >( k->punkt->x, k->punkt->y, z );
                    vertexList[ index ].tPos = ( Vec2< float > )*k->textur;
                    if( k.hasNext() && k.next().hasNext() )
                    {
                        p->indexList[ p->indexAnz ] = index;
                        p->indexAnz++;
                        p->indexList[ p->indexAnz ] = index + 1;
                        p->indexAnz++;
                        p->indexList[ p->indexAnz ] = index + 2;
                        p->indexAnz++;
                    }
                    index++;
                }
            }
            addPolygon( p );
        }
    }
}

// Entfernt ein Polygon
//  index: Der Index des Polygons
void Model3DData::removePolygon( int index )
{
    if( !polygons->hat( index ) )
        return;
    delete polygons->get( index );
    polygons->remove( index );
}

// Aktualisiert die Vertecies
//  zRObj: Das Objekt, mit dem die Grafikkarte verwaltet wird
void Model3DData::aktualisiereVertecies( Render3D *zRObj )
{
    vertexBuffer->copieren( zRObj );
}

// Berechnet die Matrizen der Knochen
//  modelMatrix: Die Matrix, die das Skelett in den Raum der Welt transformiert
//  matBuffer: Ein Array von Matrizen, der durch die Knochen Matrizen gef�llt wird
//  return: gibt die Anzahl der verwendeten Matrizen zur�ck
//  kamMatrix: Die vereiniegung der view und projektions Matrizen
int Model3DData::kalkulateMatrix( Mat4< float > &modelMatrix, Mat4< float > *matBuffer, Mat4< float > &kamMatrix ) const
{
    if( !skelett )
        return 0;
    return skelett->kalkulateMatrix( modelMatrix, matBuffer, kamMatrix );
}

// Zeichnet alle Polygons
//  world: Die Welt Matrix, die das Model in die Welt transformiert
//  zTxt: Eine Liste mit Texturen der einzelnen Polygone
//  zRObj: Das Objekt, mit dem gezeichnet werden soll
void Model3DData::render( Mat4< float > &welt, const Model3DTextur *zTxt, Render3D *zRObj )
{
    int ind = 0;
    for( auto i = polygons->getIterator(); i; i++ )
    {
        i->indexBuffer->copieren( zRObj );
        Textur *t = zTxt->zPolygonTextur( ind );
        if( t && t->brauchtUpdate() )
            t->updateTextur( zRObj );
#ifdef WIN32
        zRObj->draw( i->indexBuffer, t );
#endif
        ind++;
    }
}

// Gibt die Anzahl an Polygonen zur�ck
int Model3DData::getPolygonAnzahl() const
{
    return polygons->getEintragAnzahl();
}

// Gibt ein bestimmtes Polygon zur�ck
//  index: Der Index des Polygons
Polygon3D *Model3DData::getPolygon( int index ) const
{
    if( !polygons->hat( index ) )
        return 0;
    return polygons->get( index );
}

// Gibt den radius einer Kugel zur�ck, die das gesammte Model umschlie�t
float Model3DData::getRadius() const
{
    return radius;
}

// Gibt die Id der Daten zur�ck, wenn sie in einer Model3DList registriert wurden. (siehe Framework::zM3DRegister())
int Model3DData::getId() const
{
    return id;
}

// Gibt einen Buffer mit allen Vertecies des Models zur�ck
const DXVertexBuffer *Model3DData::zVertexBuffer() const
{
    return vertexBuffer;
}

// Erh�ht den Reference Counting Z�hler.
//  return: this.
Model3DData *Model3DData::getThis()
{
    ref++;
    return this;
}

// Verringert den Reference Counting Z�hler. Wenn der Z�hler 0 erreicht, wird das Zeichnung automatisch gel�scht.
//  return: 0.
Model3DData *Model3DData::release()
{
    ref--;
    if( !ref )
        delete this;
    return 0;
}


// Inhalt der Model3DTextur

// Konstruktor
Model3DTextur::Model3DTextur()
{
    textures = new RCArray< Textur >();
    ref = 1;
}

// Destruktor
Model3DTextur::~Model3DTextur()
{
    textures->release();
}

// Legt fest, welche Textur f�r welches Polygon ist
//  pI: Der Index des Polygons
//  txt: Die Textur des Polygons
void Model3DTextur::setPolygonTextur( int pI, Textur *txt )
{
    textures->set( txt, pI );
}

// Gibt einen Zeiger auf die Textur eines Polygons zur�ck ohne erh�hten Reference Counter
//  i: Der Index des Polygons
Textur *Model3DTextur::zPolygonTextur( int i ) const
{
    return textures->z( i );
}

// Erh�ht den Reference Counting Z�hler.
//  return: this.
Model3DTextur *Model3DTextur::getThis()
{
    ref++;
    return this;
}

// Verringert den Reference Counting Z�hler. Wenn der Z�hler 0 erreicht, wird das Zeichnung automatisch gel�scht.
//  return: 0.
Model3DTextur *Model3DTextur::release()
{
    ref--;
    if( !ref )
        delete this;
    return 0;
}

// Inhalt der AnimationData Struktur
Model3D::AnimationData *Model3D::AnimationData::getThis()
{
    return this;
}

Model3D::AnimationData *Model3D::AnimationData::release()
{
    a->release();
    delete this;
    return 0;
}

// Inhalt der Model3D Klasse
// Konstruktor
Model3D::Model3D()
    : Zeichnung3D()
{
    model = 0;
    textur = 0;
    skelett = 0;
    animations = new RCArray< AnimationData >();
}

// Destruktor
Model3D::~Model3D()
{
    if( model )
        model->release();
    if( textur )
        textur->release();
    if( skelett )
        skelett->release();
    animations->release();
}

// F�gt eine Animation hinzu
//  a: Die neue Animation
void Model3D::addAnimation( Animation3D *a, double speed )
{
    AnimationData *d = new AnimationData();
    d->a = a;
    d->speed = speed;
    d->offset = 0;
    animations->add( d );
}

// Entfernt eine Animation
//  zA: Die zu entfernende Animation
void Model3D::removeAnimation( Animation3D *zA )
{
    for( int i = 0; i < animations->getEintragAnzahl(); i++ )
    {
        if( animations->z( i )->a == zA )
        {
            animations->remove( i );
            return;
        }
    }
}

// Setzt den Zeiger auf das zum Annimieren verwendete Skelett
//  s: Das Skelett, das verwendet werden soll
void Model3D::setSkelettZ( Skelett *s )
{
    if( skelett )
        skelett->release();
    skelett = s;
}

// Setzt die Daten des Models
//  data: Die Daten
void Model3D::setModelDaten( Model3DData *data )
{
    if( model )
        model->release();
    model = data;
}

// Setzt die zum Zeichnen zu benutzenden Texturen
//  txt: Ein Liste mit Texturen zu den verschiedenen Polygonen zugeordnet
void Model3D::setModelTextur( Model3DTextur *txt )
{
    if( textur )
        textur->release();
    textur = txt;
}

// Errechnet die Matrizen aller Knochen des Skeletts des Models
//  viewProj: Die miteinander multiplizierten Kameramatrizen
//  matBuffer: Ein Array mit Matrizen, der gef�llt werden soll
//  return: Die Anzahl der Matrizen, die das Model ben�tigt
int Model3D::errechneMatrizen( Mat4< float > &viewProj, Mat4< float > *matBuffer )
{
    int ret = 0;
    if( skelett )
        ret = skelett->kalkulateMatrix( welt, matBuffer, viewProj );
    else if( model )
        ret = model->kalkulateMatrix( welt, matBuffer, viewProj );
    if( !ret )
        return Zeichnung3D::errechneMatrizen( viewProj, matBuffer );
    return ret;
}

// Verarbeitet die vergangene Zeit
//  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.
bool Model3D::tick( double tickval )
{
    radius = model ? model->getRadius() : 0;
    if( skelett )
    {
        radius += skelett->getRadius();
        for( auto i = animations->getIterator(); i && i._; i++ )
        {
            rend = i->speed > 0;
            i->a->apply( skelett, i->offset, tickval * i->speed );
        }
    }
    return Zeichnung3D::tick( tickval );
}

// Zeichnet das Model
//  zRObj: Ein Zeiger auf das Objekt, das zum Zeichnen verwendet werden soll (ohne erh�hten Reference Counter)
void Model3D::render( Render3D *zRObj )
{
    if( !model )
        return;
    model->aktualisiereVertecies( zRObj );
#ifdef WIN32
    zRObj->beginnModel( this );
#endif
    model->render( welt, textur, zRObj );
}

// Gibt die Id der Daten zur�ck, wenn sie in einer Model3DList registriert wurden. (siehe Framework::zM3DRegister())
int Model3D::getDatenId() const
{
    return model ? model->getId() : -1;
}

// Gibt einen Buffer mit allen Vertecies des Models zur�ck
const DXVertexBuffer *Model3D::zVertexBuffer() const
{
    return model ? model->zVertexBuffer() : 0;
}