/* {{{ Constants */
const string@ strMtl = "Metals";
const string@ strElc = "Electronics";
const string@ strAdv = "AdvParts";
const string@ strPlanet = "Planet";

const float twoPi = 6.28318531f;
/* }}} */
/* {{{ Tracking variables */
Empire@ player, enemy, neutral,
		remnants, pirates;
System@ homeSys, remnSys, freeSys,
	    neutSys, enemSys;

vector reinforcePos, piratePos;

/* }}} */
/* {{{ Spawn utilities */
HulledObj@ spawnShip(Empire@ emp, const HullLayout@ layout, System@ sys, vector position, bool cleared) {
	HulledObj@ hulled = spawnShip(emp, layout, sys, position);
	Object@ obj = hulled;

	if (cleared) {
		OrderList orders;
		if (orders.prepare(obj))
			orders.clearOrders(true);
	}

	return hulled;
}

void takeoverPlanets(System@ sys, Empire@ emp, int defMult) {
	// Get structure types
	const subSystemDef@ city = getSubSystemDefByName("City");
	const subSystemDef@ mtl = getSubSystemDefByName("MetalMine");
	const subSystemDef@ elc = getSubSystemDefByName("AdvPartFact");
	const subSystemDef@ adv = getSubSystemDefByName("ElectronicFact");
	const subSystemDef@ yard = getSubSystemDefByName("ShipYard");
	const subSystemDef@ port = getSubSystemDefByName("SpacePort");
	const subSystemDef@ farm = getSubSystemDefByName("Farm");
	const subSystemDef@ cap = getSubSystemDefByName("Capital");

	// Get defense layout
	const HullLayout@ defense = emp.getShipLayout(localize("#SH_Guard Platform"));

	// Take over all the planets
	SysObjList list;
	list.prepare(sys);

	uint objCnt = list.childCount;
	for (uint i = 0; i < objCnt; ++i) {
		Object@ obj = list.getChild(i);
		Planet@ pl = obj;

		if (pl is null)
			continue;

		obj.setOwner(emp);
		pl.modPopulation(1000);
		pl.addStructure(cap);
		pl.addStructure(farm);

		uint slots = uint(pl.getMaxStructureCount()) - 2;
		for (uint i = 0; i < slots; ++i) {
			switch (i % 7) {
				case 0: pl.addStructure(city); break;
				case 1: pl.addStructure(mtl); break;
				case 2: pl.addStructure(elc); break;
				case 3: pl.addStructure(adv); break;
				case 4: pl.addStructure(yard); break;
				case 5: pl.addStructure(port); break;
				case 6: pl.addStructure(farm); break;
			}
		}

		createDefenseRing(defense, emp, sys, obj, 40.f, defMult);

		pl.modPopulation(pl.getMaxPopulation());
	}
}

void createDefenseRing(const HullLayout@ layout, Empire@ emp, System@ sys, Object@ obj, float radius, uint amount) {
	float angle = 0, angleInc = twoPi / float(amount);
	for (uint i = 0; i < amount; ++i) {
		vector pos = vector(radius * cos(angle), 0, radius * sin(angle));
		if (obj !is null)
			pos += obj.position;
		angle += angleInc;

		Object@ ship = spawnShip(emp, layout, sys, pos);
		ship.setStance(AIS_Defend);
		ship.orbitAround(obj);
	}
}

int reinforceCount = -1;
void spawnReinforcements(bool cleared) {
	const HullLayout@ capShip = player.getShipLayout(localize("#SH_Capital Ship"));
	const HullLayout@ cruiser = player.getShipLayout(localize("#SH_Cruiser"));
	const HullLayout@ dread = player.getShipLayout(localize("#SH_Dreadnaught"));

	reinforceCount += 1;
	uint mult = max(reinforceCount, 1);

	// Spawn capital ships
	for (uint i = 0; i < 1 * mult; ++i) {
		vector vec = vector(30.f * i, 0, 0);
		spawnShip(player, capShip, homeSys, reinforcePos, cleared);
	}

	// Spawn cruisers
	for (uint i = 0; i < 12 * mult; ++i) {
		vector vec = vector(30.f * (i % 6) - 75.f, 0, 30.f * floor(i / 6.f));
		spawnShip(player, cruiser, homeSys, reinforcePos + vec, cleared);
	}

	// Spawn dreadnaughts
	for (uint i = 0; i < 6 * mult; ++i) {
		vector vec = vector(30.f * (i % 6) - 75.f, 0, -30.f * (floor(i / 6.f)+1));
		spawnShip(player, dread, homeSys, reinforcePos + vec, cleared);
	}
}

