import ScriptEvent.s;
import ecs.components.*;
import ecs.core.*;
import ecs.systems.*;
import res.Scene;
import res.display.FrameBuffer;
import res.geom.Vector2;
import res.input.KeyboardEvent;
import res.input.MouseEvent;
import res.text.Textmap;
import res.tiles.Tilemap;
import res.timeline.Timeline;

using Std;
using res.display.Sprite;
using res.graphics.Painter;

final INITIAL_FUND:Int = 200;

class Game extends Scene {
	var engine:Engine = new Engine();

	var buyUISystem:BuyUISystem;
	var econimicsSystem:EconomicsSystem;
	var scriptSystem:ScriptSystem;

	var placementPreview:Entity = null;

	var timeline:Timeline = new Timeline();

	var uiTilemap:Tilemap;
	var scoreText:Textmap;
	var speachText:Textmap;

	var earth:Entity;

	var gameIsOver:Bool = false;

	var resetScoreColorTimer:Null<Float> = null;

	function createEarth() {
		var earthEntity = new Entity('earth');

		var orbitSystem = engine.getSystem(OrbitSystem);

		earthEntity.add(new ecs.components.Sprite(res.rom.sprites['earth'], 0.5, 0.5));
		earthEntity.add(new Spatial(orbitSystem.centerX, orbitSystem.centerY));
		earthEntity.add(new TriggerRadius(12));
		earthEntity.add(new Health(30, false));
		earthEntity.add(new HealthDisplay(0, -25, 30, 2));
		earthEntity.add(new CollisionCircle(12));

		return earthEntity;
	}

	function onOreChange(value:Int) {
		scoreText.textCentered(1, '$value');
		uiTilemap.colorMap[16] = 13;
		scoreText.colorMap[13] = 16;
		resetScoreColorTimer = 0.1;
	}

	function setupSystems() {
		engine.addSystem(new TtlSystem());

		scriptSystem = new ScriptSystem();
		engine.addSystem(scriptSystem);

		econimicsSystem = new EconomicsSystem(onOreChange);
		engine.addSystem(econimicsSystem);
		engine.addSystem(new FlashSystem(this));
		engine.addSystem(new ShakeSystem(res.frameBuffer));
		engine.addSystem(new SpawnOreSystem(() -> res.rom.sprites['ore']));
		engine.addSystem(new DeathSystem());
		engine.addSystem(new OrbitSystem(128, 160));
		engine.addSystem(new BombSpawnSystem(res.rom));
		engine.addSystem(new DrawOrbitsSystem()).enabled = false;
		engine.addSystem(new CollectOreSystem());
		engine.addSystem(new CollisionSystem());
		engine.addSystem(new CollisionResolveSystem());
		engine.addSystem(new MoverSystem());
		engine.addSystem(new AimSystem());
		engine.addSystem(new LaserGunSystem());
		engine.addSystem(new FireGunSystem());
		engine.addSystem(new FireBallSystem());
		engine.addSystem(new HealthSystem());
		engine.addSystem(new ImpactSystem());
		engine.addSystem(new DrawOrbitPreviewSystem());
		engine.addSystem(new DrawParticlesSystem());
		engine.addSystem(new DrawSpriteSystem());
		engine.addSystem(new DrawHealthDisplaySystem());
		engine.addSystem(new BombSystem());
		engine.addSystem(new ExplosionSystem());
		buyUISystem = new BuyUISystem(res);
		engine.addSystem(buyUISystem);
		engine.addSystem(new DebugDrawSystem()).enabled = false;
		engine.addSystem(new AudioSystem(audioMixer, res.rom));
	}

	function say(text:String) {
		audioMixer.play(res.rom.audioBuffers['nasa']);
		speachText.clear();
		speachText.print(0, 30, 'NASA: $text');
	}

	function scriptAfterThirdUnlock() {
		scriptSystem.addEvent(s(2, 20, () -> {
			engine.add(spawnMeteor());
		}), s(4, () -> {
			scriptAfterThirdUnlock();
		}));
	}

