/*******************************************************************
*
*    DESCRIPTION: Code for actors and sprites
*
*    AUTHOR: Andrew Kesterson andrew@aklabs.net
*
*    HISTORY: $Log: Actor.cpp,v $
*    HISTORY: Revision 1.2  2006/01/01 19:10:00  andrew
*    HISTORY: Added Robert's STLINI library and started work on the ConfigEngine. Moved some definitions around, and changed the order of includes to prevent broken declaration/definitions.
*    HISTORY:
*
*    DATE:12/31/2005
*
*******************************************************************/

/** include files **/

#include "MogCommon.h"

#warning This file still contains FIXMEs.

/** local definitions **/

/* default settings */

/** external functions **/

/** external data **/

/** internal functions **/

// returns true if flag y in bitmask x
#define HASBIT(x, y) (x & y == y)
// toggles flag y in bitmask x
#define TOGGLEBIT(x, y) (x = x ^ y)
void moveBBox(BoundingBox *bbox, int x, int y);

/** public data **/

/** private data **/

/** public functions **/

/** private functions **/


void moveBBox(BoundingBox *bbox, int x, int y)
{
	if (bbox != NULL) {
		bbox->sx += x;
		bbox->sy += y;
	}
}

ActorCmd::ActorCmd()
{
	this->cmd = 0;
	this->cmdFlags = 0;
	this->target = 0;
}

ActorCmd::ActorCmd(unsigned int c, unsigned int cf, void *t)
{
	this->cmd = c;
	this->cmdFlags = cf;
	this->target = t;
}

Sprite2D::Sprite2D()
{
	this->bitmap = NULL;
	this->fh = 0;
	this->fw = 0;
	this->numframes = 0;
	this->frameticks = 0;
}

Sprite2D::Sprite2D(char *gfxname, long ticks, int frames, int fw, int fh)
{
	this->init(gfxname,ticks ,frames ,fw ,fh);
}

void Sprite2D::init(char *gfxname, long ticks, int frames, int fw, int fh)
{
	this->bitmap = IMG_Load(gfxname);
	if ( this->bitmap == NULL ) {
		cout << "failed to load bitmap : " << gfxname << ".\n";
		exit(1);
	}
	else {
		this->fh = fh;
		this->fw = fw;
		this->numframes = frames;
		this->frameticks = ticks;
	}
}

int Sprite2D::getfh()
{
	return this->fh;
}

int Sprite2D::getfw()
{
	return this->fw;
}

int Sprite2D::getframes()
{
	return this->numframes;
}

int Sprite2D::getticks()
{
	return this->frameticks;
}

void Sprite2D::drawFrame(SDL_Surface *canvas, int frame, int px, int py, int flip)
{
	int sx, sy;
	
	sx = 0 + (this->fw * frame);
	sy = 0;
	
	if ( this->bitmap == NULL ) {
		cout << "I was passed a NULL bitmap. EEK! (the cat)\n";
	}

	// check for out of bounds image copy
	if ( sx > this->bitmap->w || sy > this->bitmap->h) {
		// EEK! (the cat)
		// re-call this with frame 0 and return
		drawFrame(canvas, 0,px,py, flip);
		return;
	}
	
	if ( flip == 0) {
		// don't flip
		// (deprecated, I need to get rid of this)
		SDL_Rect srcrect = {sx, sy, this->fw, this->fh};
		SDL_Rect dstrect = {px, py, this->fw, this->fh};
		SDL_BlitSurface(this->bitmap, &srcrect, canvas , &dstrect);
	}
	else if ( flip == 1 ) {
		// mirror  X
	}
	else if ( flip == 2 ) {
		// flip Y
	}

}

Sprite2D::~Sprite2D()
{
    if ( this->bitmap != NULL ) {
		SDL_FreeSurface(this->bitmap);
	}
}

Actor2D::Actor2D()
{
	this->sprite = NULL;
	this->init(NULL, 0);
}

Actor2D::Actor2D(Sprite2D *spr, float speedOverride)
{
	this->init(spr,speedOverride );
}

// FIXME: change the speedOverride stuff to use the new array, instead of
// a constant override.
void Actor2D::init(Sprite2D *spr, float speedOverride)
{
    for ( int i = 0; i < NUMSTATES; i++ ) {
		this->stateSprites[i] = NULL;
		// NULL sprites in the stateSprites will make
		// the sprite use its default sprite (spr)
	}
	if ( spr != NULL ) {
		this->sprite = spr;
		this->bbox.w = this->sprite->getfw();
		this->bbox.h = this->sprite->getfh();
	}
	//this->animSpeedOverride = speedOverride;
	this->animCurrFrame = 0;
	this->animCurrTicks = 0;
	this->bbox.sx = this->px = 0;
	this->bbox.sy = this->py = 0;
    this->lastTicks = 0;
	this->vx = 0;
	this->vy = 0;
	this->state = 0;
}

