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

public class CombatLib
{

    private static Actor player=null;
    public static Actor Player { 
        get {
            if(player==null)
                player = GameObject.Find("Player(Clone)").GetComponent<Actor>();
            return player;
        }
    }

    public static void ClearPlayer()
    {
        player = null;
    }

    #region Map Functions
        public static int GetDistanceToPlayer(GridPoint2 point)
        {
            return PointyHexPoint.HexNorm(Player.Location.Subtract(point));
        }

        public static GridPoint2 GetClosestToPlayer(IEnumerable<GridPoint2> points)
        {
            int closest = points.Min(p => GetDistanceToPlayer(p));
            var candidates = points.Where((p) => GameMap.Available(p)).Where(p => GetDistanceToPlayer(p) == closest).ToList();
            return ListHelper.DrawFromAtRandom(candidates);
        }

        public static void RotateToFace(Transform subject, Vector2 target)
        {
            Vector2 diff = target - (Vector2)subject.position;

            if (diff.magnitude == 0)
                return;

            float dx = diff.x;
            float dy = diff.y;
            float aimangle = Mathf.Atan2(dx, dy) * -180 / Mathf.PI;

            subject.eulerAngles = new Vector3(0, 0, aimangle);
        }


    #endregion

    #region FOV

    public static List<GridPoint2> GetFOV(Actor actor)
    {
        if (actor == null)
            return new List<GridPoint2>();
        cacheHits = 0;
        cacheMisses = 0;
        System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
        sw.Start();
        var result = GameMap.instance.Grid
            .Points
            .Where(p => HasClearLOSCollider(actor.Location, p))
            .ToList();
        sw.Stop();
        if (sw.ElapsedMilliseconds > 8f)
            Debug.LogWarning("Slow Collider FOV!  Computed in " + sw.ElapsedMilliseconds + "ms           hits: " + cacheHits + "   misses: " + cacheMisses);

        return result;
    }

    public static bool HasClearLOSCollider(GridPoint2 from, GridPoint2 to, Func<GameCell, bool> blockPredicate = null)
    {
        if (blockPredicate == null)
            blockPredicate = c => c.HasActor() && ((Actor)c.Actor).BlocksLineOfSight;

        foreach (GameCell hitcell in GetCellsToCheck(from, to))
        {
            if (hitcell == null || hitcell == GameMap.Cell[from] || hitcell == GameMap.Cell[to])
                continue;

            if (blockPredicate(hitcell))
                return false;
        }

        return true;
    }

    private static Dictionary<GridPoint2, List<GridPoint2>> Distance_PointsToCheckCache = new Dictionary<GridPoint2, List<GridPoint2>>();
    private static int cacheHits = 0;
    private static int cacheMisses = 0;

    private static IEnumerable<GameCell> GetCellsToCheck(GridPoint2 from, GridPoint2 to)
    {
        GridPoint2 dist = to - from;
        if (Distance_PointsToCheckCache.ContainsKey(dist))
        {
            cacheHits++;
            return Distance_PointsToCheckCache[dist].Select(p => GameMap.Cell[p + from]);
        }
        else
        {
            cacheMisses++;
            var cells = BuildCellsToCheckFromRaycast(from, to);
            AddToCache(from, to, cells);
            return cells;
        }
    }

    private static List<GameCell> BuildCellsToCheckFromRaycast(GridPoint2 from, GridPoint2 to)
    {
        Vector2 fromPos = GameMap.Cell[from].transform.position;
        Vector2 toPos = GameMap.Cell[to].transform.position;
        Vector2 line = toPos - fromPos;

        RaycastHit2D[] hits = Physics2D.CircleCastAll(fromPos, TurnBasedGameLoop.instance.aimPreviewLooseness, line, line.magnitude);

        return hits.Select(h => h.transform.GetComponent<GameCell>()).ToList();
    }

    private static void AddToCache(GridPoint2 from, GridPoint2 to, List<GameCell> cells)
    {
        GridPoint2 dist = to - from;
        Distance_PointsToCheckCache[dist] = cells.Select(c => GameMap.WorldToGridPoint(c.transform.position) - from).ToList();
    }

    #endregion

}

public static class TakeExtension
{
    public static IEnumerable<T> TakeUntilIncluding<T>(this IEnumerable<T> list, Func<T, bool> predicate)
    {
        foreach (T el in list)
        {
            yield return el;
            if (predicate(el))
                yield break;
        }
    }
}