	function scriptAfterSecondWave() {
		var n = 0;

		scriptSystem.addEvent(s(() -> {
			say('Отличная работа! Но по новым данным метеоритный дождь только усиливается.');
		}), s(3, () -> {
			buyUISystem.unlockNext();
			say('Наша новая разработка: спутник извергающий пламя!');
		}), s(2, () -> {
			say('Правда круто?');
		}), s(3, 10, () -> {
			speachText.clear();
			n++;

			final meteor = spawnMeteor();

			if (n == 10) {
				final deathInfo = new Entity();
				deathInfo.add(new Action(() -> {
					if (meteor.has(Kill)) {
						say('А вы неплохо справляетесь!');
					} else {
						say('Чтож... Могло быть и лучше.');
					}

					scriptSystem.addEvent(s(2, () -> {
						buyUISystem.unlockNext();
						say('Наши ученые тоже не сидят на месте. Наша новая разработка: спутник-минёр!');
					}));

					scriptSystem.addEvent(s(4, () -> {
						say('Приготовьтесь к новой волне метеоритного дождя!');
					}));

					scriptSystem.addEvent(s(2, () -> {
						speachText.clear();
						scriptAfterThirdUnlock();
					}));
				}));

				meteor.add(new EntityContainer(deathInfo));
			}
			engine.add(meteor);
		}));
	}

	function scriptAfterFirstKill() {
		var n = 0;
		scriptSystem.addEvent(s(4, 5, () -> {
			n++;

			final meteor = spawnMeteor();

			if (n == 5) {
				final deathInfo = new Entity();
				deathInfo.add(new Action(() -> {
					scriptAfterSecondWave();
				}));

				meteor.add(new EntityContainer(deathInfo));
			}

			engine.add(meteor);
		}));
	}

	function spawnFirstMeteor() {
		final meteor = spawnMeteor();

		final deathInfo = new Entity();
		deathInfo.add(new Action(() -> {
			if (meteor.has(Kill)) {
				say('Похоже метеориты содержат ценную руду. Соберите ее и давайте посмотрим что можно с ней сделать.');
				scriptSystem.addEvent(s(3, () -> {
					speachText.clear();
					scriptAfterFirstKill();
				}));
			} else {
				final variants = [
					'Что-то пошло не так. Метеориты необходимо сбивать используя спутники',
					'Метеорит попал в атмосферу. Если продолжать в том же духе, это плохо закончится',
					'Кажется на планете стало на один континент меньше...'
				];
				say(variants[Math.floor(variants.length * Math.random())]);

				scriptSystem.addEvent(s(3, () -> {
					speachText.clear();
				}));

				spawnFirstMeteor();
			}
		}));

		meteor.add(new EntityContainer(deathInfo));

		engine.add(meteor);
	}

	function setupScript() {
		scriptSystem.addEvent(s(1, () -> {
			say('По нашим прогнозам сегодня ожидается небольшой метеоритный дождь.');
		}), s(2.5, () -> {
			say('Наши сотрудники разработали спутник, который способен сбивать метеориты лазером.');
		}), s(4, () -> {
			buyUISystem.unlockNext();
			say('Схема сборки спутника уже передана вашим инженерам.');
		}), s(3, () -> {
			econimicsSystem.collect(INITIAL_FUND);
			say('Проверьте ваш счет, финансирование от правительства в размере ${INITIAL_FUND} уже должно поступить');
		}), s(4, () -> {
			say('Выживание планеты в ваших руках!');
		}), s(2, () -> {
			speachText.clear();
		}), s(() -> {
			spawnFirstMeteor();
		}));
	}

	override function init() {
		uiTilemap = res.rom.tilemaps['ui'].clone();
		scoreText = res.createTextmap(res.rom.fonts['russian'], 9, 2, [13 => 5]);
		scoreText.textCentered(1, '0');

		speachText = res.createTextmap(res.rom.fonts['russian']);

		setupSystems();

		earth = createEarth();
		engine.add(earth);

		/*
			buyUISystem.unlockNext();
			buyUISystem.unlockNext();
		 */

		setupScript();
	}

	function spawnMeteor() {
		final entity = new Entity('meteor');

		final orbitSystem = engine.getSystem(OrbitSystem);

		final a = Math.PI * 2 * Math.random();

		final v = new Vector2(Math.sin(a), Math.cos(a)).normalize(160);

		entity.add(new Spatial(orbitSystem.centerX + v.x, orbitSystem.centerY + v.y));
		v.normalize(-15);
		entity.add(new Velocity(v.x, v.y));
		final sprite:ecs.components.Sprite = cast entity.add(new ecs.components.Sprite(res.rom.sprites['meteor'], 0.5, 0.5));
		sprite.wrap = false;

		entity.add(new TriggerRadius(5));
		entity.add(new Impact());
		entity.add(new Hostile());
		entity.add(new Health(10));
		entity.add(new HealthDisplay());
		entity.add(new Emitter(0.1, 3, 3, 3, [3, 4, 5]));
		entity.add(new HasOre((3 + Math.random() * 3).int(), 5));
		entity.add(new CollisionCircle(5));
		entity.add(new CollisionResult());

		return entity;
	}

