#ifdef LUA_LIB_EXT
#include <lua5.1/lua.hpp>
#else
#include "lib/lua/include/lua.h"
#include "lib/lua/include/lualib.h"
#include "lib/lua/include/lauxlib.h"
#endif

#include "KAIK.h"
#include "IncGlobalAI.h"

CR_BIND(CKAIK, )
CR_REG_METADATA(CKAIK, (
	CR_MEMBER(ai),
	CR_SERIALIZER(Serialize),
	CR_POSTLOAD(PostLoad)
))

#ifdef USING_CREG
CREX_REG_STATE_COLLECTOR(CKAIK, CKAIK);
//
// #define CREX_REG_STATE_COLLECTOR(Name, RootClass)
//    static RootClass* Name ## State;
CKAIK* KAIKStateExt = CKAIKState;
#else
// NOTE: never gets dereferenced (serialization
// is disabled when USING_CREG is undefined, so
// the various PostLoad()'s do not get called)
CKAIK* KAIKStateExt = NULL;
#endif



#ifdef USING_CREG
// called instead of InitAI() on load if IsLoadSupported() returns 1
void CKAIK::Load(IGlobalAICallback* callback, std::istream* ifs) { CREX_SC_LOAD(KAIK, ifs); }
void CKAIK::Save(std::ostream* ofs) { CREX_SC_SAVE(KAIK, ofs); }

void CKAIK::PostLoad(void) {
	assert((ai != NULL) && ai->Initialized());
}

void CKAIK::Serialize(creg::ISerializer* s) {
	if (!ai->Initialized()) return;
	for (int i = 0; i < MAX_UNITS; i++) {
		CUNIT* u = ai->GetUnit(i);

		if (ai->ccb->GetUnitDef(i) != NULL) {
			// do not save non-existing units
			s->SerializeObjectInstance(u, u->GetClass());

			if (!s->IsWriting()) {
				u->uid = i;
			}
		} else if (!s->IsWriting()) {
			u->uid = i;
		}
	}

	s->SerializeObjectInstance(ai, ai->GetClass());
}
#endif

void CKAIK::InitAI(IGlobalAICallback* callback, int team) {
	ai = new AIClasses(callback); ai->Init();

	std::string verMsg =
		std::string(AI_NAME) + (ai->Initialized()? " initialized successfully!": " failed to initialize");
	std::string logMsg =
		ai->Initialized()? ("logging events to " + ai->GetLogger()->GetLogName()): "not logging events";

	ai->cb->SendTextMsg(verMsg.c_str(), 0);
	ai->cb->SendTextMsg(logMsg.c_str(), 0);
	ai->cb->SendTextMsg(AI_CREDITS, 0);
}

void CKAIK::ReleaseAI() {
	delete ai; ai = NULL;
}



void CKAIK::UnitCreated(int unitID, int builderID) {
	if (!ai->Initialized()) return;
	ai->uh->UnitCreated(unitID);
	ai->econTracker->UnitCreated(unitID);
}

void CKAIK::UnitFinished(int unitID) {
	if (!ai->Initialized()) return;
	ai->econTracker->UnitFinished(unitID);

	if (ai->cb->GetUnitDef(unitID) != NULL) {
		// let attackhandler handle cat_g_attack units
		if (GCAT(unitID) == CAT_G_ATTACK) {
			ai->ah->AddUnit(unitID);
		} else {
			ai->uh->IdleUnitAdd(unitID, ai->cb->GetCurrentFrame());
		}

		ai->uh->BuildTaskRemove(unitID);
	}
}

void CKAIK::UnitDestroyed(int unitID, int attackerUnitID) {
	if (!ai->Initialized()) return;
	ai->econTracker->UnitDestroyed(unitID);

	if (ai->GetUnit(unitID)->groupID != -1) {
		ai->ah->UnitDestroyed(unitID);
	}

	ai->uh->UnitDestroyed(unitID);
}

void CKAIK::UnitIdle(int unitID) {
	if (!ai->Initialized()) return;
	if (ai->GetUnit(unitID)->isDead) {
		return;
	}

	if (ai->uh->lastCapturedUnitFrame == ai->cb->GetCurrentFrame()) {
		if (unitID == ai->uh->lastCapturedUnitID) {
			// KLOOTNOTE: for some reason this also gets called when one
			// of our units is captured (in the same frame as, but after
			// HandleEvent(AI_EVENT_UNITCAPTURED)), *before* the unit has
			// actually changed teams (ie. for any unit that is no longer
			// on our team but still registers as such)
			ai->uh->lastCapturedUnitFrame = -1;
			ai->uh->lastCapturedUnitID = -1;
			return;
		}
	}

	// AttackHandler handles cat_g_attack units
	if (GCAT(unitID) == CAT_G_ATTACK && ai->GetUnit(unitID)->groupID != -1) {
		// attackHandler->UnitIdle(unit);
	} else {
		ai->uh->IdleUnitAdd(unitID, ai->cb->GetCurrentFrame());
	}
}