int Actor2D::getpx()
{
	return px;
}

int Actor2D::getpy()
{
	return py;
}

int Actor2D::getvx()
{
	return vx;
}

int Actor2D::getvy()
{
	return vy;
}

void Actor2D::move(int x, int y)
{
	px += x;
	py += y;
}

void Actor2D::moveAbs(int x, int y)
{
	px = x;
	py = y;
}

void Actor2D::setVelocity(int x, int y)
{
	vx = x;
	vy = y;
}

void Actor2D::update()
{
	//long newticks = SDL_GetTicks();
	long newticks = clock();
	long elapsed = newticks - this->lastTicks;
	this->lastTicks = newticks;

	static long lastState = this->state;
	lastState = this->state;

	this->procQueue();

    if ( this->hasState(ACTOR_MOVELEFT) ) this->px -= this->vx;
	if ( this->hasState(ACTOR_MOVERIGHT) ) this->px += this->vx;
	if ( this->hasState(ACTOR_MOVEDOWN) ) this->py += this->vy;
	if ( this->hasState(ACTOR_MOVEUP) ) this->py -= this->vy;
    

	// FIXME: Sprite timing is semi-broken.
	if ( elapsed >= sprite->getticks() && sprite->getframes() > 1 && sprite != NULL ) {
		this->animCurrFrame++;
        if ( this->animCurrFrame >= sprite->getframes() ) {
			this->animCurrFrame = 0;
		}
	}

	moveBBox(&this->bbox, this->vx, this->vy);
}

void Actor2D::render(SDL_Surface *canvas)
{
    this->sprite->drawFrame(canvas, this->animCurrFrame, 
							this->px, this->py, 0);
}

long Actor2D::getState()
{
	return this->state;
}

void Actor2D::setState(long state)
{
	this->state = state;
}

void Actor2D::addState(long newstate)
{
	if (this->hasState(newstate)) return;
	else this->state = this->state | newstate;
}

void Actor2D::delState(long oldstate)
{
	if ( this->hasState(oldstate) ) {
        this->state = this->state ^ oldstate;
	}
    else {
		return;
	}
}

int Actor2D::hasState(long checkstate)
{
	if ( (this->state & checkstate) == checkstate) {
		return true;
	}
	else {
		return false;
	}
}

void Actor2D::setSprite(Sprite2D *spr)
{
	if ( this->sprite != spr && spr != NULL ) {
		this->sprite = spr;
		this->animCurrTicks = 0;
		this->animCurrFrame = 0;
		this->lastTicks = 0;
	}
}

void Actor2D::setStateSprite(Sprite2D *spr, long state)
{
	if ( spr != NULL ) {
		this->stateSprites[state] = spr;
	}
}

void Actor2D::command(int cmd, int flags, void *target)
{
    this->cmdList.push(ActorCmd(cmd, flags, target));
}

void Actor2D::command(ActorCmd cmd)
{
	this->cmdList.push(cmd);
}

void Actor2D::procQueue()
{
	ActorCmd cmd;
	Sprite2D *n = NULL;
	// process the actor's command queue
	while ( !this->cmdList.empty() ) {
        cmd = this->cmdList.front();
        switch ( cmd.cmd ) {
		case ACTOR_MOVELEFT:
            this->addState(ACTOR_MOVELEFT);
            this->setSprite(this->stateSprites[ACTOR_MOVELEFT]);
            break;
		case ACTOR_MOVERIGHT:
            this->addState(ACTOR_MOVERIGHT);
            this->setSprite(this->stateSprites[ACTOR_MOVERIGHT]);
            break;
		case ACTOR_MOVEDOWN:
            this->addState(ACTOR_MOVEDOWN);
            this->setSprite(this->stateSprites[ACTOR_MOVEDOWN]);
			break;
		case ACTOR_MOVEUP:
            this->addState(ACTOR_MOVEUP);
			this->setSprite(this->stateSprites[ACTOR_MOVEUP]);
            break;
		case ACTOR_ATTACK:
			this->addState(ACTOR_ATTACK);
			this->setSprite(this->stateSprites[ACTOR_ATTACK]);
			break;
		case ACTOR_IDLE:
            // stop all of the current states
			if ( !cmd.cmdFlags ) {
				this->setState(ACTOR_IDLE);
			}
			// (or) get rid of any states outlined in the cmdFlags
			else this->delState(cmd.cmdFlags);

            if ( this->hasState(ACTOR_MOVELEFT) ) { 
				n = this->stateSprites[SPR_STILLLEFT];
			}
			else if (this->hasState(ACTOR_MOVERIGHT) ) {
				n = this->stateSprites[SPR_STILLRIGHT];
			}
			else if (this->hasState(ACTOR_MOVEUP) ) {
				n = this->stateSprites[SPR_STILLUP];
			}
			else if ( this->hasState(ACTOR_MOVEDOWN) ) {
				n = this->stateSprites[SPR_STILLDOWN];
			}
			if ( n != NULL ) this->setSprite(n);

            break;
		case ACTOR_SEEK:
            this->addState(ACTOR_SEEK);
			break;
		case ACTOR_SET_TYPE:
			this->addState(cmd.cmdFlags);
			break;
		default:
			break;
		}
		this->cmdList.pop();;
	}
}
 
