#include "Welt3D.h"

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

using namespace Framework;

// Inhalt der Welt3D Klasse aus Welt3D.h
// Konstructor
Welt3D::Welt3D()
    : Model3DCollection()
{
    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();
    rend |= tick(
        [this, &tickval](Model3D* m) { rend |= m->tick(tickval); }, 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);
}

//! f�hrt eine tick funktion auf jedem Model aus
bool Welt3D::tick(std::function<void(Model3D*)> f, double time)
{
    for (auto m : *members)
        f(m);
    bool res = 0;
    for (auto c : modelCollections)
        res |= c->tick(f, time);
    return res;
}

//! f�hrt eine render funktion auf jedem Model aus
void Welt3D::render(std::function<void(Model3D*)> f)
{
    for (auto m : *members)
        f(m);
    for (auto c : modelCollections)
        c->render(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];
}

//! removes a specific fiffuse light from the world
//! \param index the index of the light
DLLEXPORT void Framework::Welt3D::removeDiffuseLight(int index)
{
    for (int i = index; i < diffuseLightCount - 1; i++)
    {
        diffuseLights[i] = diffuseLights[i + 1];
    }
    diffuseLightCount--;
}

//! removes a specific point light from the world
//! \param index the index of the light
DLLEXPORT void Framework::Welt3D::removePointLight(int index)
{
    for (int i = index; i < pointLightCount - 1; i++)
    {
        pointLights[i] = pointLights[i + 1];
    }
    pointLightCount--;
}