#include "World3D.h"

#include "Drawing3D.h"
#include "DXBuffer.h"
#include "Globals.h"
#include "GraphicsApi.h"
#include "Model3D.h"
#include "MouseEvent.h"

using namespace Framework;

// Contents of the World3D class from World3D.h
// Konstructor
World3D::World3D()
    : Model3DCollection()
{
    members = new RCArray<Model3D>();
    pointLightCount = 0;
    diffuseLightCount = 0;
    pointLights = 0;
    diffuseLights = 0;
    rend = 0;
}

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

// Blocks access to the object and waits for access if necessary
Lock& World3D::readLock()
{
    return lock.getReadLock();
}

// Releases the object for other threads
Lock& World3D::writeLock()
{
    return lock.getWriteLock();
}

// Adds an object to the world
//  obj: The object to be added
void World3D::addDrawable(Model3D* obj)
{
    lock.lockWrite();
    if (debugDX)
    {
        for (auto i : *members)
        {
            if (i == obj) throw std::exception();
        }
    }
    members->add(obj);
    rend = 1;
    lock.unlockWrite();
}

// Removes an object from the world
//  obj: The object to be removed
void World3D::removeDrawable(Model3D* obj)
{
    lock.lockWrite();
    int index = 0;
    for (Model3D* member : *members)
    {
        if (member == obj)
        {
            members->remove(index);
            rend = 1;
            break;
        }
        index++;
    }
    lock.unlockWrite();
}

//! Adds a collection of objects to the world
//! \param collection The collection to be added
void World3D::addCollection(Model3DCollection* collection)
{
    lock.lockWrite();
    modelCollections.add(collection);
    rend = 1;
    lock.unlockWrite();
}

//! removes a collection of models from the world
//! \param zCollection The collection to be removed
void World3D::removeCollection(Model3DCollection* zCollection)
{
    lock.lockWrite();
    int index = 0;
    for (Model3DCollection* collection : modelCollections)
    {
        if (collection == zCollection)
        {
            modelCollections.remove(index);
            rend = 1;
            break;
        }
        index++;
    }
    lock.unlockWrite();
}

// Processes a mouse event
//  me: The mouse event to be processed
void World3D::doMouseEvent(MouseEvent3D& me)
{
    // cs.lock()
    // int anz = 0;
    // int index = 0;
    // for( Drawable3D **i = members; index < arraySize; i++, index++ )
    //{
    //     if( *i )
    //     {
    //         distSq[ anz ] = me.pos.distanceSq( ( *i )->getPos() );
    //         alphaVS[ anz ] = *i;
    //         anz++;
    //     }
    // }
    // index = 0;
    // for( Drawable3D **i = membersAlpha; index < arraySizeAlpha; i++, index++
    // )
    //{
    //     if( *i )
    //     {
    //         distSq[ anz ] = me.pos.distanceSq( ( *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 ]->doMouseEvent( me );
    //         if( me.processed )
    //         {
    //             cs.unlock();
    //             return;
    //         }
    //         used[ ind ] = 1;
    //     }
    // } while( ind >= 0 );
    // cs.unlock();
}

// Processes the elapsed time
//  tickval: The time in seconds since the last call of the function
//  return: true if the object has changed, false
//  otherwise.
bool World3D::tick(double tickval)
{
    lock.lockRead();
    rend |= tick(
        [this, &tickval](Model3D* m) { rend |= m->tick(tickval); }, tickval);
    lock.unlockRead();
    bool tmp = rend;
    rend = 0;
    return tmp;
}

// Calculates the color of a view ray from a specific point
// in a specific direction
//  point: The origin of the ray,
//  dir: The direction of the ray
//  return: The color of the ray
int World3D::traceRay(Vec3<float>& point, Vec3<float>& dir)
{
    float min = INFINITY;
    int pId = 0;
    Model3D* nearest = 0;
    lock.lockRead();
    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;
        }
    });
    lock.unlockRead();
    if (nearest) return nearest->traceRay(point, dir, pId, this);
    return 0xFF000000;
}

//! executes a function on every model
void Framework::World3D::forAll(std::function<void(Model3D*)> f)
{
    lock.lockRead();
    for (auto m : *members)
        f(m);
    for (auto c : modelCollections)
        c->forAll(f);
    lock.unlockRead();
}

//! executes a tick function on every model
bool World3D::tick(std::function<void(Model3D*)> f, double time)
{
    lock.lockRead();
    for (auto m : *members)
        f(m);
    bool res = 0;
    for (auto c : modelCollections)
        res |= c->tick(f, time);
    lock.unlockRead();
    return res;
}

//! executes a render function on every model
void World3D::render(std::function<void(Model3D*)> f)
{
    lock.lockRead();
    for (auto m : *members)
        f(m);
    for (auto c : modelCollections)
        c->render(f);
    lock.unlockRead();
}

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

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

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

//! adds a new diffuse light source
//! \param light The new light source
void Framework::World3D::addDiffuseLight(DiffuseLight light)
{
    lock.lockWrite();
    DiffuseLight* tmp = new DiffuseLight[diffuseLightCount + 1];
    for (int i = 0; i < diffuseLightCount; i++)
    {
        tmp[i] = diffuseLights[i];
    }
    tmp[diffuseLightCount] = light;
    delete[] diffuseLights;
    diffuseLights = tmp;
    diffuseLightCount++;
    lock.unlockWrite();
}

//! adds a new point light source
//! \param light The new light source
void Framework::World3D::addPointLight(PointLight light)
{
    lock.lockWrite();
    PointLight* tmp = new PointLight[pointLightCount + 1];
    for (int i = 0; i < pointLightCount; i++)
    {
        tmp[i] = pointLights[i];
    }
    tmp[pointLightCount] = light;
    delete[] pointLights;
    pointLights = tmp;
    pointLightCount++;
    lock.unlockWrite();
}

//! Returns the reference to a diffuse light source
//! \param index The index of the light source
DiffuseLight& Framework::World3D::getDiffuseLight(int index) const
{
    return diffuseLights[index];
}

//! Returns the reference to a point light source
//! \param index The index of the light source
PointLight& Framework::World3D::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::World3D::removeDiffuseLight(int index)
{
    lock.lockWrite();
    for (int i = index; i < diffuseLightCount - 1; i++)
    {
        diffuseLights[i] = diffuseLights[i + 1];
    }
    diffuseLightCount--;
    lock.unlockWrite();
}

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