package search;

import flixel.util.FlxColor;
import flixel.FlxSprite;
import flixel.util.FlxPoint;
import flixel.FlxG;
import flash.display.Graphics;
import flash.geom.Point;

enum SearchPlacerType
{
	GLOBAL;
	CIRCLE(radius:Float);
	RAY(width:Float);
	LINE(width:Float);
	SEGMENT(width:Float, length:Float);
}

enum SearchPlacerMode
{
	NONE;
	PLACING_START;
	PLACING_END;
}

class Placer
{
	var area:Area;
	var start:Point;
	var end:Point;
	var canvas:CanvasSprite;
	var mode:SearchPlacerMode = NONE;
	var icon:Icon;
	var instructionsSprite:FlxSprite;
	var needsEnd(get,never):Bool;

	public var whenDone:Icon->Bool->Void;

	public function new(area_:Area)
	{
		area = area_;
		canvas = area.makeCanvasSprite();

		instructionsSprite = new FlxSprite(0,0);
		instructionsSprite.loadGraphic("assets/images/Place-SetDirection.png");
		instructionsSprite.visible = false;
		instructionsSprite.x = Math.floor(512/2 - instructionsSprite.width/2);
		instructionsSprite.y = 100;
		//instructionsSprite.makeGraphic(1,1,FlxColor.TRANSPARENT);
		area.group.add(instructionsSprite);
	}

	public function destroy()
	{
		canvas.destroy();
		instructionsSprite.destroy();
	}

	public function begin(icon_:Icon)
	{
		if (mode == PLACING_END)
			finish(false);

		icon = icon_;
		mode = PLACING_START;
		setStart(area.screenToCoords(new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY)));
		canvas.clear();
		canvas.visible = true;

