#include <mutex>
#include <GLFW/glfw3.h>
#include "emitter.h"
#include <unistd.h>
#include <list>
#include <thread>
#include "random.h"
#include "spawner.h"
#include "particle.h"
#include <log.h>
#include <algorithm>

using namespace std;
using namespace render::fx;

std::list<emitter*> emitterQueue;
std::mutex queueMutex;
std::vector<std::thread*> particlesThreads;

std::mutex particlesRunning;

::emitter* getEmitterFromQueue();
void mathParticles();
bool updateParticle(particlesUpdateInfo*, uint32_t);
void spawnParticle(particlesUpdateInfo*, uint32_t, std::shared_ptr<rnd>);
void spawnParticles(particlesUpdateInfo*, uint32_t*, uint32_t, std::shared_ptr<rnd>);

namespace render{
namespace fx{

    void initParticles()
    {
        int numCores = 1;
        particlesThreads.reserve(numCores);
        for(int i = 0; i < numCores; i++) {
            particlesThreads.push_back(new std::thread(mathParticles));
        }
    }

    void freeParticles()
    {
        particlesRunning.lock();
        queueMutex.lock();
        emitterQueue.clear();
        queueMutex.unlock();
        for(auto i: particlesThreads)
        {
            i->join();
            delete i;
        }

        particlesThreads.clear();

        particlesRunning.unlock();
    }
}}

void mathParticles()
{
    Logger::Debug("PARTICLES UPDATE THREAD STARTED\n");
    srand(time(0));
    uint32_t spawnBuffer[512];
    emitter *em = nullptr;
    particlesUpdateInfo src;

    std::shared_ptr<rnd> randEngine = std::make_shared<rnd>();

    while(true)
    {
        if(!particlesRunning.try_lock())
            break;
        particlesRunning.unlock();
        if((em = getEmitterFromQueue()) == nullptr)
        {
            usleep(4000);//спать немного
            continue;
        }
        uint32_t spawnCount = 0;

        em->getInfo(&src);

        *src.drawCount = 0;
        src.ctime = glfwGetTime();
        src.delta = src.ctime - *src.oldTime;

        src.size = src.data->size();
        src.dataPtr = src.data->data();
        src.rdataPtr = src.rdata->data();

        if(src.running)
            *src.spawnCount += src.emission->emission.math(*src.time)*src.delta;

        for(uint32_t i = 0; i < src.size; i++)
            if(!updateParticle(&src, i) && src.running)
            {
                if(spawnCount == 512)
                {
                    spawnParticles(&src, spawnBuffer, spawnCount, randEngine);
                    spawnCount = 0;
                }

                if(*src.spawnCount > 1)
                {
                    spawnBuffer[spawnCount++] = i;
                    *src.spawnCount -= 1.0f;
                }
            }

        if(src.running)
        {
            uint32_t c = src.size;
            while(*src.spawnCount > 1)
            {
                if(spawnCount == 512)
                {
                    spawnParticles(&src, spawnBuffer, spawnCount, randEngine);
                    spawnCount = 0;
                }
                spawnBuffer[spawnCount++] = c++;
                *src.spawnCount -= 1.0f;
            }
            if(spawnCount)
                spawnParticles(&src, spawnBuffer, spawnCount, randEngine);
            *src.time += src.delta/src.workTime;
            if((*src.time) >= 1 && src.looped)
                *src.time -= 1;
        }

        *src.oldPos = src.pos;
        *src.oldTime = src.ctime;
        *src.updated = 1;

        src.locker->unlock();
    }

    Logger::Debug("PARTICLES UPDATE THREAD TERMINATED\n");
}