bool Actor2D::collidesWith(Actor2D *actor)
{
	if ( ((actor->bbox.sx > this->bbox.sx && 
		  actor->bbox.sx < this->bbox.sx + this->bbox.w) && 
		 (actor->bbox.sy > this->bbox.sy &&
		  actor->bbox.sy < this->bbox.sy + this->bbox.h)) ) {
		// ^^^ actor is inside of this
		return false;
	}
    else if ((this->bbox.sx > actor->bbox.sx && 
		  this->bbox.sx < actor->bbox.sx + actor->bbox.w) && 
		 (this->bbox.sy > actor->bbox.sy &&
		  this->bbox.sy < actor->bbox.sy + actor->bbox.h)){
		// ^^^ this is inside of actor
		return false;
	}
	else return true;
}

// FIXME: change the speedOverride stuff to use the new array, instead of
// a constant override.
AIActor::AIActor(Sprite2D *spr, float speedOverride, 
				 unsigned int target, unsigned int maxrange)
{
    this->primTarget = target;
	this->range = maxrange; 
	this->target = NULL;
	this->sprite = NULL;
	this->setSprite(spr);
	//this->animSpeedOverride = speedOverride;
}

// FIXME: ugly.
void AIActor::updateAI(std::vector<Actor2D *> actorList)
{
	// check the state and position of each actor within range of the AI
	// to see if we need to react to them (ie, if they will change our state)
	// this is the "default" AI that will wander in the given area until
	// a creature of the appropriate type walks into the area, and then it will
	// move to it to attack it

    Actor2D *actor = NULL;
    unsigned int dist;
	double dX = 0.00;
	double dY = 0.00;

    if ( this->target == NULL || (this->target != NULL && this->target->hasState(ACTOR_DEAD))) {
		for (unsigned int i = 0; i < actorList.size() ; i++ ) {
			actor = actorList.at(i);
			if ( actor->hasState(this->primTarget) ) {
				this->target = actor;
				break;			
			}
			else continue;
		}
	}
	
	if ( this->target == NULL ) {
		this->command(ACTOR_IDLE, 0, NULL);
		return;
	}

    // the distance is calculated as the hypotenuse of a triangle between the two sprites and
	// an imaginary third converging point somewhere at a right angle. There's probly a faster way to
	// do this.

	dX = this->px - this->target->getpx();
	dX *= dX;
	dY = this->py - this->target->getpy();
	dY *= dY;
	dist = (unsigned int) sqrt( dX + dY );
    
    if ( dist < this->range || this->target == actor) {
        if ( !this->collidesWith(this->target) ) {
			this->command(ACTOR_IDLE, 0, NULL);
            if ( this->target->getpx() > this->px ) {
                this->command(ACTOR_MOVERIGHT, 0, NULL);
			}
			else if ( this->target->getpx() < this->px ) {
                this->command(ACTOR_MOVELEFT, 0, NULL);
			}
			if ( this->target->getpy() > this->py ) {
                this->command(ACTOR_MOVEDOWN, 0, NULL);
			}
			if ( this->target->getpy() < this->py ) {
                this->command(ACTOR_MOVEUP, 0, NULL);
			}
			this->command(ACTOR_SEEK, 0, this->target);
			// do a little cheating here.... (stop the "jitter" effect)
			if ( dist < (unsigned int) this->vx ) {
                this->px = target->getpx();
			}
			if ( dist < (unsigned int) this->vy ) {
				this->py = target->getpy();
			}
		}
		else {
			this->command(ACTOR_ATTACK, 0, this->target);
		}
	}
}
