/*
Copyright (C) 2007, 2010 - Bit-Blot

This file is part of Aquaria.

Aquaria 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.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "ScriptedEntity.h"
#include "DSQ.h"
#include "Game.h"
#include "Avatar.h"
#include "Shot.h"

bool ScriptedEntity::runningActivation = false;

ScriptedEntity::ScriptedEntity(const std::string &scriptName, Vector position, EntityType et) : CollideEntity(), Segmented(2, 26)
{
	addType(SCO_SCRIPTED_ENTITY);
	crushDelay = 0;
	script = 0;
	songNoteFunction = songNoteDoneFunction = true;
	addChild(&pullEmitter, PM_STATIC);

	hair = 0;
	becomeSolidDelay = false;
	strandSpacing = 10;
	animKeyFunc = true;
	//runningActivation = false;

	setEntityType(et);
	myTimer = 0;
	layer = LR_ENTITIES;
	surfaceMoveDir = 1;
	this->position = position;
	numSegments = 0;
	reverseSegments = false;
	manaBallAmount = 1;
	this->name = scriptName;

	std::string file;
	if (!scriptName.empty())
	{
		if (scriptName[0]=='@' && dsq->mod.isActive())
		{
			file = dsq->mod.getPath() + "scripts/" + scriptName.substr(1, scriptName.size()) + ".lua";
			this->name = scriptName.substr(1, scriptName.size());
		}
		else if (dsq->mod.isActive())
		{
			file = dsq->mod.getPath() + "scripts/" + scriptName + ".lua";

			if (!exists(file))
			{
				file = "scripts/entities/" + scriptName + ".lua";
			}
		}
		else
		{
			file = "scripts/entities/" + scriptName + ".lua";
		}
	}
	script = dsq->scriptInterface.openScript(file);
	if (!script)
	{
		debugLog("Could not load script [" + file + "]");
	}
}

void ScriptedEntity::setAutoSkeletalUpdate(bool v)
{
	skeletalSprite.ignoreUpdate = !v;
}

void ScriptedEntity::message(const std::string &msg, int v)
{
	if (script)
	{
		if (!script->call("msg", this, msg.c_str(), v))
			debugLog(name + " : msg : " + script->getLastError());
	}
	Entity::message(msg, v);
}

int ScriptedEntity::messageVariadic(lua_State *L, int nparams)
{
	if (script)
	{
		int res = script->callVariadic("msg", L, nparams, this);
		if (res < 0)
			luaDebugMsg("msg", script->getLastError());
		else
			return res;
	}
	return Entity::messageVariadic(L, nparams);
}

void ScriptedEntity::warpSegments()
{
	Segmented::warpSegments(position);
}

void ScriptedEntity::init()
{	
	if (script)
	{
		if (!script->call("init", this))
			luaDebugMsg("init", script->getLastError());
	}

	Entity::init();
}

void ScriptedEntity::postInit()
{	
	if (script)
	{
		if (!script->call("postInit", this))
			luaDebugMsg("postInit", script->getLastError());
	}

	Entity::postInit();
}

void ScriptedEntity::initEmitter(int emit, const std::string &file)
{
	if (emitters.size() <= emit)
	{
		emitters.resize(emit+1);
	}
	if (emitters[emit] != 0)
	{
		errorLog("Trying to init emitter being used");
		return;
	}
	emitters[emit] = new ParticleEffect;
	addChild(emitters[emit], PM_POINTER);
	emitters[emit]->load(file);
}

void ScriptedEntity::startEmitter(int emit)
{
	if (emitters[emit])
	{
		emitters[emit]->start();
	}
}

void ScriptedEntity::stopEmitter(int emit)
{
	if (emitters[emit])
	{
		emitters[emit]->stop();
	}
}

ParticleEffect *ScriptedEntity::getEmitter(int emit)
{
	return (size_t(emit) < emitters.size()) ? emitters[emit] : NULL;
}

int ScriptedEntity::getNumEmitters() const
{
	return emitters.size();
}

void ScriptedEntity::registerNewPart(RenderObject *r, const std::string &name)
{
	partMap[name] = r;
}

void ScriptedEntity::initSegments(int numSegments, int minDist, int maxDist, std::string bodyTex, std::string tailTex, int w, int h, float taper, bool reverseSegments)
{
	this->reverseSegments = reverseSegments;
	this->numSegments = numSegments;
	this->minDist = minDist;
	this->maxDist = maxDist;
	segments.resize(numSegments);
	for (int i = segments.size()-1; i >= 0 ; i--)
	{
		Quad *q = new Quad;
		if (i == segments.size()-1)
			q->setTexture(tailTex);
		else
			q->setTexture(bodyTex);
		q->setWidthHeight(w, h);
		
		if (i > 0 && i < segments.size()-1 && taper !=0)
			q->scale = Vector(1.0f-(i*taper), 1-(i*taper));
		dsq->game->addRenderObject(q, LR_ENTITIES);
		segments[i] = q;
	}
	Segmented::initSegments(position);
}

void ScriptedEntity::setupEntity(const std::string &tex, int lcode)
{
	setEntityType(ET_NEUTRAL);
	if (!tex.empty())
		setTexture(tex);

	updateCull = -1;
	manaBallAmount = 0;
	setState(STATE_IDLE);

	this->layer = dsq->getEntityLayerToLayer(lcode);
}

void ScriptedEntity::setupBasicEntity(const std::string& texture, int health, int manaBall, int exp, int money, float collideRadius, int state, int w, int h, int expType, bool hitEntity, int updateCull, int layer)
{
	//this->updateCull = updateCull;
	updateCull = -1;

	if (texture.empty())
		renderQuad = false;
	else
		setTexture(texture);
	this->health = maxHealth = health;
	this->collideRadius = collideRadius;
	setState(state);
	this->manaBallAmount = manaBall;
	width = w;
	height = h;

	setEntityLayer(layer);
}

void ScriptedEntity::setEntityLayer(int lcode)
{
	this->layer = dsq->getEntityLayerToLayer(lcode);
}

void ScriptedEntity::initStrands(int num, int segs, int dist, int strandSpacing, Vector color)
{
	this->strandSpacing = strandSpacing;
	strands.resize(num);
	for (int i = 0; i < strands.size(); i++)
	{
		strands[i] = new Strand(position, segs, dist);
		strands[i]->color = color;
		dsq->game->addRenderObject(strands[i], this->layer);
	}
	updateStrands(0);
}

void ScriptedEntity::onAlwaysUpdate(float dt)
{
	Entity::onAlwaysUpdate(dt);

	updateStrands(dt);

	if (!isEntityDead() && getState() != STATE_DEAD && getState() != STATE_DEATHSCENE && isPresent())
	{
		if (frozenTimer > 0)
		{
			pullEmitter.update(dt);

			doFriction(dt, 50);
			updateCurrents(dt);
			updateMovement(dt);

			if (hair)
			{
				setHairHeadPosition(position);
				updateHair(dt);
			}

			if (skeletalSprite.isLoaded())
				dsq->game->handleShotCollisionsSkeletal(this);
			else
				dsq->game->handleShotCollisions(this);
		}

		if (isPullable() && !fillGridFromQuad)
		{
			bool doCrush = false;
			crushDelay -= dt;
			if (crushDelay < 0)
			{
				crushDelay = 0.2;
				doCrush = true;
			}
			FOR_ENTITIES(i)
			{
				Entity *e = *i;
				if (e && e != this && e->life == 1 && e->ridingOnEntity != this)
				{
					if ((e->position - this->position).isLength2DIn(collideRadius + e->collideRadius))
					{
						if (this->isEntityProperty(EP_BLOCKER) && doCrush)
						{
							bool doit = !vel.isLength2DIn(64) || (e->position.y > position.y && vel.y > 0);
							if (doit)
							{
								if (e->getEntityType() == ET_ENEMY && e->isDamageTarget(DT_CRUSH))
								{
									DamageData d;
									d.damageType = DT_CRUSH;
									d.attacker = this;
									d.damage = 1;
									if (e->damage(d))
									{
										e->sound("RockHit");
										dsq->spawnParticleEffect("rockhit", e->position, 0, 0);
									}
									//e->push(vel, 0.2, 500, 0);
									Vector add = vel;
									add.setLength2D(5000*dt);
									e->vel += add;
								}
							}
						}
						Vector add = e->position - this->position;
						add.capLength2D(10000 * dt);
						e->vel += add;
						e->doCollisionAvoidance(dt, 3, 1);
					}
				}
			}
		}

		if (isPullable())
		{		
			Entity *followEntity = dsq->game->avatar;
			if (followEntity && dsq->game->avatar->pullTarget == this)
			{
				Vector dist = followEntity->position - this->position;
				if (dist.isLength2DIn(followEntity->collideRadius + collideRadius + 16))
				{
					vel = 0;
				}
				else if (!dist.isLength2DIn(800))
				{
					// break;
					vel.setZero();
					dsq->game->avatar->pullTarget->stopPull();
					dsq->game->avatar->pullTarget = 0;
				}
				else if (!dist.isLength2DIn(128))
				{				
					Vector v = dist;
					int moveSpeed = 1000;
					moveSpeed = 4000;
					v.setLength2D(moveSpeed);
					vel += v*dt;
					setMaxSpeed(dsq->game->avatar->getMaxSpeed());
				}
				else
				{
					if (!vel.isZero())
					{
						Vector sub = vel;
						sub.setLength2D(getMaxSpeed()*maxSpeedLerp.x*dt);
						vel -= sub;
						if (vel.isLength2DIn(100))
							vel = 0;
					}
				}
				doCollisionAvoidance(dt, 2, 0.5);
			}
		}
	}
}

void ScriptedEntity::updateStrands(float dt)
{
	if (strands.empty()) return;
	float angle = rotation.z;
	angle = (PI*(360-(angle-90)))/180.0;
	//angle = (180*angle)/PI;
	float sz = (strands.size()/2);
	for (int i = 0; i < strands.size(); i++)
	{
		float diff = (i-sz)*strandSpacing;
		if (diff < 0)
			strands[i]->position = position - Vector(sinf(angle)*fabsf(diff), cosf(angle)*fabsf(diff));
		else
			strands[i]->position = position + Vector(sinf(angle)*diff, cosf(angle)*diff);
		if (dt > 0)
			strands[i]->update(dt);
	}
}

void ScriptedEntity::destroy()
{
	CollideEntity::destroy();

	if (script)
	{
		dsq->scriptInterface.closeScript(script);
		script = 0;
	}
}

void ScriptedEntity::song(SongType songType)
{
	if (script)
	{
		if (!script->call("song", this, int(songType)))
			debugLog(name + " : " + script->getLastError());
	}
}

void ScriptedEntity::shiftWorlds(WorldType lastWorld, WorldType worldType)
{
	if (script)
	{
		if (!script->call("shiftWorlds", this, int(lastWorld), int(worldType)))
			debugLog(name + " : " + script->getLastError() + " shiftWorlds");
	}
}

void ScriptedEntity::startPull()
{	
	Entity::startPull();
	beforePullMaxSpeed = getMaxSpeed();
	becomeSolidDelay = false;
	debugLog("HERE!");
	if (isEntityProperty(EP_BLOCKER))
	{
		fillGridFromQuad = false;
		dsq->game->reconstructEntityGrid();
	}
	pullEmitter.load("Pulled");
	pullEmitter.start();

	// HACK: move this to the lower level at some point

	if (isEntityProperty(EP_BLOCKER))
	{
		FOR_ENTITIES(i)
		{
			Entity *e = *i;
			if (e != this && e->getEntityType() != ET_AVATAR && e->isv(EV_CRAWLING, 1))
			{
				if ((e->position - position).isLength2DIn(collideRadius+e->collideRadius+32))
				{
					debugLog(e->name + ": is now riding on : " + name);
					e->ridingOnEntity = this;
					e->ridingOnEntityOffset = e->position - position;
					e->ridingOnEntityOffset.setLength2D(collideRadius);
				}
			}
		}
	}
}

void ScriptedEntity::sporesDropped(const Vector &pos, int type)
{
	if (script)
	{
		script->call("sporesDropped", this, pos.x, pos.y, type);
	}
}

void ScriptedEntity::stopPull()
{
	Entity::stopPull();
	pullEmitter.stop();
	setMaxSpeed(beforePullMaxSpeed);
}

void ScriptedEntity::onUpdate(float dt)
{
	BBGE_PROF(ScriptedEntity_onUpdate);

	CollideEntity::onUpdate(dt);

	if (becomeSolidDelay)
	{
		if (vel.isLength2DIn(5))
		{
			if (!isEntityInside())
			{
				becomeSolid();
				becomeSolidDelay = false;
			}
		}
	}


	if (life != 1 || isEntityDead()) return;


	if (myTimer > 0)
	{
		myTimer -= dt;
		if (myTimer <= 0)
		{
			myTimer = 0;
			onExitTimer();
		}
	}

	if (this->isEntityDead() || this->getState() == STATE_DEATHSCENE || this->getState() == STATE_DEAD)
	{
		return;
	}
	if (script)
	{
		if (!script->call("update", this, dt))
			debugLog(name + " : update : " + script->getLastError());
	}

	if (numSegments > 0)
	{
		updateSegments(position, reverseSegments);
		updateAlpha(alpha.x);
	}
}

void ScriptedEntity::resetTimer(float t)
{
	myTimer = t;
}

void ScriptedEntity::stopTimer()
{
	myTimer = 0;
}

void ScriptedEntity::onExitTimer()
{
	if (script)
	{
		if (!script->call("exitTimer", this))
			debugLog(this->name + " : " + script->getLastError() + " exitTimer");
	}
}

void ScriptedEntity::onAnimationKeyPassed(int key)
{
	if (script && animKeyFunc)
	{
		if (!script->call("animationKey", this, key))
		{
			debugLog(this->name + " : " + script->getLastError() + " animationKey");
			animKeyFunc = false;
		}
	}

	Entity::onAnimationKeyPassed(key);
}

void ScriptedEntity::lightFlare()
{
	if (script && !isEntityDead())
	{
		script->call("lightFlare", this);
	}
}

bool ScriptedEntity::damage(const DamageData &d)
{
	if (d.damageType == DT_NONE)	return false;
	bool doDefault = true;
	if (script)
	{
		if (!script->call("damage", this, d.attacker, d.bone, int(d.damageType), d.damage, d.hitPos.x, d.hitPos.y, d.shot, &doDefault))
		{
			debugLog(name + ": damage function failed");
		}
	}

	if (doDefault)
	{
		return Entity::damage(d);
	}

	return false;
}

void ScriptedEntity::songNote(int note)
{
	Entity::songNote(note);

	if (script && songNoteFunction)
	{
		if (!script->call("songNote", this, note))
		{
			songNoteFunction = false;
			debugLog(this->name + " : " + script->getLastError() + " songNote");
		}
	}
}

void ScriptedEntity::songNoteDone(int note, float len)
{
	Entity::songNoteDone(note, len);
	if (script && songNoteDoneFunction)
	{
		if (!script->call("songNoteDone", this, note, len))
		{
			songNoteDoneFunction = false;
			debugLog(this->name + " : " + script->getLastError() + " songNoteDone");
		}
	}
}

void ScriptedEntity::becomeSolid()
{
	//vel = 0;
	float oldRot = 0;
	bool doRot=false;
	Vector n = dsq->game->getWallNormal(position);
	if (!n.isZero())
	{
		oldRot = rotation.z;
		rotateToVec(n, 0);
		doRot = true;
	}
	fillGridFromQuad = true;
	dsq->game->reconstructEntityGrid();

	FOR_ENTITIES(i)
	{
		Entity *e = *i;
		if (e->ridingOnEntity == this)
		{
			e->ridingOnEntity = 0;
			e->moveOutOfWall();
			// if can't get the rider out of the wall, kill it
			if (dsq->game->isObstructed(TileVector(e->position)))
			{
				e->setState(STATE_DEAD);
			}
		}
	}

	if (doRot)
	{
		rotation.z = oldRot;
		rotateToVec(n, 0.01);
	}
}

void ScriptedEntity::onHitWall()
{
	if (isEntityProperty(EP_BLOCKER) && !fillGridFromQuad && dsq->game->avatar->pullTarget != this)
	{
		becomeSolidDelay = true;
	}
	
	if (isEntityProperty(EP_BLOCKER) && !fillGridFromQuad)
	{
		Vector n = dsq->game->getWallNormal(position);
		if (!n.isZero())
		{
			rotateToVec(n, 0.2);
		}
	}

	CollideEntity::onHitWall();

	if (script)
	{
		if (!script->call("hitSurface", this))
			debugLog(this->name + " : " + script->getLastError() + " hitSurface");
	}
}

void ScriptedEntity::activate()
{	
	if (runningActivation) return;
	Entity::activate();
	
	runningActivation = true;
	if (script)
	{
		if (!script->call("activate", this))
			luaDebugMsg("activate", script->getLastError());
	}
	runningActivation = false;
}

void ScriptedEntity::shotHitEntity(Entity *hit, Shot *shot, Bone *bone)
{
	Entity::shotHitEntity(hit, shot, bone);

	if (script)
	{
		script->call("shotHitEntity", this, hit, shot, bone);
	}
}

void ScriptedEntity::entityDied(Entity *e)
{
	CollideEntity::entityDied(e);

	if (script)
	{
		script->call("entityDied", this, e);
	}
}

void ScriptedEntity::luaDebugMsg(const std::string &func, const std::string &msg)
{
	debugLog("luaScriptError: " + name + " : " + func + " : " + msg);
}

void ScriptedEntity::onDieNormal()
{
	Entity::onDieNormal();
	if (script)
	{
		script->call("dieNormal", this);
	}
}

void ScriptedEntity::onDieEaten()
{
	Entity::onDieEaten();
	if (script)
	{
		script->call("dieEaten", this);
	}
}

void ScriptedEntity::onEnterState(int action)
{
	CollideEntity::onEnterState(action);
	
	if (script)
	{
		if (!script->call("enterState", this))
			luaDebugMsg("enterState", script->getLastError());
	}
	switch(action)
	{
	case STATE_DEAD:
		if (!isGoingToBeEaten())
		{			
			doDeathEffects(manaBallAmount);
			dsq->spawnParticleEffect(deathParticleEffect, position);
			onDieNormal();
		}
		else
		{
			// eaten
			doDeathEffects(0);
			onDieEaten();
		}
		destroySegments(1);


		for (int i = 0; i < strands.size(); i++)
		{
			strands[i]->safeKill();
		}
		strands.clear();

		// BASE ENTITY CLASS WILL HANDLE CLEANING UP HAIR
	break;
	}
}

void ScriptedEntity::onExitState(int action)
{
	
	if (script)
	{
		if (!script->call("exitState", this))
			luaDebugMsg("exitState", script->getLastError());
	}

	CollideEntity::onExitState(action);
}

void ScriptedEntity::deathNotify(RenderObject *r)
{
	if (script)
	{
		if (!script->call("deathNotify", this, r))
			luaDebugMsg("deathNotify", script->getLastError());
	}
	CollideEntity::deathNotify(r);
}