void CKAIK::UnitDamaged(int unitID, int attackerID, float damage, float3 dir) {
	if (!ai->Initialized()) return;
	if (ai->GetUnit(unitID)->isDead) {
		return;
	}
	ai->econTracker->UnitDamaged(unitID, damage);
}

void CKAIK::UnitMoveFailed(int unitID) {
	if (!ai->Initialized()) return;
	ai->uh->UnitMoveFailed(unitID);
}



void CKAIK::EnemyEnterLOS(int enemyUnitID) {
	if (!ai->Initialized()) return;
	//TODO
}

void CKAIK::EnemyLeaveLOS(int enemyUnitID) {
	if (!ai->Initialized()) return;
	//TODO
}

void CKAIK::EnemyEnterRadar(int enemyUnitID) {
	if (!ai->Initialized()) return;
	//TODO
}

void CKAIK::EnemyLeaveRadar(int enemyUnitID) {
	if (!ai->Initialized()) return;
	//TODO
}

void CKAIK::EnemyDestroyed(int enemyUnitID, int attackerUnitID) {
	if (!ai->Initialized()) return;
	ai->dgunConHandler->NotifyEnemyDestroyed(enemyUnitID, attackerUnitID);
	ai->thm->EnemyDestroyed(enemyUnitID, attackerUnitID);
}

void CKAIK::EnemyDamaged(int enemyUnitID, int attackerUnitID, float damage, float3 dir) {
	if (!ai->Initialized()) return;
	ai->thm->EnemyDamaged(enemyUnitID, attackerUnitID);
}

void CKAIK::EnemyCreated(int enemyUnitID) {
	if (!ai->Initialized()) return;
	ai->thm->EnemyCreated(enemyUnitID);
}
void CKAIK::EnemyFinished(int enemyUnitID) {
	if (!ai->Initialized()) return;
	ai->thm->EnemyFinished(enemyUnitID);
}



void CKAIK::RecvChatMessage(const char* msg, int player) {
	if (!ai->Initialized()) return;

	if ((msg = strstr(msg, "KAIK::")) == NULL) {
		return;
	}

	if ((msg = strstr(msg, "ThreatMap::DBG")) != NULL) {
		ai->thm->ToggleVisOverlay();
	}
}

void CKAIK::RecvLuaMessage(const char* inData, const char** outData) {
	*outData = inData;
}


int CKAIK::HandleEvent(int msg, const void* data) {
	if (!ai->Initialized()) return 0;

	switch (msg) {
		case AI_EVENT_UNITGIVEN:
		case AI_EVENT_UNITCAPTURED: {
			const ChangeTeamEvent* cte = (const ChangeTeamEvent*) data;

			const int myAllyTeamId = ai->cb->GetMyAllyTeam();
			const bool oldEnemy = !ai->cb->IsAllied(myAllyTeamId, ai->cb->GetTeamAllyTeam(cte->oldteam));
			const bool newEnemy = !ai->cb->IsAllied(myAllyTeamId, ai->cb->GetTeamAllyTeam(cte->newteam));

			if (oldEnemy && !newEnemy) {
				// unit changed from an enemy to an allied team
				// we got a new friend! :)
				EnemyDestroyed(cte->unit, -1);
			} else if (!oldEnemy && newEnemy) {
				// unit changed from an ally to an enemy team
				// we lost a friend! :(
				EnemyCreated(cte->unit);

				if (!ai->cb->UnitBeingBuilt(cte->unit)) {
					EnemyFinished(cte->unit);
				}
			}

			if (cte->oldteam == ai->cb->GetMyTeam()) {
				// we lost a unit
				UnitDestroyed(cte->unit, -1);

				// FIXME: multiple units given during same frame?
				ai->uh->lastCapturedUnitFrame = ai->cb->GetCurrentFrame();
				ai->uh->lastCapturedUnitID = cte->unit;
			} else if (cte->newteam == ai->cb->GetMyTeam()) {
				// we have a new unit
				UnitCreated(cte->unit, -1);

				if (!ai->cb->UnitBeingBuilt(cte->unit)) {
					UnitFinished(cte->unit);
					ai->uh->IdleUnitAdd(cte->unit, ai->cb->GetCurrentFrame());
				}
			}
		} break;
	}
	return 0;
}




void CKAIK::Update() {
	if (!ai->Initialized()) return;
	const int frame = ai->cb->GetCurrentFrame();

	ai->econTracker->frameUpdate(frame);
	ai->dgunConHandler->Update(frame);

	if ((frame - ai->InitFrame()) == 1) {
		// ai->thm->Init();
		ai->dm->Init();
	}

	ai->bu->Update(frame);
	ai->uh->IdleUnitUpdate(frame);

	ai->ah->Update(frame);
	ai->uh->MMakerUpdate(frame);
	ai->ct->Update(frame);
}