void spawnColonizer() {
	const HullLayout@ colo = player.getShipLayout(localize("#SH_Colony Ship"));
	spawnShip(player, colo, homeSys, reinforcePos + vector(0, 0, 16.f), true);
}
/* }}} */
/* {{{ Pirate Events */
void spawnFirstPirates() {
	const HullLayout@ capShip = pirates.getShipLayout(localize("#SH_Capital Ship"));
	const HullLayout@ cruiser = pirates.getShipLayout(localize("#SH_Cruiser"));
	const HullLayout@ dread = pirates.getShipLayout(localize("#SH_Dreadnaught"));

	// Spawn capital ship
	spawnShip(pirates, capShip, homeSys, piratePos, true);

	// Spawn cruisers
	for (uint i = 0; i < 6; ++i) {
		vector vec = vector(30.f * i - 75.f, 0, -30.f);
		spawnShip(pirates, cruiser, homeSys, piratePos + vec, true);
	}

	// Spawn dreadnaughts
	for (uint i = 0; i < 3; ++i) {
		vector vec = vector(60.f * i - 75.f, 0, 30.f);
		spawnShip(pirates, dread, homeSys, piratePos + vec, true);
	}
}

void spawnAtRandom(Empire@ emp, const HullLayout@ layout, System@ sys) {
	// Calculate a random position at the edge of the system
	float theta = randomf(twoPi);
	float radius = sys.toObject().radius;

	vector pos = vector(radius * cos(theta), 0, radius * sin(theta));

	// Spawn it
	spawnShip(emp, layout, sys, pos);
}

void spawnSecondPirates() {
	const HullLayout@ capShip = pirates.getShipLayout(localize("#SH_Capital Ship"));
	const HullLayout@ cruiser = pirates.getShipLayout(localize("#SH_Cruiser"));
	const HullLayout@ dread = pirates.getShipLayout(localize("#SH_Dreadnaught"));

	// Spawn capital ship
	for (uint i = 0; i < 3; ++i)
		spawnAtRandom(pirates, capShip, homeSys);

	// Spawn cruisers
	for (uint i = 0; i < 8; ++i)
		spawnAtRandom(pirates, cruiser, homeSys);

	// Spawn dreadnaughts
	for (uint i = 0; i < 12; ++i)
		spawnAtRandom(pirates, dread, homeSys);
}

void spawnThirdPirates() {
	const HullLayout@ capShip = pirates.getShipLayout(localize("#SH_Capital Ship"));
	const HullLayout@ cruiser = pirates.getShipLayout(localize("#SH_Cruiser"));
	const HullLayout@ dread = pirates.getShipLayout(localize("#SH_Dreadnaught"));

	// Spawn capital ship
	for (uint i = 0; i < 4; ++i)
		spawnAtRandom(pirates, capShip, homeSys);

	// Spawn cruisers
	for (uint i = 0; i < 16; ++i)
		spawnAtRandom(pirates, cruiser, homeSys);

	// Spawn dreadnaughts
	for (uint i = 0; i < 16; ++i)
		spawnAtRandom(pirates, dread, homeSys);
}
/* }}} */
/* {{{ Game setup */
/* {{{ Set up neutral system */
void setupNeutralSystem(Empire@ emp, System@ sys) {
	float radius = sys.toObject().radius;
	const HullLayout@ capShip = emp.getShipLayout(localize("#SH_Capital Ship"));
	const HullLayout@ cruiser = emp.getShipLayout(localize("#SH_Cruiser"));
	const HullLayout@ dread = emp.getShipLayout(localize("#SH_Dreadnaught"));

	takeoverPlanets(sys, emp, 2);

	createDefenseRing(cruiser, emp, sys, null, radius * 0.7f, 12);
}
/* }}} */
/* {{{ Set up enemy system */
void setupEnemySystem(Empire@ emp, System@ sys) {
	float radius = sys.toObject().radius;
	const HullLayout@ capShip = emp.getShipLayout(localize("#SH_Capital Ship"));
	const HullLayout@ cruiser = emp.getShipLayout(localize("#SH_Cruiser"));
	const HullLayout@ dread = emp.getShipLayout(localize("#SH_Dreadnaught"));

	takeoverPlanets(sys, emp, 6);
	createDefenseRing(cruiser, emp, sys, null, radius * 0.7f, 24);
	createDefenseRing(dread, emp, sys, null, radius * 0.75f, 12);
	createDefenseRing(capShip, emp, sys, null, radius * 0.8f, 6);
}
/* }}} */
void postMap() {
	// Set up empires
	@player = getEmpireByID(1);
	@neutral = getEmpireByID(2);
	@enemy = getEmpireByID(3);
	@remnants = getEmpireByID(-3);
	@pirates = getEmpireByID(-2);

	// Track systems
	@homeSys = getSystem(0);
	@remnSys = getSystem(1);
	@freeSys = getSystem(2);
	@neutSys = getSystem(3);
	@enemSys = getSystem(4);

	// Get positions for spawning
	float radius = homeSys.toObject().radius;
	reinforcePos = vector(radius * 0.5f, 0, radius * 0.5f);
	piratePos = vector(radius * 0.5f, 0, radius * 0.3f);

	// Anything that follows shouldn't be done on load
	if (gameTime >= 1.f)
		return;

	// Set remnants to be seeded
	remnSys.toObject().setStat(remnants, "Seed", 46.f);

	// Create initial player fleet
	spawnReinforcements(true);

	// Create enemy system
	setupEnemySystem(enemy, enemSys);

	// Create neutral system
	setupNeutralSystem(neutral, neutSys);

	// Fill up neutral empire bank
	neutral.setStat(strMtl, pow(10, 20));
	neutral.setStat(strElc, pow(10, 20));
	neutral.setStat(strAdv, pow(10, 20));
	neutral.modResearchRate(pow(10, 6));

	// Fill up player empire bank
	player.setStat(strMtl, pow(10, 20));
	player.setStat(strElc, pow(10, 20));
	player.setStat(strAdv, pow(10, 20));

	// Player can't research at first
	player.modResearchRate(-pow(10, 4));

	// Don't auto-scout the two other systems
	player.addScouting(neutSys);
	player.addScouting(enemSys);

	// Cheat research
	ResearchWeb web;
	web.prepare(player);

	web.markAsVisible("MegaConstruction");
	levelTo(web, "MegaConstruction", 8.f);
	levelTo(web, "ShipConstruction", 8.f);
	levelTo(web, "Metallurgy", 8.f);
	levelTo(web, "Economics", 8.f);
	levelTo(web, "Science", 3.f);
}

