/*
 *  Copyright (C) 2008 Patrik Gornicz, Gornicz_P (at) hotmail (dot) com.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "entityManager.h"

#include <bear/debug.h>
#include <bear/Lock.h>
#include <bear/Autolock.h>
using namespace bear;

#include <set>
#include <SDL/SDL.h>

#include "Entities/Entity.h"
#include "Entities/Particle.h"
#include "Entities/PhysicsEntity.h"

#include "collisionManager.h"
#include "effectManager.h"

/// ***** Private Method Headers *****

static void updateParticles(float);
static void updatePhysics(float);
static void addOrRemoveParticles();
static void addOrRemovePhysics();

/// ***** Private Variables *****

typedef std::set<Particle*> setPart;
static setPart particles_To_Add;
static setPart active_Particles;
static setPart particles_To_Remove;

typedef std::set<PhysicsEntity*> setPhys;
static setPhys physics_To_Add;
static setPhys active_Physics;
static setPhys physics_To_Remove;

static Lock muSetPart;
static Lock muSetPhys;

/// ***** Initializers/Cleaners *****

void manager::init()
{
    muSetPart.init();
    muSetPhys.init();

    collision::init();
}
void manager::clean()
{
    collision::clean();

    muSetPhys.fini();
    muSetPart.fini();
}

/// ***** Public Methods *****

void manager::add(Entity* pe)
{
    DASSERT(pe != NULL);

    {
        Particle* pp = dynamic_cast<Particle*>(pe);
        if( pp != NULL )
        {
            particles_To_Add.insert(pp);
            return;
        }
    }

    {
        PhysicsEntity* ppe = dynamic_cast<PhysicsEntity*>(pe);
        if( ppe != NULL )
        {
            physics_To_Add.insert(ppe);
            return;
        }
    }

    DPF(0, "ENTITY TYPE NOT SUPPORTED BY addEntity()!!");
}
void manager::remove(Entity* pe)
{
    DASSERT(pe != NULL);

    {
        Autolock lock( &muSetPart );
        Particle* p = dynamic_cast<Particle*>(pe);
        if( p != NULL )
        {
            particles_To_Remove.insert(p);
            return;
        }
    }

    {
        Autolock lock( &muSetPhys );
        PhysicsEntity* ppe = dynamic_cast<PhysicsEntity*>(pe);
        if( ppe != NULL )
        {
            physics_To_Remove.insert(ppe);
            return;
        }
    }

    DPF(0, "ENTITY TYPE NOT SUPPORTED BY deleteEntity()!!");
}

void manager::handleInput()
{
    effect::handleInput();
}
void manager::update(float time_step)
{
    effect::update(time_step);

    updateParticles(time_step);
    updatePhysics(time_step);
}
void manager::draw()
{
    {
        Autolock lock( &muSetPart );

        addOrRemoveParticles();

        // draw active Particle*s
        for( setPart::iterator it = active_Particles.begin();
             it != active_Particles.end();
             it++ )
        {
            (*it)->draw();
        }
    }

    {
        Autolock lock( &muSetPhys );

        addOrRemovePhysics();

        // draw active PhysicsEntity*s
        for( setPhys::iterator it = active_Physics.begin();
             it != active_Physics.end();
             it++ )
        {
            (*it)->draw();
        }
    }
}

/// ***** Private Methods *****

void updateParticles(float time_step)
{
    addOrRemoveParticles();

    // update active Particle*s
    for( setPart::iterator it = active_Particles.begin();
         it != active_Particles.end();
         it++ )
    {
        (*it)->update(time_step);
    }
}
void updatePhysics(float time_step)
{
    addOrRemovePhysics();

    // apply collision math
    collision::update(active_Physics);

    // update active PhysicsEntity*s
    for( setPhys::iterator it = active_Physics.begin();
         it != active_Physics.end();
         it++ )
    {
        (*it)->update(time_step);
    }
}
void addOrRemoveParticles()
{
    Autolock lock( &muSetPart );

    // add new Particle*s to Active
    for( setPart::iterator it = particles_To_Add.begin();
         it != particles_To_Add.end();
         it++ )
    {
        active_Particles.insert(*it);
    }
    particles_To_Add.clear();

    // remove dead Particle*s from Active
    for( setPart::iterator it = particles_To_Remove.begin();
         it != particles_To_Remove.end();
         it++ )
    {
        active_Particles.erase(*it);
    }
    particles_To_Remove.clear();
}
void addOrRemovePhysics()
{
    Autolock lock( &muSetPhys );

    // add new PhysicsEntity*s to Active
    for( setPhys::iterator it = physics_To_Add.begin();
         it != physics_To_Add.end();
         it++ )
    {
        active_Physics.insert(*it);
    }
    physics_To_Add.clear();

    // remove dead PhysicsEntity*s from Active
    for( setPhys::iterator it = physics_To_Remove.begin();
         it != physics_To_Remove.end();
         it++ )
    {
        active_Physics.erase(*it);
    }
    physics_To_Remove.clear();
}
