﻿using System;
using Gamelogic;
using Gamelogic.Grids;
using Gamelogic.Grids.Examples;
using UnityEngine;

public class PointyHexReflectionsMain : GLMonoBehaviour
{
	public enum NormalDirection
	{
		X, Y, Z
	}
	
	private readonly Vector2 HexDimensions = new Vector2(74, 84);
	
	public SpriteCell cellPrefab;
	public GameObject root;
	
	public LineRenderer normalLineRenderer;
	public LineRenderer mirrorLineRenderer;
	public LineRenderer reflectionLineRenderer1;
	public LineRenderer reflectionLineRenderer2;
	
	private PointyHexGrid<SpriteCell> grid;
	private IMap3D<PointyHexPoint> map;
	
	private NormalDirection normalDirection = NormalDirection.X;
	
	private PointyHexPoint mirrorHexPoint;
	private PointyHexPoint selectedPoint;
	private PointyHexPoint reflectedPoint;
	
	public void Start()
	{
		normalLineRenderer.SetVertexCount(2);
		mirrorLineRenderer.SetVertexCount(2);
		reflectionLineRenderer1.SetVertexCount(2);
		reflectionLineRenderer2.SetVertexCount(2);
		
		normalLineRenderer.SetColors(ExampleUtils.Colors[2], ExampleUtils.Colors[2]);
		mirrorLineRenderer.SetColors(ExampleUtils.Colors[4], ExampleUtils.Colors[4]);
		reflectionLineRenderer1.SetColors(ExampleUtils.Colors[3], ExampleUtils.Colors[3]);
		reflectionLineRenderer2.SetColors(ExampleUtils.Colors[3], ExampleUtils.Colors[3]);

		BuildGrid();
		UpdateReflectedPoint();
		UpdateGridColors();
	}
	
	private void BuildGrid()
	{
		grid = PointyHexGrid<SpriteCell>.Hexagon(10);
		
		map = new PointyHexMap(HexDimensions)
			.AnchorCellMiddleCenter()
				.WithWindow(ExampleUtils.ScreenRect)
				.AlignMiddleCenter(grid)
				.To3DXY();      
		
		foreach(PointyHexPoint point in grid)
		{
			SpriteCell cell = Instantiate(cellPrefab);
			Vector3 worldPoint = map[point];
			
			cell.transform.parent = root.transform;
			cell.transform.localScale = new Vector3(0.3f, 0.3f, 0.3f);
			cell.transform.localPosition = worldPoint;
			cell.name = point.ToString();
			grid[point] = cell;
		}
	}
	
	public void Update()
	{
		var isDirty = false;
		if (Input.GetKeyDown(KeyCode.X))
		{
			normalDirection = NormalDirection.X;
			isDirty = true;
		}
		
		if (Input.GetKeyDown(KeyCode.Y))
		{
			normalDirection = NormalDirection.Y;
			isDirty = true;
		}
		
		if (Input.GetKeyDown(KeyCode.Z))
		{
			normalDirection = NormalDirection.Z;
			isDirty = true;
		} 
		
		if (Input.GetMouseButtonDown(0))
		{
			UpdateSelectedPoint();
			isDirty = true;
		}
		
		if (Input.GetMouseButtonDown(1))
		{
			UpdateMirrorPoint();
			isDirty = true;
		}
		
		if (isDirty)
		{
			UpdateReflectedPoint();
			UpdateGridColors();
		}
		
		DrawNormal();
		DrawMirror();
		DrawReflection();

	}

	
	private void UpdateGridColors()
	{
		foreach (var point in grid)
		{
			grid[point].Color = ExampleUtils.Colors[8];
			grid[point].HighlightOn = false;
		}
		
		if (grid.Contains(mirrorHexPoint))
		{
			grid[mirrorHexPoint].Color = Color.black;
		}
		
		if (grid.Contains(selectedPoint))
		{
			grid[selectedPoint].HighlightOn = true;
		}
		
		if (grid.Contains(reflectedPoint))
		{
			grid[reflectedPoint].HighlightOn = true;
		}
	}
	
	private void UpdateMirrorPoint()
	{
		Vector3 worldPosition = ExampleUtils.ScreenToWorld(root, Input.mousePosition);
		PointyHexPoint hexPoint = map[worldPosition];
		
		if (grid.Contains(hexPoint))
		{
			mirrorHexPoint = hexPoint;
		}
	}
	
	private void UpdateSelectedPoint()
	{
		Vector3 worldPosition = ExampleUtils.ScreenToWorld(root, Input.mousePosition);
		PointyHexPoint hexPoint = map[worldPosition];
		
		if (grid.Contains(hexPoint))
		{
			selectedPoint = hexPoint;
		}
	}
	
