package squidpony.squidai;

import squidpony.squidmath.Coord;
import squidpony.squidmath.OrderedMap;


/**
 * Static utilities for use in AOE and anything else that might need HashMaps of Coord keys to Double values.
 * Created by Tommy Ettinger on 7/13/2015.
 */
public class AreaUtils {
    /**
     * This takes a 2D boolean array and returns a HashMap of Coord keys to Double values, but will only use the value
     * 1.0, and only for positions in map that have as their boolean element true.
     * @param map width by height, commonly generated by FOV methods
     * @return a HashMap of Coord keys to Double values, but the only value used is 1.0
     */
    public static OrderedMap<Coord, Double> arrayToHashMap(boolean[][] map)
    {
        OrderedMap<Coord, Double> ret = new OrderedMap<>();
        for(int i = 0; i < map.length; i++)
        {
            for(int j = 0; j < map[i].length; j++)
            {
                if(map[i][j])
                    ret.put(Coord.get(i, j), 1.0);
            }
        }
        return ret;
    }

    /**
     * This takes a 2D double array called map and returns a HashMap of Coord keys to Double values, and will have a key
     * for every position in map that is greater than 0.0, with values equal to those in map.
     * @param map width by height, commonly generated by FOV methods
     * @return a HashMap of Coord keys to Double values, with values all greater than 0.0
     */
    public static OrderedMap<Coord, Double> arrayToHashMap(double[][] map)
    {
        OrderedMap<Coord, Double> ret = new OrderedMap<>();
        for(int i = 0; i < map.length; i++)
        {
            for(int j = 0; j < map[i].length; j++)
            {
                if(map[i][j] > 0.0)
                    ret.put(Coord.get(i, j), map[i][j]);
            }
        }
        return ret;
    }

    /**
     * This takes a 2D double array and returns a HashMap of Coord keys to Double values, but will only use the value
     * 1.0, and only does this if the passed double[][] has a value at that position that is greater than cutoff.
     * For example, a cutoff of 0.3 will make all elements in the 2D array that are 0.3 or less be ignored and not put
     * into the HashMap, but all elements that are greater than 0.3 will be placed in as 1.0.
     * @param map width by height, commonly generated by FOV methods
     * @param cutoff any elements greater than this will be 1.0 in the return, anything else will be ignored
     * @return a HashMap of Coord keys to Double values, but the only value used is 1.0
     */
    public static OrderedMap<Coord, Double> arrayToHashMap(double[][] map, double cutoff)
    {
        OrderedMap<Coord, Double> ret = new OrderedMap<>();
        for(int i = 0; i < map.length; i++)
        {
            for(int j = 0; j < map[i].length; j++)
            {
                if(map[i][j] > cutoff)
                    ret.put(Coord.get(i, j), 1.0);
            }
        }
        return ret;
    }

    /**
     * This takes a DijkstraMap that has already completed a scan() and returns a HashMap of Coord keys to Double
     * values, and will have a key for every position that was reached in the DijkstraMap, with 1.0 as the only value.
     * @param map a double[][] returned by a DijkstraMap running its scan()
     * @return a HashMap of Coord keys to Double values, with values of 1.0 only
     */
    public static OrderedMap<Coord, Double> dijkstraToHashMap(double[][] map)
    {
        OrderedMap<Coord, Double> ret = new OrderedMap<>();
        for(int i = 0; i < map.length; i++)
        {
            for(int j = 0; j < map[i].length; j++)
            {
                if(map[i][j] < DijkstraMap.WALL)
                    ret.put(Coord.get(i, j), 1.0);
            }
        }
        return ret;
    }

