package;

import flixel.text.FlxText;
import flixel.ui.FlxBar;
import flixel.FlxObject;
import flixel.math.FlxPoint;
import flixel.group.FlxGroup.FlxTypedGroup;
import openfl.events.Event;
import js.Lib;
import flixel.util.FlxColor;
import openfl.filters.ShaderFilter;
import openfl.filters.BitmapFilter;
//import vfx.CrtShader;
//import vfx.Scanline;
//import vfx.Tiltshift;
//import vfx.DitherShader;
import vfx.*;
import flixel.FlxSprite;
import js.Browser;
import flixel.FlxCamera;
import flixel.FlxState;
import flixel.FlxG;
import flixel.system.scaleModes.RatioScaleMode;
import flixel.system.scaleModes.FillScaleMode;

import flixel.tweens.FlxEase;
import flixel.tweens.FlxTween;
import flixel.util.FlxTimer;




class PlayState extends FlxState
{
	static public var cameraBG:FlxCamera;
	static public var cameraGame:FlxCamera;
	static public var cameraGlows:FlxCamera;
	static public var cameraHUD:FlxCamera;
	static public var cameraOverHUD:FlxCamera;

	public var tweens:FlxTweenManager = new FlxTweenManager();
	public var timers:FlxTimerManager = new FlxTimerManager();
	
	var hitSoundTime:Float = 0.0;
	var hitSoundTimeMax:Float = 3.0;

	var crtShader:CrtShader;
	var crtFilter:ShaderFilter;

	var scanlineShader:Scanline;
	var scanlineFilter:ShaderFilter;

	var tiltshiftShader:Tiltshift;
	var tiltshiftFilter:ShaderFilter;

	var ditherShader:DitherShader;
	var ditherFilter:ShaderFilter;

	var scanlines:FlxSprite;
	var scanlinesNight:FlxSprite;

	var bg:FlxSprite;

	public var player:Player;

	var decors = new FlxTypedGroup<FlxSprite>();

	public var enemies = new FlxTypedGroup<Enemy>();
	//public var undeads = new FlxTypedGroup<Enemy>();

	public var playerBullets = new FlxTypedGroup<PlayerBullet>();
	public var enemyBullets = new FlxTypedGroup<EnemyBullet>();

	var sortedGroup:FlxTypedGroup<FlxSprite> = new FlxTypedGroup<FlxSprite>();
	var sortedIndex:Int = 0;
	var sortedIndexStep:Int = 10;
	var enemyIndex:Int = 0;

	var undeadVisionArea:FlxObject = new FlxObject(0, 0, 300, 200);

	var gameTime:Float = 0.0;
	var phase:Int = 0;

	var dayTime:Float = 14.0;
	var dayTimeChange:Float = 6;

	public var isNight:Bool = false;

	public var healthBar:FlxBar;
	public var xpBar:FlxBar;
	public var resurrectBar:FlxBar;
	public var dayBar:FlxBar;

	var healthBarOutline:FlxSprite;
	var xpBarOutline:FlxSprite;
	var resurrectBarOutline:FlxSprite;
	var dayBarOutline:FlxSprite;

	public var levelText:FlxText;

	var showedNightTutorial:Bool = false;