	private void UpdateReflectedPoint()
	{
		switch (normalDirection)
		{
		case NormalDirection.X:
			reflectedPoint = ReflectNormalX(selectedPoint, mirrorHexPoint);
			break;
		case NormalDirection.Y:
			reflectedPoint = ReflectNormalY(selectedPoint, mirrorHexPoint);
			break;
		case NormalDirection.Z:
			reflectedPoint = ReflectNormalZ(selectedPoint, mirrorHexPoint);
			break;
		default:
			throw new ArgumentOutOfRangeException();
		}
	}
	
	private static PointyHexPoint ReflectNormalX(PointyHexPoint point, PointyHexPoint mirrorPoint)
	{
		var translated = point - mirrorPoint; //move problem to origin
		var reflected = new PointyHexPoint(-translated.X - translated.Y, translated.Y); //reflect
		var result = reflected + mirrorPoint; //move problem back
		
		return result;
	}
	
	private static PointyHexPoint ReflectNormalY(PointyHexPoint point, PointyHexPoint mirrorPoint)
	{
		var translated = point - mirrorPoint; //move problem to origin
		var reflected = new PointyHexPoint(-translated.X, translated.X + translated.Y); //reflect
		var result = reflected + mirrorPoint; //move problem back
		
		return result;
	}
	
	private static PointyHexPoint ReflectNormalZ(PointyHexPoint point, PointyHexPoint mirrorPoint)
	{
		var translated = point - mirrorPoint; //move problem to origin
		var reflected = new PointyHexPoint(-translated.Y, -translated.X); //reflect
		var result = reflected + mirrorPoint; //move problem back
		
		return result;
	}
	
	private void DrawNormal()
	{
		PointyHexPoint point0, point1;
		
		switch (normalDirection)
		{
		case NormalDirection.X:
			point0 = mirrorHexPoint + PointyHexPoint.East;
			point1 = mirrorHexPoint + PointyHexPoint.West;
			break;
		case NormalDirection.Y:
			point0 = mirrorHexPoint + PointyHexPoint.NorthEast;
			point1 = mirrorHexPoint + PointyHexPoint.SouthWest;
			break;
		case NormalDirection.Z:
			point0 = mirrorHexPoint + PointyHexPoint.NorthWest;
			point1 = mirrorHexPoint + PointyHexPoint.SouthEast;
			break;
		default:
			throw new ArgumentOutOfRangeException();
		}
		
		if (grid.Contains(point0) && grid.Contains(point1))
		{
			Vector3 worldPoint0 = grid[point0].transform.position + Vector3.back;
			Vector3 worldPoint1 = grid[point1].transform.position + Vector3.back;
			
			normalLineRenderer.SetPosition(0, worldPoint0);
			normalLineRenderer.SetPosition(1, worldPoint1);
		}
	}
	
	private void DrawMirror()
	{
		PointyHexPoint point0, point1;
		
		switch (normalDirection)
		{
		case NormalDirection.X:
			point0 = mirrorHexPoint + new PointyHexPoint(-1, 2);
			point1 = mirrorHexPoint + new PointyHexPoint(1, -2);
			break;
		case NormalDirection.Y:
			point0 = mirrorHexPoint + new PointyHexPoint(-2, 1);
			point1 = mirrorHexPoint + new PointyHexPoint(2, -1);
			break;
		case NormalDirection.Z:
			point0 = mirrorHexPoint + new PointyHexPoint(1, 1);
			point1 = mirrorHexPoint + new PointyHexPoint(-1, -1);
			break;
		default:
			throw new ArgumentOutOfRangeException();
		}
		
		if (grid.Contains(point0) && grid.Contains(point1))
		{
			Vector3 worldPoint0 = grid[point0].transform.position + Vector3.back;
			Vector3 worldPoint1 = grid[point1].transform.position + Vector3.back;
			
			mirrorLineRenderer.SetPosition(0, worldPoint0);
			mirrorLineRenderer.SetPosition(1, worldPoint1);
		}
	}
	
	private void DrawReflection()
	{
		Vector3 worldPoint0 = grid[selectedPoint].transform.position + Vector3.back;
		Vector3 worldPoint1 = grid[mirrorHexPoint].transform.position + Vector3.back;
		Vector3 worldPoint2 = grid[reflectedPoint].transform.position + Vector3.back;

		reflectionLineRenderer1.SetPosition(0, worldPoint0);
		reflectionLineRenderer1.SetPosition(1, worldPoint1);
		reflectionLineRenderer2.SetPosition(0, worldPoint1);
		reflectionLineRenderer2.SetPosition(1, worldPoint2);
	}
}