#include "Welt3D.h"
#include "Zeichnung3D.h"
#include "MausEreignis.h"
#include "Model3D.h"
#include "GraphicsApi.h"
#include "DXBuffer.h"
#include "Globals.h"

using namespace Framework;


// Inhalt der Welt3D Klasse aus Welt3D.h
// Konstructor
Welt3D::Welt3D()
    : ReferenceCounter()
{
    members = new RCArray< Model3D >();
    pointLightCount = 0;
    diffuseLightCount = 0;
    pointLights = 0;
    diffuseLights = 0;
    rend = 0;
}

// Destruktor
Welt3D::~Welt3D()
{
    members->release();
    delete[] pointLights;
    delete[] diffuseLights;
}

// Blockiert den zugriff auf das Objekt und wartet gegebenfalls auf den Zugriff
void Welt3D::lock()
{
    cs.lock();
}

// Gibt das Objekt für andere Threads frei
void Welt3D::unlock()
{
    cs.unlock();
}

// Fügt der Welt ein Objekt hinzu
//  obj: Das Objekt, was hinzugefügt werden soll
void Welt3D::addZeichnung( Model3D* obj )
{
    cs.lock();
    if( debugDX )
    {
        for( auto i : *members )
        {
            if( i == obj )
                throw std::exception();
        }
    }
    members->add( obj );
    rend = 1;
    cs.unlock();
}

// Entfernt ein Objekt aus der Welt
//  obj: Das Objekt, das entwernt werden soll
void Welt3D::removeZeichnung( Model3D* obj )
{
    cs.lock();
    int index = 0;
    for( Model3D* member : *members )
    {
        if( member == obj )
        {
            members->remove( index );
            rend = 1;
            break;
        }
        index++;
    }
    cs.unlock();
}

//! Fügt der Welt eine Collection von Objekten hinzu
//! \param collection Die Collection, die hinzugefügt werden soll
void Welt3D::addCollection( Model3DCollection* collection )
{
    cs.lock();
    modelCollections.add( collection );
    rend = 1;
    cs.unlock();
}

//! removes a collection of models from the world
//! \param zCollection Die Collection die entfernt werden soll
void Welt3D::removeCollection( Model3DCollection* zCollection )
{
    cs.lock();
    int index = 0;
    for( Model3DCollection* collection : modelCollections )
    {
        if( collection == zCollection )
        {
            modelCollections.remove( index );
            rend = 1;
            break;
        }
        index++;
    }
    cs.unlock();
}

// Verarbeitet ein Mausereignis
//  me: Das Mausereignis, das verarbeitet werden soll
void Welt3D::doMausEreignis( MausEreignis3D& me )
{
    //cs.lock()
    //int anz = 0;
    //int index = 0;
    //for( Zeichnung3D **i = members; index < arraySize; i++, index++ )
    //{
    //    if( *i )
    //    {
    //        distSq[ anz ] = me.pos.abstandSq( ( *i )->getPos() );
    //        alphaVS[ anz ] = *i;
    //        anz++;
    //    }
    //}
    //index = 0;
    //for( Zeichnung3D **i = membersAlpha; index < arraySizeAlpha; i++, index++ )
    //{
    //    if( *i )
    //    {
    //        distSq[ anz ] = me.pos.abstandSq( ( *i )->getPos() );
    //        alphaVS[ anz ] = *i;
    //        anz++;
    //    }
    //}
    //float maxEntf;
    //int ind;
    //do
    //{
    //    maxEntf = -1;
    //    ind = -1;
    //    for( int i = 0; i < anz; i++ )
    //    {
    //        if( !used[ i ] && distSq[ i ] > maxEntf )
    //        {
    //            maxEntf = distSq[ i ];
    //            ind = i;
    //        }
    //    }
    //    if( ind >= 0 )
    //    {
    //        alphaVS[ ind ]->doMausEreignis( me );
    //        if( me.verarbeitet )
    //        {
    //            cs.unlock();
    //            return;
    //        }
    //        used[ ind ] = 1;
    //    }
    //} while( ind >= 0 );
    //cs.unlock();
}

// 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 Welt3D::tick( double tickval )
{
    cs.lock();
    forAll( [this, &tickval]( Model3D* m ) {
        rend |= m->tick( tickval );
    } );
    cs.unlock();
    bool tmp = rend;
    rend = 0;
    return tmp;
}

// brerechnet die Farbe eines Sichtstrahls, der von einem bestimmten punkt aus in eine bestimmte richtung schaut
//  point: Der ursprung des Strahls,
//  dir: Die Richtung des Strahls
//  return: Die Farbe des Strahls
int Welt3D::traceRay( Vec3< float >& point, Vec3< float >& dir )
{
    float min = INFINITY;
    int pId = 0;
    Model3D* nearest = 0;
    forAll( [this, &point, &dir, &pId, &min, &nearest]( Model3D* m ) {
        float tmp = m->traceRay( point, dir, min, pId );
        if( min > tmp && tmp >= 0 )
        {
            min = tmp;
            nearest = m;
        }
    } );
    if( nearest )
        return nearest->traceRay( point, dir, pId, this );
    return 0xFF000000;
}

//! führt eine funktion auf jedem Model aus
void Framework::Welt3D::forAll( std::function<void( Model3D* )> f )
{
    for( auto m : *members )
        f( m );
    for( auto c : modelCollections )
        c->forAll( f );
}

int Framework::Welt3D::getPointLightCount() const
{
    return pointLightCount;
}

int Framework::Welt3D::getDiffuseLightCount() const
{
    return diffuseLightCount;
}

void Framework::Welt3D::copyLight( DXBuffer* zDiffuse, DXBuffer* zPoints ) const
{
    zDiffuse->setData( diffuseLights );
    zDiffuse->setLength( diffuseLightCount * (int)sizeof( DiffuseLight ) );
    zDiffuse->copieren();
    zPoints->setData( pointLights );
    zPoints->setLength( pointLightCount * (int)sizeof( PointLight ) );
    zPoints->copieren();
}

//! fügt eine neue diffuse lichtquelle hinzu
//! \param light Die neue Lichtquelle
void Framework::Welt3D::addDiffuseLight( DiffuseLight light )
{
    DiffuseLight* tmp = new DiffuseLight[ diffuseLightCount + 1 ];
    memcpy( tmp, diffuseLights, sizeof( DiffuseLight ) * diffuseLightCount );
    tmp[ diffuseLightCount ] = light;
    delete[] diffuseLights;
    diffuseLights = tmp;
    diffuseLightCount++;
}

//! fügt eine neue Punkt lichtquelle hinzu
//! \param light Die neue Lichtquelle
void Framework::Welt3D::addPointLight( PointLight light )
{
    PointLight* tmp = new PointLight[ pointLightCount + 1 ];
    memcpy( tmp, pointLights, sizeof( PointLight ) * pointLightCount );
    tmp[ pointLightCount ] = light;
    delete[] pointLights;
    pointLights = tmp;
    pointLightCount++;
}

//! Gibt die Referenz auf eine Diffuse Lichtquelle zurück
//! \param index Der Index der Lichtquelle
DiffuseLight& Framework::Welt3D::getDiffuseLight( int index ) const
{
    return diffuseLights[ index ];
}

//! Gibt die Referenz auf eine Punkt Lichtquelle zurück
//! \param index Der Index der Lichtquelle
PointLight& Framework::Welt3D::getPointLight( int index ) const
{
    return pointLights[ index ];
}