    /**
     * Checks that the given end Coord can be targeted from the given origin Coord given the directional targeting
     * rules specified by limit. If any of the arguments are null, returns true (it assumes that any limits are not
     * valid and don't restrict anything). The following AimLimit enum values for limit have the following meanings:
     *
     * <ul>
     *     <li>AimLimit.FREE makes no restrictions; it is equivalent here to passing null for limit.</li>
     *     <li>AimLimit.EIGHT_WAY will only consider Points to be valid targets
     *     if they are along a straight line with an angle that is a multiple of 45 degrees, relative to the positive x
     *     axis. Essentially, this limits the points to those a queen could move to in chess.</li>
     *     <li>AimLimit.ORTHOGONAL will cause the AOE to only consider Points to be valid targets if
     *     they are along a straight line with an angle that is a multiple of 90 degrees, relative to the positive x
     *     axis. Essentially, this limits the points to those a rook could move to in chess.</li>
     *     <li>AimLimit.DIAGONAL will cause the AOE to only consider Points to be valid targets if they are along a
     *     straight line with an angle that is 45 degrees greater than a multiple of 90 degrees, relative to the
     *     positive x axis. Essentially, this limits the points to those a bishop could move to in chess.</li>
     * </ul>
     *
     * @param limit an AimLimit enum that restricts valid points unless it is AimLimit.FREE or null
     * @param origin where the user is
     * @param end where the point we want to verify is
     * @return true if the point is a valid target or if the limits are invalid (non-restricting), false otherwise
     */
    public static boolean verifyLimit(AimLimit limit, Coord origin, Coord end)
    {
        if (limit != null && origin != null && end != null) {
            switch (limit) {
                case EIGHT_WAY:
                    if(Math.abs(end.x - origin.x) == Math.abs(end.y - origin.y) ||
                            end.x == origin.x || end.y == origin.y)
                    {
                        return true;
                    }
                    break;
                case DIAGONAL:
                    if(Math.abs(end.x - origin.x) == Math.abs(end.y - origin.y))
                    {
                        return true;
                    }
                    break;
                case ORTHOGONAL:
                    if(end.x == origin.x || end.y == origin.y)
                    {
                        return true;
                    }
                    break;
                case FREE: return true;
            }
            return false;
        }
        return true;
    }

    /**
     * Checks that the given end Coord can be targeted from the given origin Coord given the complete targeting rules
     * specified by reach. If any of the arguments are null, returns true (it assumes that any limits are not
     * valid and don't restrict anything). If reach.limit is null, it also returns true. Otherwise, it uses the metric,
     * minDistance, and maxDistance from reach to calculate if end is target-able from origin assuming an unobstructed
     * playing field.
     *
     * @param reach a Reach object that, if non-null, gives limits for how targeting can proceed.
     * @param origin where the user is
     * @param end where the point we want to verify is
     * @return true if the point is a valid target or if the limits are invalid (non-restricting), false otherwise
     */
    public static boolean verifyReach(Reach reach, Coord origin, Coord end)
    {
        if (reach != null && reach.limit != null && origin != null && end != null) {
            switch (reach.limit) {
                case EIGHT_WAY:
                    if(Math.abs(end.x - origin.x) == Math.abs(end.y - origin.y) ||
                            end.x == origin.x || end.y == origin.y)
                    {
                        return reach.metric.inRange(origin.x, origin.y, end.x, end.y,
                                reach.minDistance, reach.maxDistance);
                    }
                    break;
                case DIAGONAL:
                    if(Math.abs(end.x - origin.x) == Math.abs(end.y - origin.y))
                    {
                        return reach.metric.inRange(origin.x, origin.y, end.x, end.y,
                                reach.minDistance, reach.maxDistance);
                    }
                    break;
                case ORTHOGONAL:
                    if(end.x == origin.x || end.y == origin.y)
                    {
                        return reach.metric.inRange(origin.x, origin.y, end.x, end.y,
                                reach.minDistance, reach.maxDistance);
                    }
                    break;
                case FREE: return reach.metric.inRange(origin.x, origin.y, end.x, end.y,
                            reach.minDistance, reach.maxDistance);
            }
            return false;
        }
        return true;
    }
}