	override public function create():Void
	{
		super.create();

		Reg.playstate = this;

		flixelInit();

		add(tweens);
		add(timers);

		//bg = new FlxSprite(0, 0, "assets/images/bg.png");
		//bg.camera = cameraBG;
		//add(bg);

		crtShader = new CrtShader();
		crtFilter = new ShaderFilter(crtShader);
		
		//scanlineShader = new Scanline();
		//scanlineFilter = new ShaderFilter(scanlineShader);

		//tiltshiftShader = new Tiltshift();  // does this make a difference?
		//tiltshiftFilter = new ShaderFilter(tiltshiftShader);

		//ditherShader = new DitherShader();
		//ditherFilter = new ShaderFilter(ditherShader);

		
		createGrass();

		/*for (i in 0...15) {
			if (FlxG.random.float() < 0.5) {
				enemies.add((new Enemy((FlxG.random.float() < 0.5) ? -50 : FlxG.width + 50, FlxG.random.float() * FlxG.height)));
			}
			else {
				enemies.add((new Enemy(FlxG.random.float() * FlxG.width, (FlxG.random.float() < 0.5) ? -50 : FlxG.height + 50)));
			}
		}*/

		player = new Player(FlxG.worldBounds.x + FlxG.worldBounds.width * 0.5 - 25, FlxG.worldBounds.y + FlxG.worldBounds.height * 0.5 - 28);

		FlxG.camera.setScrollBoundsRect(FlxG.worldBounds.x, FlxG.worldBounds.y, FlxG.worldBounds.width, FlxG.worldBounds.height, true);
		FlxG.camera.follow(player, FlxCameraFollowStyle.LOCKON);
		FlxG.camera.update(FlxG.elapsed);
		FlxG.camera.follow(player, FlxCameraFollowStyle.TOPDOWN_TIGHT, 0.1);

		

		sortedGroup.add(player);
		for (i in 0...enemies.members.length) {
			sortedGroup.add(enemies.members[i]);
		}
		add(sortedGroup);

		for (i in 0...100) {
			playerBullets.add(new PlayerBullet(0, 0));
			playerBullets.members[i].kill();
		}
		add(playerBullets);

		for (i in 0...250) {
			enemyBullets.add(new EnemyBullet(0, 0));
			enemyBullets.members[i].kill();
		}
		add(enemyBullets);


		var filters:Array<BitmapFilter> = [];
		filters.push(crtFilter);
		//filters.push(scanlineFilter);
		//filters.push(tiltshiftFilter);
		//filters.push(ditherFilter);
		cameraBG.filters = filters;

		createScanlines(12);
		scanlines.camera = cameraOverHUD;
		add(scanlines);

		createScanlinesNight(0);
		scanlinesNight.camera = cameraOverHUD;
		scanlinesNight.exists = false;
		add(scanlinesNight);


		healthBar = new FlxBar(831, 33, FlxBarFillDirection.LEFT_TO_RIGHT, 108, 12, player, "health", 0, player.maxHealth);
		healthBar.camera = cameraHUD;
		add(healthBar);

		dayBar = new FlxBar(93, 33, FlxBarFillDirection.LEFT_TO_RIGHT, 108, 12, this, "dayTimeChange", 0, 12);
		dayBar.camera = cameraHUD;
		add(dayBar);

		resurrectBar = new FlxBar(450, 33, FlxBarFillDirection.LEFT_TO_RIGHT, 108, 12, player, "canResurrectInv", 0, player.canResurrectTime);
		resurrectBar.camera = cameraHUD;
		add(resurrectBar);

		xpBar = new FlxBar(34, 522, FlxBarFillDirection.LEFT_TO_RIGHT, 943, 9, player, "xp", player.xpLevels[player.xpLevel - 1], player.xpLevels[player.xpLevel]);
		xpBar.camera = cameraHUD;
		add(xpBar);

		healthBarOutline = new FlxSprite(healthBar.x - 36, healthBar.y - 9, "assets/images/bar_health_outline.png");
		healthBarOutline.camera = cameraHUD;
		add(healthBarOutline);

		dayBarOutline = new FlxSprite(dayBar.x - 36, dayBar.y - 9, "assets/images/bar_sun_outline.png");
		dayBarOutline.camera = cameraHUD;
		add(dayBarOutline);

		resurrectBarOutline = new FlxSprite(resurrectBar.x - 36, resurrectBar.y - 9, "assets/images/bar_summon_outline.png");
		resurrectBarOutline.camera = cameraHUD;
		add(resurrectBarOutline);

		xpBarOutline = new FlxSprite(xpBar.x - 3, xpBar.y - 3, "assets/images/bar_xp_outline.png");
		xpBarOutline.camera = cameraHUD;
		add(xpBarOutline);


		levelText = new FlxText(0, xpBar.y + 13, FlxG.width, "", 12);
		levelText.alignment = FlxTextAlign.CENTER;
		levelText.font = "assets/data/PressStart2P.ttf";
		levelText.camera = cameraHUD;
		levelText.text = "Level " + player.xpLevel;
		add(levelText);
	}

