using UnityEngine;
using System.Linq;
using System.Collections.Generic;
using Gamelogic.Grids2;

public class GameMap : GridBehaviour<GridPoint2, TileCell> {

    public static GameMap Cell;

    static public bool Contains(GridPoint2 point)
    {
        return instance.Grid.Contains(point);
    }

    static public bool Available(GridPoint2 destination)
    {
        return (Contains(destination) && !Cell[destination].HasActor());
    }

    public static GameMap instance;

    public GameCell this[GridPoint2 p]
    {
        get
        {
            return (GameCell)(instance.Grid[p]);
        }
    }

    public static GameCell GetCellUnderMouse()
    {
        Vector2 worldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        return GetCellAtWorld(worldPos);
    }

    public static GridPoint2 GetGridPointUnderMouse()
    {
        Vector2 worldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        return WorldToGridPoint(worldPos);
    }

    public static GameCell GetCellAtWorld(Vector2 worldPos)
    {
        GridPoint2 p = WorldToGridPoint(worldPos);

        if (Contains(p))
            return Cell[p];
        else
            return null;        
    }

    public static bool IsEdge(GridPoint2 p)
    {
        var outsides = PointyHexPoint.GetOrthogonalNeighbors(p)
            .Where(q => !GameMap.Contains(q));

        return outsides.Count() != 0;
    }

    public static GridPoint2 WorldToGridPoint(Vector2 worldPos)
    {
        // For Rect map, do not know why I need to subtract 1 y, I must be doing this wrong...
        //return new GridPoint2(0, 1).Add(GameMap.instance.GridMap.WorldToGridToDiscrete(worldPos));

        // why do I need this North.Add?  WorldToGridToDiscrete seems off by one
        return FlatHexPoints.North.Add(GameMap.instance.GridMap.WorldToGridToDiscrete(worldPos));
    }

    public static bool AreOnHexLine(GridPoint2 p1, GridPoint2 p2)
    {

        int dist = PointyHexPoint.HexNorm(p1 - p2);
        return
            (p2 == p1 + (dist * PointyHexPoint.NorthEast)) ||
            (p2 == p1 + (dist * PointyHexPoint.NorthWest)) ||
            (p2 == p1 + (dist * PointyHexPoint.East)) ||
            (p2 == p1 + (dist * PointyHexPoint.West)) ||
            (p2 == p1 + (dist * PointyHexPoint.SouthEast)) ||
            (p2 == p1 + (dist * PointyHexPoint.SouthWest));
    }

    public static GridPoint2 GetBestHexDirection(GridPoint2 from, GridPoint2 to)
    {
        GridPoint2 best = PointyHexPoint.GetOrthogonalNeighbors(from)
            .Where(q => GameMap.Contains(q))
            .OrderBy(p => (Cell[p].transform.position - Cell[to].transform.position).magnitude)
            .FirstOrDefault();

        return best - from;
    }

    public static IEnumerable<GridPoint2> GetPointsExactlyAtDistance(GridPoint2 point, int dist)
    {
        return instance.Grid.Points
            .Where(p => PointyHexPoint.HexNorm(point - p) == dist);
    }

    public static IEnumerable<GridPoint2> GetPointsWithinDistance(GridPoint2 point, int dist)
    {
        return instance.Grid.Points
            .Where(p => PointyHexPoint.HexNorm(point - p) <= dist);
    }

     public static GridPoint2 GetClosestPointToLine(GridPoint2 start, GridPoint2 end, List<GridPoint2> points)
    {
        float mindist = 99999;
        GridPoint2 closest = GridPoint2.None;

        foreach(GridPoint2 point in points)
        {
            if(!GameMap.Contains(point))
                continue;

            float dist = DistanceFromAimLineToPoint(start, end, point);
            if(dist < mindist)
            {
                mindist = dist;
                closest = point;
            }
        }

        return closest;
    }

    private static float DistanceFromAimLineToPoint(GridPoint2 startPoint, GridPoint2 endPoint, GridPoint2 checkPoint)
    {
        Vector2 start = PointToPos(startPoint);
        Vector2 end   = PointToPos(endPoint);
        Vector2 point = PointToPos(checkPoint);

        Vector2 pureAimLine = (end - start);
        pureAimLine.Normalize();

        Vector2 lineToPoint = point - start;
        float projection = Vector2.Dot(lineToPoint, pureAimLine);
        Vector2 closestPointOnAimLine = start + pureAimLine * projection;
        return (closestPointOnAimLine - point).magnitude;
    }

    private static Vector2 PointToPos(GridPoint2 p)
    {
        return GameMap.Cell[p].transform.position;
    }


    public static void MarkCellsAimable(IEnumerable<GridPoint2> points)
    {
        ClearHighLight();
        foreach (GridPoint2 p in points)
            Cell[p].IsAimable = true;
    }

    public static void ClearHighLight()
    {
        foreach (GridPoint2 p in instance.Grid.Points)
        {
            Cell[p].IsAimable = false;
            Cell[p].HighLight = false;
        }
    }
    public override void InitGrid()
    {
        Debug.Log("Game InitGrid,  Grid is " + ((Grid == null) ? "null" : Grid.ToString()));
        instance = this;
        Cell = instance;
    }

}
