using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace Assets.Common
{
    public static class PointUtils
    {
        public static Point Mean(IEnumerable<Point> points)
        {
            var result = new Point(0, 0);
            var i = 0;

            foreach (var point in points)
            {
                result.x = (result.x * i + point.x) / (i + 1);
                result.y = (result.y * i + point.y) / (i + 1);
                
                i++;
            }

            return result;
        }

        public static IEnumerable<Point> CalculateNormals(IEnumerable<Point> points)
        {
            var pairs  = points.RotateRight().Zip(points, (a, b) => (a, b));
            var thirds = pairs.Zip(points.RotateLeft(), (pair, c) => (pair.a, pair.b, c));

            return thirds.Select(third =>
            {
                var left = (third.a - third.b).Direction;
                var right = (third.c - third.b).Direction;

                return ((left + right) / 2).Direction * (IsClockwise(third.a, third.b, third.c) ? -1 : 1);
            });
        }
        
        public static double AngleBetween(Point center, Point a, Point b)
        {
            a = a - center;
            b = b - center;

            var dot = a.x * b.x + a.y * b.y;
            var det = a.x * b.y - a.y * b.x;

            var angle = Math.Atan2(det, dot);

            return angle > 0 ? angle : 2*Math.PI + angle;
        }

        public static bool IsClockwise(Point center, Point a, Point b)
        {
            var xa = a.x - center.x;
            var xb = b.x - center.x;
            var ya = a.y - center.y;
            var yb = b.y - center.y;

            return xa * yb - xb * ya < 0;
        }

        public static bool IsClockwise(IEnumerable<Point> points)
        {
            return points.Zip(points.RotateRight(), (a, b) => (b.x - a.x) * (b.y + a.y)).Aggregate((a, b) => a + b) < 0;
        }

        public static bool IsPointInside(Point point, IEnumerable<Point> points)
        {
            var polygon = points.ToArray();
            
            var j = polygon.Length - 1;
            var inside = false;

            for (int i = 0; i < polygon.Length; j = i++)
            {
                var pi = polygon[i];
                var pj = polygon[j];

                if (((pi.y <= point.y && point.y < pj.y) || (pj.y <= point.y && point.y < pi.y)) &&
                    (point.x < (pj.x - pi.x) * (point.y - pi.y) / (pj.y - pi.y) + pi.x))
                    inside = !inside;
            }

            return inside;
        }
    }
}