	override public function update(elapsed:Float)
	{
		super.update(elapsed);

		if (gameTime == 0.0) {
			openSubState(new TitleWindow());
		}

		gameTime += elapsed;

		if (hitSoundTime > 0.0) {
			hitSoundTime -= elapsed;
			if (hitSoundTime < 0.0) hitSoundTime = 0.0;
		}

		dayTime += elapsed * 0.6;
		if (dayTime >= 24) dayTime -= 24;
		if (dayTime >= 12 && !scanlines.exists) {
			tweens.tween(scanlinesNight, {alpha: 0.0}, 2.0, {onComplete: function(t) { scanlinesNight.exists = false; isNight = false; dayBarOutline.loadGraphic("assets/images/bar_sun_outline.png"); }});
			scanlines.exists = true;
			scanlines.alpha = 0.0;
			tweens.tween(scanlines, {alpha: 1.0}, 2.0);
		}
		else if (dayTime < 12 && !scanlinesNight.exists) {
			tweens.tween(scanlines, {alpha: 0.0}, 2.0, {onComplete: function(t) { scanlines.exists = false; }});
			scanlinesNight.exists = true;
			scanlinesNight.alpha = 0.0;
			isNight = true;
			dayBarOutline.loadGraphic("assets/images/bar_moon_outline.png");
			tweens.tween(scanlinesNight, {alpha: 1.0}, 2.0);

			if (!showedNightTutorial) {
				showedNightTutorial = true;
				openSubState(new NightWindow());
			}
		}

		dayTimeChange = dayTime;
		if (dayTimeChange > 12)
			dayTimeChange -= 12;


		if (phase == 0 && gameTime > 0.0) {
			phase++;

			addEnemies(8);
		}
		else if (phase == 1 && gameTime > 20) {
			phase++;

			addEnemies(15);
		}
		else if (phase == 2 && gameTime > 42) {
			phase++;

			addEnemies(15);
			addEnemies(2, 2);
			
		}
		else if (phase == 3 && gameTime > 65) {
			phase++;

			addEnemies(30);
			addEnemies(1, 3);
		}
		else if (phase == 4 && gameTime > 90) {
			phase++;

			addEnemies(12, 2);
			addEnemies(8);
		}
		else if (phase == 5 && gameTime > 124) {
			phase++;

			addEnemies(70);
			
		}
		else if (phase == 6 && gameTime > 138) {
			phase++;

			addEnemies(17, 2);
		}
		else if (phase == 7 && gameTime > 155) {
			phase++;

			addEnemies(9, 3);
			addEnemies(10);
			addEnemies(12, 2);
		}
		else if (phase == 8) {
			if (FlxG.random.float() < 0.05) {
				for (i in 0...enemies.members.length) {
					if (enemies.members[i].exists && !enemies.members[i].undead && enemies.members[i].health > 0)
						break;

					if (i == enemies.members.length - 1) {
						openSubState(new WinWindow());
					}
				}
			}
		}


		for (i in 0...sortedGroup.members.length) {
			sortedGroup.members.sort(function(a, b):Int {
				if (a.y + a.height < b.y + b.height) return -1;
				else if (a.y + a.height > b.y + b.height) return 1;
				return 0;
			});
		}

		for (i in 0...enemies.members.length) {
			if (!enemies.members[i].alive) continue;
			if (enemies.members[i].target == null)
				enemies.members[i].target = new FlxPoint(player.x + enemies.members[i].randomOffTarget.x, player.y + enemies.members[i].randomOffTarget.y);
			else {
				if (enemies.members[i].undead) {
					if (FlxG.random.float() < 0.2) {  // beware: behavior will change with different framerates
						undeadVisionArea.x = undeadVisionArea.last.x = enemies.members[i].x + enemies.members[i].width * 0.5;
						undeadVisionArea.y = undeadVisionArea.last.y = enemies.members[i].y + enemies.members[i].height * 0.5;
						
						if (FlxG.overlap(enemies.members[i], enemies, function(area, enemy) {
							if (enemy.health > 0 && !enemy.undead) {
								enemies.members[i].target.x = enemy.x;
								enemies.members[i].target.y = enemy.y;
							}
						}, function(area, enemy) {
							if (enemy.health <= 0 || enemy.undead) return false;
							return true;
						})) {

						}
						else if (FlxG.overlap(undeadVisionArea, enemies, function(area, enemy) {
							if (enemy.health > 0 && !enemy.undead) {
								enemies.members[i].target.x = enemy.x;
								enemies.members[i].target.y = enemy.y;
							}
						}, function(area, enemy) {
							if (enemy.health <= 0 || enemy.undead) return false;
							return true;
						})) {

						}
						else {
							enemies.members[i].target.x = player.x;
							enemies.members[i].target.y = player.y;
						}
						
					}
				}
				else {
					enemies.members[i].target.set(player.x, player.y);
				}
			}
		}

		FlxG.overlap(enemies, enemies, onEnemiesOverlap);  // so enemies separate from each other
		FlxG.overlap(playerBullets, enemies, onPlayerBulletHitEnemy);
		FlxG.overlap(enemyBullets, player, onEnemyBulletHitPlayer);
		FlxG.overlap(enemies, player, onEnemyHitPlayer);
	}