		// set instructions
//		instructionsSprite.loadGraphic("assets/images/PlaceInstructions-" + (needsEnd ? "Start.png" : "Drop.png"));
//		instructionsSprite.visible = true;
//		instructionsSprite.x = area.rect.right - instructionsSprite.width - 2;
//		instructionsSprite.y = area.rect.bottom - instructionsSprite.height - 2;
	}

	public function update()
	{
		switch (mode)
		{
			case PLACING_START:
			{
				setStart(area.screenToCoords(new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY)));
				canvas.visible = isInArea(start);
				if (!FlxG.mouse.pressed)
				{
					if (isInArea(start))
					{
						// placed start
						if (needsEnd)
						{
							mode = PLACING_END;
							//instructionsSprite.loadGraphic("assets/images/PlaceInstructions-End.png");
							instructionsSprite.visible = true;
						}
						else
							finish(true);
					}
					else
					{
						finish(false);
					}
				}
			}
			case PLACING_END:
				setEnd(area.screenToCoords(new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY)));
				//instructionsSprite.visible = area.clock.getFlashing(2, 0.4);
				if (FlxG.mouse.pressed)
				{
					finish(true);
				}
				else if (FlxG.mouse.pressedRight || FlxG.keys.pressed.ESCAPE)
				{
					finish(false);
				}
			default:
		}
	}

	public function wantIconSecondFrame():Bool
	{
		return isInArea(area.screenToCoords(new FlxPoint(FlxG.mouse.screenX, FlxG.mouse.screenY)));
	}

	private function isInArea(coords:Point):Bool
	{
		return (coords.x >= 0 && coords.x < area.dim.x && coords.y >= 0 && coords.y < area.dim.y);
	}

	public function finish(success:Bool)
	{
		mode = NONE;
		canvas.visible = false;
		instructionsSprite.visible = false;

		if (success)
			area.addTool(icon.toolSpec, start, end);

		if (whenDone != null) whenDone(icon, success);
	}

	function get_needsEnd()
	{
		if (icon == null) return false;
		switch (icon.toolSpec.placerType)
		{
			case LINE(width): return true;
			case RAY(width): return true;
			case SEGMENT(width,length): return true;
			default: return false;
		}
	}

	private function setStart(start_:Point)
	{
		start = start_;

		if (icon == null) return;

		switch (icon.toolSpec.placerType)
		{
			case LINE(width):
				end = new Point(start.x+1, start.y);
			case RAY(width):
				end = new Point(start.x+1, start.y);
			case SEGMENT(width, length):
				end = Util.add(start, new Point(length,0));
			default:
		}
	}

	private function setEnd(end_:Point)
	{
		end = end_;
		if (Util.dist(start, end) < 0.001) setStart(start);
	}

	public function draw()
	{
		if (mode == NONE || icon == null) return;

		var graphics = canvas.beginPatternDraw(openfl.Assets.getBitmapData("assets/images/Pattern-Placer.png"));
		var fixed = fixCoords(area, icon.toolSpec.placerType, start, end);

		switch (icon.toolSpec.placerType)
		{
			case CIRCLE(radius):
				graphics.drawCircle(start.x, start.y, radius);
			case RAY(width):
				drawThickLine(graphics,	fixed.c0, fixed.c1, width);
			case LINE(width):
				drawThickLine(graphics,	fixed.c0, fixed.c1, width);
			case SEGMENT(width, length):
				drawThickLine(graphics,	fixed.c0, fixed.c1, width);
			case GLOBAL:
				graphics.drawRect(0, 0, area.dim.x, area.dim.y);
		}
		canvas.endDraw();
	}

	function drawThickLine(graphics:Graphics, p0:Point, p1:Point, width:Float)
	{
		var perp = Util.normalize(Util.perp(Util.sub(p1, p0)));
		var a = Util.add(p0, Util.mul(0.5*width, perp));
		var b = Util.add(p0, Util.mul(-0.5*width, perp));
		var c = Util.add(p1, Util.mul(-0.5*width, perp));
		var d = Util.add(p1, Util.mul(0.5*width, perp));
		graphics.moveTo(a.x, a.y);
		graphics.lineTo(b.x, b.y);
		graphics.lineTo(c.x, c.y);
		graphics.lineTo(d.x, d.y);
	}

	static inline function intersectWithArea(area:Area, inside:Point, outside:Point):Point
	{
		var expand = 3;

		var tl = new Point(-expand, -expand);
		var tr = new Point(area.dim.x+expand, -expand);
		var br = new Point(area.dim.x+expand, area.dim.y+expand);
		var bl = new Point(-expand, area.dim.y+expand);

		var i:Point;
		i = Util.intersect(inside, outside, tl, tr);
		if (i != null) return i;
		i = Util.intersect(inside, outside, tr, br);
		if (i != null) return i;
		i = Util.intersect(inside, outside, br, bl);
		if (i != null) return i;
		i = Util.intersect(inside, outside, bl, tl);
		if (i != null) return i;

		return outside;
	}

	public static function fixCoords(area:Area, placerType:SearchPlacerType, start:Point, end:Point) : { c0:Point, c1:Point }
	{
		switch (placerType)
		{
			case CIRCLE(radius):
				return {
					c0:start,
					c1:null
				};
			case RAY(width):
				return {
					c0:start,
					c1:intersectWithArea(area, start, Util.add(start, Util.mul(area.dim.x, Util.normalize(Util.sub(end, start)))))
				};
			case LINE(width):
				var dir = Util.mul(area.dim.x, Util.normalize(Util.sub(end, start)));
				return {
					c0:intersectWithArea(area, start, Util.sub(start, dir)),
					c1:intersectWithArea(area, start, Util.add(start, dir))
				};
			case SEGMENT(width, length):
				var dir = Util.normalize(Util.sub(end, start));
				return {
					c0:start,
					c1:intersectWithArea(area, start, Util.add(start, Util.mul(length, dir)))
				};
			case GLOBAL:
				return {
					c0:null,
					c1:null
				};
		}
	}
}