bool updateParticle(particlesUpdateInfo *src, uint32_t i)
{
    particleData *data = src->dataPtr;

    if(data[i].timer < 0 || !data[i].textureAnimation.size)
        return 0;
    particle *rdata = src->rdataPtr;
    data[i].timer -= src->delta/data[i].lifeTime;
    float timer = 1.0f-data[i].timer;
    data[i].pos += data[i].direction*data[i].speed.math(timer)*src->delta;
    data[i].textureAnimation.math(timer);
    const vec2u32 &index = data[i].textureAnimation.nodes;
    rdata[*src->drawCount].color = data[i].color.math(timer);
    rdata[*src->drawCount].alpha = data[i].alpha.math(timer);
    rdata[*src->drawCount].scale = data[i].scale.math(timer);
    rdata[*src->drawCount].angle = data[i].angle.math(timer);

    rdata[*src->drawCount].tex  = data[i].textureAnimation.values[index.x].value;
    rdata[*src->drawCount].tex2 = data[i].textureAnimation.values[index.y].value;
    rdata[*src->drawCount].blend= data[i].textureAnimation.ctimer;
    rdata[*src->drawCount].vert = data[i].pos;
    (*src->drawCount)++;
    return 1;
}

void spawnParticle(particlesUpdateInfo *src, uint32_t i, shared_ptr<rnd> rEng)
{
    particleData *data = src->dataPtr;
    data[i].timer = 1;
    data[i].lifeTime = src->life;

    data[i].pos = src->position;
    if(src->emission->spawner)
        data[i].pos += src->emission->spawner->getSpawnPos(0);
    if(src->texNodes && src->particles->texAnim.size())
    {
        data[i].textureAnimation.resize(src->particles->texAnim.size());
        for(uint32_t c = 0; c < src->particles->texAnim.size(); c++)
        {
            data[i].textureAnimation.values[c].time = src->particles->texAnim[c].time;
            data[i].textureAnimation.values[c].value = src->texNodes[src->particles->texAnim[c].getValue()];
        }
    }

    sincosf(src->direction, &data[i].direction.x, &data[i].direction.y);

    src->particles->color.math(&data[i].color, rEng->rand3());
    src->particles->scale.math(&data[i].scale, rEng->rand2());
    src->particles->angle.math(&data[i].angle, rEng->rand());
    src->particles->alpha.math(&data[i].alpha, rEng->rand());
    src->particles->speed.math(&data[i].speed, rEng->rand());

    for (int j = 0; j < data[i].angle.size; j++) {
        data[i].angle.values[j].value -= src->direction;
    }

    updateParticle(src, i);
}

void spawnParticles(particlesUpdateInfo *src, uint32_t *indeces, uint32_t count, shared_ptr<rnd> rEng)
{
    float delta = src->delta;
    float time = *src->time;
    src->delta = 0;
    float timeStep = (delta)/(count-1);
    vec2f posStep = (src->pos - *src->oldPos)/(count-1);
    src->position = src->pos;
    for(uint32_t i = 0; i < count && time <= 1; i++)
    {
        vec2f dir = src->emission->direction.math(time);
        vec2f life = src->emission->lifeTime.math(time);

        src->direction = dir.x+dir.y*rEng->rand();
        src->life = life.x+life.y*rEng->rand();

        if(indeces[i] >= src->size)
        {
            if(!src->size)
                src->size++;
            src->size *= 2;
            src->data->resize(src->size);
            src->rdata->resize(src->size);
            src->dataPtr = src->data->data();
            src->rdataPtr = src->rdata->data();
        }
        spawnParticle(src, indeces[i], rEng);
        src->position -= posStep;
        time += timeStep;
        src->delta += timeStep;
        if(src->looped && time > 1)
            time -= 1.0f;
    }
    src->delta = delta;
}

void render::fx::pushEmitterToQueue(emitter *em)
{
    queueMutex.lock();
    emitterQueue.push_back(em);
    queueMutex.unlock();
}

void render::fx::removeEmitterFromQueue(emitter *em)
{
    queueMutex.lock();
    //uint32_t size = emitterQueue.size();
    emitterQueue.remove(em);
    queueMutex.unlock();
}

emitter* getEmitterFromQueue()
{
    if(!queueMutex.try_lock())
        return nullptr;
    emitter* ret = nullptr;

    if(!emitterQueue.empty())
    {
        try_again:
        ret = emitterQueue.front();
        if(!ret->lock())
        {
            emitterQueue.pop_front();
            emitterQueue.push_back(ret);
            goto try_again;
        }

        emitterQueue.pop_front();
    }
    queueMutex.unlock();
    return ret;
}