	function onEnemyHitPlayer(e:Enemy, p:Player) {
		if (e.health <= 0) return;
		if (e.undead) return;
		if (p.hurting > 0) return;

		p.health -= e.damage;
		p.velocity.x = e.velocity.x * 40;
		p.velocity.y = e.velocity.y * 40;
		p.hurting = p.hurtTime;
		FlxG.sound.play("assets/sounds/player_hit_" + FlxG.random.int(1, 3) + ".wav", 0.3);
		
		if (p.health <= 0) {
			p.killPlayer();
		}
	}
	
	function onEnemiesOverlap(e1:Enemy, e2: Enemy) {
		if (e1.health <= 0 || e2.health <= 0) return;

		if (e1.undead && !e2.undead) {
			if (e1.attackCooldown <= 0.0) {
				e1.attackCooldown = e1.attackCooldownMax;
				e2.health -= e1.damage;
				
				var velNorm = new FlxPoint(e1.velocity.x, e1.velocity.y);
				velNorm.normalize();

				e2.velocity.x = velNorm.x * 50;
				e2.velocity.y = velNorm.y * 50;
				e2.hurting = e2.hurtTime;
		
				if (e2.health <= 0) {
					e2.standbyDeath();
					player.xp += e2.xpGiven * 1.0;
				}
			}
			else if (e2.attackCooldown <= 0.0) {
				e2.attackCooldown = e2.attackCooldownMax;
				e1.health -= e2.damage;

				var velNorm = new FlxPoint(e2.velocity.x, e2.velocity.y);
				velNorm.normalize();

				e1.velocity.x = velNorm.x * 50;
				e1.velocity.y = velNorm.y * 50;
				e1.hurting = e1.hurtTime;
		
				if (e1.health <= 0) {
					e1.kill();
				}
			}
		}
		else if (e2.undead && !e1.undead) {
			if (e2.attackCooldown <= 0.0) {
				e2.attackCooldown = e2.attackCooldownMax;
				e1.health -= e2.damage;

				var velNorm = new FlxPoint(e2.velocity.x, e2.velocity.y);
				velNorm.normalize();

				e1.velocity.x = velNorm.x * 50;
				e1.velocity.y = velNorm.y * 50;
				e1.hurting = e1.hurtTime;
		
				if (e1.health <= 0) {
					e1.standbyDeath();
					player.xp += e1.xpGiven * 1.0;
				}
			}
			else if (e1.attackCooldown <= 0.0) {
				e1.attackCooldown = e1.attackCooldownMax;
				e2.health -= e1.damage;

				var velNorm = new FlxPoint(e1.velocity.x, e1.velocity.y);
				velNorm.normalize();

				e2.velocity.x = velNorm.x * 50;
				e2.velocity.y = velNorm.y * 50;
				e2.hurting = e2.hurtTime;
		
				if (e2.health <= 0) {
					e2.kill();
				}
			}
		}

		if (FlxG.random.float() < 0.5) return;  // improves performance a bit, and keeps movement more random, though more jittery too.
												// but beware: behavior will change with different framerates
		e1.moveAwayFromPoint(e2.x, e2.y, 40 * FlxG.elapsed);
		e2.moveAwayFromPoint(e1.x, e1.y, 40 * FlxG.elapsed);
	}