	override function keyboardEvent(event:KeyboardEvent) {
		buyUISystem.keyEvent(event);
	}

	override function mouseEvent(event:MouseEvent) {
		buyUISystem.mouseEvent(event);
	}

	function explodeEarth() {
		audioMixer.play(res.rom.audioBuffers['big_explosion']);

		engine.getSystem(ScriptSystem).enabled = false;
		engine.getSystem(CollectOreSystem).enabled = false;
		engine.getSystem(BuyUISystem).enabled = false;

		final ores = engine.getByName('ore');

		for (ore in ores) {
			final spatial = ore.get(Spatial);
			final velocity = ore.get(Velocity);
			final earthSpatial = earth.get(Spatial);

			final v = Vector2.xy(earthSpatial.x - spatial.x, earthSpatial.y - spatial.y).normalize(1);

			velocity.x = v.x * 50;
			velocity.y = v.y * 50;
		}

		for (deb in [[1, -1, 0], [2, 1, 0], [3, 0, -1]]) {
			var debEntity = new Entity('debrie_${deb[0]}');

			debEntity.add(earth.get(Spatial).clone());
			debEntity.add(new ecs.components.Sprite(res.rom.sprites['earth_deb_${deb[0]}'], 0.5, 0.5));
			debEntity.add(new Velocity(deb[1], deb[2]));
			debEntity.add(new VelocityChange(5, 0, 10));

			var emitter = new Emitter(0.1, 1, 10, 5, [3, 4]);
			emitter.ttl = 10;

			debEntity.add(emitter);

			engine.add(debEntity);
		}

		var dust = new Entity();
		dust.add(earth.get(Spatial).clone());
		var dustEmitter = new Emitter(0.5, 30, earth.get(TriggerRadius).radius * 2, 10, [13, 5, 4]);
		dustEmitter.ttl = 5;
		dust.add(dustEmitter);
		engine.add(dust);

		var bigExplosion = new Entity();
		bigExplosion.add(earth.get(Spatial).clone());
		bigExplosion.add(new Explosion(50, 3));
		engine.add(bigExplosion);

		final satellites = engine.getByName('satellite');

		if (satellites != null) {
			for (sat in satellites) {
				sat.add(new Death());
			}
		}

		final meteors = engine.getByName('meteor');

		if (meteors != null) {
			for (meteor in meteors) {
				meteor.add(new Death());
			}
		}

		engine.getSystem(DrawOrbitPreviewSystem).enabled = false;

		if (placementPreview != null)
			engine.remove(placementPreview);

		engine.remove(earth);

		engine.getSystem(FlashSystem).flash(10, 0.1);
		engine.getSystem(ShakeSystem).shake(20, 5);

		timeline.after(5, (_) -> {
			res.setScene(GameOver);
		});
	}

	override function update(dt:Float) {
		timeline.update(dt);
		engine.getSystem(DrawOrbitPreviewSystem).setMousePos(res.mouse.x, res.mouse.y);
		engine.update(dt);

		if (!gameIsOver) {
			if (earth.get(Health).current == 0) {
				gameIsOver = true;
				explodeEarth();
			}
		}

		if (resetScoreColorTimer != null) {
			if (resetScoreColorTimer <= 0) {
				uiTilemap.colorMap[16] = 16;
				scoreText.colorMap[13] = 5;
				resetScoreColorTimer = null;
			} else
				resetScoreColorTimer -= dt;
		}
	}

	override function render(frameBuffer:FrameBuffer) {
		frameBuffer.clear(clearColorIndex);
		engine.render(frameBuffer);

		frameBuffer.scrollX = frameBuffer.scrollY = 0;

		if (!gameIsOver) {
			Tilemap.drawTilemap(frameBuffer, uiTilemap, 0, 0, 72, 24);
			Tilemap.drawTilemap(frameBuffer, scoreText, 0, 0, 8 * 9, 16);
			Tilemap.drawTilemap(frameBuffer, speachText);
		}
	}
}
