/**
 * =========================================================================
 * File        : ParticleEngine.cpp
 * Project     : 0 A.D.
 * Description : Particle engine implementation
 * =========================================================================
 */

#include "precompiled.h"
#include "ParticleEngine.h"

CParticleEngine *CParticleEngine::m_pInstance = 0;
CParticleEngine::CParticleEngine(void)
{
    m_pHead = NULL;
    totalParticles = 0;
}

CParticleEngine::~CParticleEngine(void)
{
}

void CParticleEngine::Cleanup()
{
    tEmitterNode *temp = m_pHead;
    totalParticles = 0;
    while(temp)
    {
        tEmitterNode *pTemp = temp->next;
        if(!temp->prev)
            m_pHead = temp->next;
        else
            temp->prev->next = temp->next;

        if(pTemp)
            temp->next->prev = temp->prev;

        delete temp->pEmitter;
        delete temp;
        temp = pTemp;
    }

    DeleteInstance();
}

CParticleEngine *CParticleEngine::GetInstance(void)
{
    // Check to see if one hasn't been made yet.
    if (m_pInstance == 0)
        m_pInstance = new CParticleEngine;

    // Return the address of the instance.
    return m_pInstance;
}

void CParticleEngine::DeleteInstance()
{
    if (m_pInstance)
        delete m_pInstance;
    m_pInstance = 0;
}

bool CParticleEngine::InitParticleSystem()
{
    // Texture Loading
    CTexture pTex;
    pTex.SetName("art/textures/particles/sprite.tga");

    u32 flags = 0;
    if(!(g_Renderer.LoadTexture(&pTex, flags)))
        return false;

    g_Renderer.SetTexture(0, &pTex);
    idTexture[DEFAULTTEXT] = pTex;

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

    glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
    //glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_DECAL);
    //glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_BLEND);
    //glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_REPLACE);

    return true;
}

bool CParticleEngine::AddEmitter(CEmitter *emitter, int type, int ID)
{
    emitter->SetTexture(&idTexture[type]);
    if(m_pHead == NULL)
    {
        tEmitterNode *temp = new tEmitterNode;
        temp->pEmitter = emitter;
        temp->prev = NULL;
        temp->next = NULL;
        temp->ID = ID;
        m_pHead = temp;
        return true;
    }
    else
    {
        tEmitterNode *temp = new tEmitterNode;
        temp->pEmitter = emitter;
        temp->next = m_pHead;
        temp->prev = NULL;
        temp->ID = ID;
        m_pHead->prev = temp;
        m_pHead = temp;
        return true;
    }
}

CEmitter* CParticleEngine::FindEmitter(int ID)
{
    tEmitterNode *temp = m_pHead;
    while(temp)
    {
        if(temp->ID < 0 || temp->ID > MAX_EMIT)
            continue;

        // NOTE: In the event that there are two different
        // emitters with the same ID, this will only
        // return the first one that it finds. So
        // make sure your emitter has a unique ID
        // if you want to use this function.
        if(temp->ID == ID)
            return temp->pEmitter;

        temp = temp->next;
    }

    // NOTE: Just in case this happens, it's VERY important
    // that you wrap this function in a if statement
    // to check for this condition because you could
    // end up with a crash if you try to change an
    // emitter when you couldn't find it and returned
    // NULL instead.
    return NULL;
}

void CParticleEngine::UpdateEmitters()
{
    tEmitterNode *temp = m_pHead;
    totalParticles = 0;
    while(temp)
    {
        // are we ready for deletion?
        if(temp->pEmitter->IsFinished())
        {
            // store a pointer to the next node
            tEmitterNode *pTemp = temp->next;

            // check for the head
            if(!temp->prev)
                m_pHead = pTemp;
            else
                // forward the previous's pointer
                temp->prev->next = pTemp;

            // if there is any next one,
            if(pTemp)
                // fix the backwards pointer
                pTemp->prev = temp->prev;

            delete temp->pEmitter;
            delete temp;
            temp = pTemp;
        }
        else
        {
            temp->pEmitter->Update();

            // Add current emitter to particle count
            totalParticles += temp->pEmitter->GetParticleCount();
            temp = temp->next;
        }
    }
}

void CParticleEngine::RenderParticles()
{
    EnterParticleContext();

    tEmitterNode *temp = m_pHead;
    while(temp)
    {
        temp->pEmitter->Render();
        temp = temp->next;
    }

    LeaveParticleContext();
}

void CParticleEngine::DestroyAllEmitters(bool fade)
{
    tEmitterNode *temp = m_pHead;
    while(temp)
    {
        if(fade)
        {
            temp->pEmitter->SetEmitterLife(0);
            temp = temp->next;
        }
        else
        {
            // store a pointer to the next node
            tEmitterNode *pTemp = temp->next;

            // check for the head
            if(!temp->prev)
                m_pHead = pTemp;
            else
                // forward the previous's pointer
                temp->prev->next = pTemp;

            // if there is any next one,
            if(pTemp)
                // fix the backwards pointer
                pTemp->prev = temp->prev;

            delete temp->pEmitter;
            delete temp;
            temp = pTemp;
        }
    }
    m_pHead = NULL;
    UpdateEmitters();
}

void CParticleEngine::EnterParticleContext(void)
{
    //glEnable(GL_DEPTH_TEST);               // Enable depth testing for hidden surface removal.
    glDepthMask(false);
    //glDisable(GL_LIGHTING);
    glEnable(GL_TEXTURE_2D);               // Enable texture mapping.
    glPushMatrix();
    glEnable(GL_BLEND);
}

void CParticleEngine::LeaveParticleContext(void)
{
    //glDisable(GL_DEPTH_TEST);
    glDepthMask(true);
    //glEnable(GL_LIGHTING);
    glDisable(GL_TEXTURE_2D);
    glPopMatrix();
    glDisable(GL_BLEND);
}