	function onPlayerBulletHitEnemy(b:PlayerBullet, e:Enemy) {
		if (e.health <= 0) return;
		if (e.undead) return;

		b.kill();

		e.health -= b.damage;
		e.velocity.x = b.velocity.x * 0.4;
		e.velocity.y = b.velocity.y * 0.4;
		e.hurting = e.hurtTime;

		//FlxG.sound.play("assets/sounds/hit_" + FlxG.random.int(1, 4) + ".wav", 0.6);
		hitSound(e.x, e.y);
		
		if (e.health <= 0) {
			e.standbyDeath();
			player.xp += e.xpGiven;
		}
	}

	function hitSound(ex:Float, ey:Float) {
		if (hitSoundTime < hitSoundTimeMax) {
			var approxDistance = Math.abs((ex - player.x) + (ey - player.y)) * 0.85;
			var volume = 0.4;
			if (approxDistance > 60) volume = 0.3;
			if (approxDistance > 150) volume = 0.2;
			if (approxDistance > 300) volume = 0.15;
			if (approxDistance > 600) volume = 0.1;
			if (approxDistance > 1000) volume = 0.05;

			volume *= 0.67;

			FlxG.sound.play("assets/sounds/hit_" + FlxG.random.int(1, 3) + ".mp3", volume);
			hitSoundTime += 0.74;
		}
	}

	function onEnemyBulletHitPlayer(b:EnemyBullet, p:Player) {
		if (p.health <= 0) return;

		b.kill();

		p.health -= b.damage;
		p.velocity.x = b.velocity.x * 0.4;
		p.velocity.y = b.velocity.y * 0.4;
		p.hurting = p.hurtTime;

		FlxG.sound.play("assets/sounds/player_hit_" + FlxG.random.int(1, 3) + ".wav", 0.3);
		
		if (p.health <= 0) {
			p.killPlayer();
		}
	}



	function createGrass() {
		var grass:FlxSprite;
		var numGrass = Std.int((FlxG.worldBounds.width * FlxG.worldBounds.height) / 10000);
		//trace("numGrass: " + numGrass);
		for (i in 0...numGrass) {
			grass = new FlxSprite(FlxG.worldBounds.x, FlxG.worldBounds.y, "assets/images/grass" + FlxG.random.int(1, 3) + ".png");
			grass.moves = false;
			grass.solid = false;
			grass.antialiasing = false;
			grass.immovable = true;
			grass.alive = false;
			grass.active = false;
			grass.x += Std.int(Math.random() * FlxG.worldBounds.width);
			grass.x -= grass.x % 3;
			grass.y += Std.int(Math.random() * FlxG.worldBounds.height);
			grass.y -= grass.y % 3;
			decors.add(grass);
		}
		add(decors);
	}

	function addEnemies(number:Int, eType:Int = 1) {
		for (i in 0...number) {
			if (FlxG.random.float() < 0.5) {
				enemies.add((new Enemy((FlxG.random.float() < 0.5) ? player.x - 700 : player.x + 700, FlxG.random.float(player.y - 400, player.y + 400), eType)));
			}
			else {
				enemies.add((new Enemy(FlxG.random.float(player.x - 700, player.x + 700), (FlxG.random.float() < 0.5) ? player.y - 400 : player.y + 400, eType)));
			}

			sortedGroup.add(enemies.members[enemies.members.length - 1]);
		}
	}

	function createScanlines(timeOfDay:Float = 12.0)
	{
		scanlines = new FlxSprite(0.0, 0.0);
		scanlines.makeGraphic(FlxG.width + 1, FlxG.height, FlxColor.TRANSPARENT);

		//for (w in 0...Browser.window.innerWidth) {
		for (w in 0...FlxG.width) {
			for (h in 0...FlxG.height) {
				// black scanlines //
				if (h % 2 > 0) 
				{
					/*if (w % 2 > 0)
						scanlines.pixels.setPixel32(w, h, 0x2d000000);
					*/
					continue;
				}
	
				if (timeOfDay >= 21.0 || timeOfDay < 6.0)
					scanlines.pixels.setPixel32(w, h, 0x6f000065); // night
				else if (timeOfDay >= 6.0 && timeOfDay < 8.0)
					scanlines.pixels.setPixel32(w, h, 0x5db94d13); // dawn
				else
					scanlines.pixels.setPixel32(w, h, 0x5d000000); // day
				}
			}
	
		//scanlines.blend = openfl.display.BlendMode.LIGHTEN;

		/*if (!FlxG.fullscreen) {
			//scanlines.scale.y = FlxG.height / Reg.windowHeight;
			scanlines.scale.y = FlxG.height / Browser.window.innerHeight;
		}
		else {
			scanlines.scale.y = 1.0;
		}*/
		scanlines.antialiasing = true;
	
		scanlines.camera = cameraHUD;
		scanlines.scrollFactor.set(0.0, 0.0);
	}

