// Description:
//   Particles are grouped into ParticleGroups. 
//
// Copyright (C) 2001 Frank Becker
//
// 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 2 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
//
#include <Trace.hpp>
#include <Particles.hpp>
#include <ParticleGroup.hpp>
#include <FindHash.hpp>

hash_map< const string, ParticleType*, hash<const string>, equal_to<const string> > 
    ParticleGroup::_particleTypeMap;

ParticleGroup::ParticleGroup( const string &groupName, int numParticles):
    _particles( 0),
    _aliveCount( 0),
    _groupName( groupName),
    _numParticles( numParticles)
{
    XTRACE();
    _usedList.next = 0;
}

ParticleGroup::~ParticleGroup()
{
    XTRACE();

    ParticleInfo *p = _usedList.next;
    while( p)
    {
	p->tod = 0;
	p->particle->update( p);
	p = p->next; 
    }

    delete[] _particles;
}

void ParticleGroup::reset( void)
{
    XTRACE();

    //trigger particles in use to die
    ParticleInfo *p = _usedList.next;
    while( p)
    {
	p->tod = 0;
	p->particle->update( p);
	p = p->next; 
    }

    //reset free/used lists
    _freeList.next = &_particles[0];
    for( int i=0; i<_numParticles-1; i++)
    {
        _particles[i].next = &_particles[i+1];
    }
    _particles[_numParticles-1].next = 0;
    _usedList.next = 0;
    _aliveCount = 0;
}

bool ParticleGroup::init( void)
{
    XTRACE();

    //FIXME: do we really need to restrict size?
    if( _numParticles > MAX_PARTICLES_PER_GROUP)
    {
        _numParticles = MAX_PARTICLES_PER_GROUP;
    }

    _particles = new ParticleInfo[ _numParticles];

    reset();

    static bool initialized = false;
    if( !initialized)
    {
	new HeroStinger();
	new StingerTrail();
	new SmokePuff();
	new MiniSmoke();
	new ExplosionPiece();
        new PlasmaBullet();
        new Bonus1( "SuperBonus", 1000);
        new Bonus1( "Bonus1", 100);
	new ScoreHighlight();
        new EnergyBlob();
        new ShieldBoost();
        new ArmorPierce();
        new WeaponUpgrade();
	new StatusMessage();
	new Spark();
	new BallOfFire();
	new BallOfIce();
	new BallOfPoison();
	new Phaser();
	new FireSpark( "FireSpark1");
	new FireSpark( "FireSpark2");
	new FireSpark( "FireSpark3");
	new FlankBurst( "FlankBurstLeft");
	new FlankBurst( "FlankBurstRight");
	HeroS::instance();

        initialized = true;
    }

    return true;
}

void ParticleGroup::newParticle( 
    const string & name, const ParticleInfo &pi)
{
//    XTRACE();
    ParticleType *particleType = getParticleType( name);
    if( !particleType)
    {
	LOG_ERROR << "Unknown particleType [" << name << "]" << endl;
	return;
    }
    newParticle( particleType, pi);
}

void ParticleGroup::newParticle( 
    ParticleType *particleType, const ParticleInfo &pi)
{
//    XTRACE();
    ParticleInfo *p = _freeList.next;
    if( !p)
    {
	LOG_ERROR << _groupName << " is out of particles!" << endl;
	return;
    }

    _freeList.next = p->next;
    p->next = _usedList.next;
    _usedList.next = p;

    p->particle = particleType;
    p->position = pi.position;
    p->velocity = pi.velocity;
    p->extra = pi.extra;
    p->color = pi.color;
    p->text = pi.text;
    p->damage = pi.damage;

    //particle initializes particle info
    particleType->init( p);

    _aliveCount++;

    return;
}

void ParticleGroup::newParticle( 
    const string & name, float x, float y, float z)
{
//    XTRACE();
    ParticleType *particleType = getParticleType( name);
    if( !particleType)
    {
	LOG_ERROR << "Unknown particleType [" << name << "]" << endl;
	return;
    }
    newParticle( particleType, x, y, z);
}

void ParticleGroup::newParticle( 
    ParticleType *particleType, float x, float y, float z)
{
//    XTRACE();
    ParticleInfo *p = _freeList.next;
    if( !p)
    {
	LOG_ERROR << _groupName << " is out of particles!" << endl;
	return;
    }

    _freeList.next = p->next;
    p->next = _usedList.next;
    _usedList.next = p;

    p->particle = particleType;
    p->position.x = x;
    p->position.y = y;
    p->position.z = z;

    //particle initializes particle info
    particleType->init( p);

    _aliveCount++;

    return;
}

//FIXME: find better place for this...
void ParticleGroup::detectCollisions( ParticleGroup *pg)
{
//    XTRACE();
    ParticleInfo *p1 = _usedList.next;
    while( p1)
    {
	ParticleInfo *p2 = pg->_usedList.next;
	while( p2)
	{
#if 0
            if( (fabs(p1->position.x - p2->position.x) < 20) &&
                (fabs(p1->position.y - p2->position.y) < 20))
#else
            float d2 = (p1->radius + p2->radius);
            d2 *= d2;
            float dx = (p2->position.x-p1->position.x);
            float dy = (p2->position.y-p1->position.y);
            float D2 = dx*dx + dy*dy;
            if( D2 < d2)
#endif
            {
                p1->particle->hit( p1, p2->damage);
                p2->particle->hit( p2, p1->damage);
                break;
            }
            
	    p2 = p2->next;
        }

        p1 = p1->next;
    }
}

void ParticleGroup::addParticleType( ParticleType *particleType)
{
    XTRACE();
    if( particleType)
    {
        LOG_INFO << "New Particle type: [" 
	         << particleType->name() << "]" << endl;

        _particleTypeMap[ particleType->name()] = particleType;
    }
}

ParticleType * ParticleGroup::getParticleType( const string particleTypeName)
{
//    XTRACE();
    ParticleType * particleType = findHash( particleTypeName, _particleTypeMap);
    if( !particleType)
    {
        LOG_ERROR << "ParticleGroup never heard of a " << particleTypeName 
                  << " before!" << endl;

        //trying first in list
        particleType = _particleTypeMap.begin()->second;
    }
    return particleType;
}