void unlockLasers() {
	// Cheat research
	ResearchWeb web;
	web.prepare(player);

	web.markAsVisible("BeamWeapons");
	levelTo(web, "BeamWeapons", 3.f);
	player.updateSubSystems();
}

void positiveResearch() {
	player.modResearchRate(pow(10, 4));
}

void levelTo(ResearchWeb& web, string@ techName, uint level) {
	const WebItem@ tech = web.getItem(techName);

	if (tech !is null)
		for (uint i = 0; i < level; ++i)
			levelTech(web, tech);
}

void levelTech(ResearchWeb& web, const WebItem@ tech) {
	float level, progress, cost, max;
	tech.getLevels(level, progress, cost, max);
	web.addPoints(tech.descriptor, cost - progress + 10);
}

void emptyBank() {
	player.setStat(strMtl, 0);
	player.setStat(strElc, 0);
	player.setStat(strAdv, 0);
}

void emptyMetals() {
	player.setStat(strMtl, 0);
}

void giveNeutralVision() {
	player.addVisibility(neutral);
	player.addMet(neutral);
}

void createNeutralTreaty() {
	// Propose treaty
	TreatyFactory@ fact = TreatyFactory(neutral, player);
	Treaty@ treaty = @fact.get_treaty();
	Clause@ clause;
	ClauseOption@ option;

	treaty.addClause("peace", false);

	@clause = treaty.addClause("timeout", false);
	@option = clause.getOption(0);
	option = 60.f * 5.f;

	@clause = treaty.addClause("trade", false);
	@option = clause.getOption(0);
	option = "Metals";
	@option = clause.getOption(1);
	option = 10000.f;

	fact.propose();
}

void giveEnemyVision() {
	player.addVisibility(enemy);
	player.addMet(enemy);
}

bool foundCapitol = false;
void checkCapitol() {
	if (player.getStat(strPlanet) > 0.01f) {
		SysObjList list;
		list.prepare(homeSys);

		Planet@ capitol;
		uint cnt = list.childCount;
		for (uint i = 0; i < cnt; ++i) {
			Object@ obj = list.getChild(i);
			if (obj.getOwner() !is player)
				continue;
			Planet@ pl = obj;
			if (pl is null)
				continue;
			@capitol = pl;
			break;
		}

		if (capitol !is null) {
			foundCapitol = true;

			if (capitol.getStructureCount() < 5) {
				capitol.removeAllStructures();
				capitol.addStructure(getSubSystemDefByName("GalacticCapital"));
				for (uint i = 0; i < 6; ++i)
					capitol.addStructure(getSubSystemDefByName("City"));
				for (uint i = 0; i < 2; ++i)
					capitol.addStructure(getSubSystemDefByName("Farm"));
				for (uint i = 0; i < 3; ++i)
					capitol.addStructure(getSubSystemDefByName("SpacePort"));
				for (uint i = 0; i < 3; ++i)
					capitol.addStructure(getSubSystemDefByName("ShipYard"));
				for (uint i = 0; i < 3; ++i)
					capitol.addStructure(getSubSystemDefByName("SciLab"));
				capitol.modPopulation(100.f * 1000.f * 1000.f);
				player.setSetting("autoGovern", 1);
			}
		}
	}
}

bool initialized = false;
void tick(float time) {
	if (!initialized) {
		postMap();
		initialized = true;
	}

	if (!foundCapitol)
		checkCapitol();
}
/* }}} */
/* {{{ Client messages */
void onMessage(Empire@ emp, uint msg, string@ arg1, float arg2) {
	if (!initialized)
		return;
	switch (msg) {
		case 0: spawnReinforcements(false); break;
		case 1: spawnFirstPirates(); break;
		case 2: spawnSecondPirates(); break;
		case 3: spawnThirdPirates(); break;
		case 4: spawnColonizer(); break;
		case 5: emptyBank(); break;
		case 6: emptyMetals(); break;
		case 7: giveNeutralVision(); createNeutralTreaty(); break;
		case 8: giveEnemyVision(); break;
		case 9: unlockLasers(); break;
		case 10: positiveResearch(); break;
	}
}
/* }}} */