	function createScanlinesNight(timeOfDay:Float = 0.0)
		{
			scanlinesNight = new FlxSprite(0.0, 0.0);
			scanlinesNight.makeGraphic(FlxG.width, FlxG.height, FlxColor.TRANSPARENT);
	
			//for (w in 0...Browser.window.innerWidth) {
			for (w in 0...FlxG.width) {
				for (h in 0...FlxG.height) {
					// black scanlines //
					if (h % 2 > 0) 
					{
						/*if (w % 2 > 0)
							scanlines.pixels.setPixel32(w, h, 0x2d000000);
						*/
						continue;
					}
		
					if (timeOfDay >= 21.0 || timeOfDay < 6.0)
						scanlinesNight.pixels.setPixel32(w, h, 0x6f000065); // night
					else if (timeOfDay >= 6.0 && timeOfDay < 8.0)
						scanlinesNight.pixels.setPixel32(w, h, 0x5db94d13); // dawn
					else
						scanlinesNight.pixels.setPixel32(w, h, 0x5d000000); // day
					}
				}
		
			//scanlines.blend = openfl.display.BlendMode.LIGHTEN;
	
			/*if (!FlxG.fullscreen) {
				//scanlines.scale.y = FlxG.height / Reg.windowHeight;
				scanlines.scale.y = FlxG.height / Browser.window.innerHeight;
			}
			else {
				scanlines.scale.y = 1.0;
			}*/
			scanlinesNight.antialiasing = true;
		
			scanlinesNight.camera = cameraHUD;
			scanlinesNight.scrollFactor.set(0.0, 0.0);
		}
	
	function flixelInit()
	{
		#if FLX_MOUSE
		FlxG.mouse.useSystemCursor = true;
		FlxG.mouse.unload();
		#end
		FlxG.fixedTimestep = false;
		FlxG.maxElapsed = 0.0333333;
	
		FlxG.stage.showDefaultContextMenu = false;
			
		#if html5
		FlxG.log.redirectTraces = true;
		#end
	
		#if !mobile
		//FlxG.sound.volumeDownKeys = null;
		//FlxG.sound.volumeUpKeys = null;
		//FlxG.sound.muteKeys = null;
		#end
		
		FlxG.worldBounds.set(-1000, -1000, 2000, 2000);   // also set in SaveLevelData.hx
		
		cameraBG = FlxG.camera;
		cameraBG.bgColor = 0xffabb2bb;
		FlxG.stage.opaqueBackground = 0xff000000;

		cameraGame = new FlxCamera();
		cameraGame.bgColor = 0x0;
		FlxG.cameras.add(cameraGame, true);
		cameraGlows = new FlxCamera();
		cameraGlows.bgColor = 0x0;
		FlxG.cameras.add(cameraGlows, false);
		cameraHUD = new FlxCamera();
		cameraHUD.bgColor = 0x0;
		FlxG.cameras.add(cameraHUD, false);
		cameraOverHUD = new FlxCamera();
		cameraOverHUD.bgColor = 0x0;
		FlxG.cameras.add(cameraOverHUD, false);
		FlxG.cameras.setDefaultDrawTarget(cameraBG, false);
		FlxG.camera = cameraGame;
	
		cameraBG.antialiasing = true;
		cameraGame.antialiasing = true;
		cameraHUD.antialiasing = true;
		
		#if html5
		if(Reg.almostEqual(Browser.window.innerWidth / Browser.window.innerHeight, 1.77778, 0.004)) {
			FlxG.scaleMode = new FillScaleMode();
		}
		else {
			FlxG.scaleMode = new RatioScaleMode();
		}
		#end

		FlxG.random.initialSeed = 1234;
	}

}
