using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using Interop;
using OpenTK;
using OpenTK.Graphics;

namespace Fuseren
{
    public class HeatmapSample
    {
        public double Desirability { get; set; }
        public int X { get; private set; }
        public int Y { get; private set; }

        public HeatmapSample(int x, int y, double desirability)
        {
            X = x;            
            Y = y;
            Desirability = desirability;
        }
    }

    class Heatmap
    {
        readonly HeatmapSample [,]_heatmapSamples;

        public Heatmap(int width, int height)
        {
            _heatmapSamples = new HeatmapSample[width,height];
        }

        public void SetSample(HeatmapSample sample)
        {
            _heatmapSamples[sample.X, sample.Y] = sample;
        }

        public HeatmapSample GetSample(int x, int y)
        {
            return _heatmapSamples[x, y];
        }

        public double GetDesirability(int x, int y)
        {
            var sample = _heatmapSamples[x, y];
            if (sample == null)
                return 0;

            return sample.Desirability;
        }

        public void AccumulateSample(HeatmapSample sample)
        {
            var curSample = _heatmapSamples[sample.X, sample.Y];
            if (curSample == null)
            {
                SetSample(sample);
                return;
            }
            
            curSample.Desirability += sample.Desirability;
            if (curSample.Desirability > 1)
            {
                curSample.Desirability = 1;
            }
            else if (curSample.Desirability < 0)
            {
                curSample.Desirability = 0;
            }
        }

        public Bitmap ToBitmap()
        {
            var bitmap = new Bitmap(_heatmapSamples.GetLength(0), _heatmapSamples.GetLength(1));

            for (var x = 0; x < bitmap.Width; ++x)
            {
                for (var y = 0; y < bitmap.Height; ++y)
                {
                    var sample = _heatmapSamples[x, y];
                    double desire = 0;
                    if(sample != null)
                        desire = sample.Desirability;

                    var color = new Color4((float)desire, (float)(1 - desire), 0, 1);
                    bitmap.SetPixel(x, y, Color.FromArgb(color.ToArgb()));       
                }
            }

            return bitmap;
        }

        public Size Size { get { return new Size(_heatmapSamples.GetLength(0), _heatmapSamples.GetLength(1)); } }

        public int NumSamples { get { return _heatmapSamples.Length; } }
    }

    class HeatmapGenerator
    {
        readonly MapInfo _mapInfo;
        readonly double _sigma;
        readonly List<BoxData> _boxes;

        public static async Task<Heatmap> StartAsync(MapInfo mapInfo, IEnumerable<BoxData> boxes, double sigma)
        {
            var heatmapGen = new HeatmapGenerator(mapInfo, boxes, sigma);
            return await Task.Run(() => heatmapGen.Process());
        }

        HeatmapGenerator(MapInfo mapInfo, IEnumerable<BoxData> boxes, double sigma)
        {
            _mapInfo = mapInfo;
            _sigma = sigma;
            _boxes = boxes.ToList();
        }

        Heatmap Process()
        {
            var size = _mapInfo.GetMapSize();
            var sizeInPixels = _mapInfo.GetSizeInPixels();
            var heatmap = new Heatmap(sizeInPixels.Width, sizeInPixels.Height);

            for(var x = 0;x < size.Width;++x)
            {
                for(var y = 0;y < size.Height;++y)
                    ProcessTile(heatmap, new TilePos(x, y));                
            }
            ProcessModifiers(heatmap);

            return heatmap;
        }

        void ProcessModifiers(Heatmap heatmap)
        {
            foreach (var modifier in _mapInfo.Modifiers)
            {
                var modifierRect = new Rectangle((int)modifier.X, (int)modifier.Y, modifier.Width, modifier.Height);
                for (var x = modifierRect.Left-1; x < modifierRect.Right+1; ++x)
                {
                    for (var y = modifierRect.Top-1; y < modifierRect.Bottom+1; ++y)
                    {
	                    if (_mapInfo.IsInvalidPoint(new Point(x, y)))
		                    continue;

                        heatmap.AccumulateSample(new HeatmapSample(x, y, GetScaleBasedOnModiferType(modifier.Type)));
                    }
                }
            }
        }

        void ProcessTile(Heatmap heatmap, TilePos tilePos)
        {
            var tile = _mapInfo.GetTileType(tilePos);
            var tileRect = new Rectangle(tilePos.X * _mapInfo.TileWidth, tilePos.Y * _mapInfo.TileHeight, _mapInfo.TileWidth, _mapInfo.TileHeight);
            var center = new Vector2d((tileRect.Left + tileRect.Right) * 0.5, (tileRect.Bottom + tileRect.Top) * 0.5);

            var sigmaSqr2 = 2 * _sigma * _sigma;

            for (var x = tileRect.Left; x < tileRect.Right; ++x)
            {
                for (var y = tileRect.Top; y < tileRect.Bottom; ++y)
                {                    
                    var desire = 0.0;

                    if (tile == TileType.HorizontalStraight)
                    {
                        var grad = center.Y - y;
                        desire = Math.Exp(-(grad * grad) / sigmaSqr2);
                    }
                    else if (tile == TileType.VerticalStraight)
                    {
                        var grad = center.X - x;
                        desire = Math.Exp(-(grad * grad) / sigmaSqr2);
                    }
                    else if (tile == TileType.LeftCorner)
                    {
                        var radius = _mapInfo.TileWidth * 0.5;
                        var v = new Vector2d(x, y) - new Vector2d(tileRect.Left, tileRect.Top);
                        double grad;
                        if (v.Length < radius)
                            grad = radius - v.Length;
                        else
                            grad = v.Length - radius;

                        desire = Math.Exp(-(grad * grad) / sigmaSqr2);
                    }
                    else if (tile == TileType.DownCorner)
                    {
                        var radius = _mapInfo.TileWidth * 0.5;
                        var v = new Vector2d(x, y) - new Vector2d(tileRect.Left, tileRect.Bottom);
                        double grad;
                        if (v.Length < radius)
                            grad = radius - v.Length;
                        else
                            grad = v.Length - radius;

                        desire = Math.Exp(-(grad * grad) / sigmaSqr2);
                    }
                    else if (tile == TileType.RightCorner)
                    {
                        var radius = _mapInfo.TileWidth * 0.5;
                        var v = new Vector2d(x, y) - new Vector2d(tileRect.Right, tileRect.Bottom);
                        double grad;
                        if (v.Length < radius)
                            grad = radius - v.Length;
                        else
                            grad = v.Length - radius;

                        desire = Math.Exp(-(grad * grad) / sigmaSqr2);
                    }
                    else if (tile == TileType.UpCorner)
                    {
                        var radius = _mapInfo.TileWidth * 0.5;
                        var v = new Vector2d(x, y) - new Vector2d(tileRect.Right, tileRect.Top);
                        double grad;
                        if (v.Length < radius)
                            grad = radius - v.Length;
                        else
                            grad = v.Length - radius;

                        desire = Math.Exp(-(grad * grad) / sigmaSqr2);
                    }
                    else if (tile == TileType.Intersection)
                    {
                        var grad = center.X - x;
                        desire = Math.Exp(-(grad * grad) / sigmaSqr2) * 0.5;

                        grad = center.Y - y;
                        desire += Math.Exp(-(grad * grad) / sigmaSqr2) * 0.5;
                    }
                    else
                        continue;

                    heatmap.SetSample(new HeatmapSample(x, y, 0.5 + desire * 0.3));
                }
            }
        }

        double GetScaleBasedOnModiferType(ModifierType type)
        {
            switch (type)
            {
                case ModifierType.Unknown:
                    return 0;
                case ModifierType.Ice:
                    return -0.4;
                case ModifierType.Booster:
                    return 0.5;
                case ModifierType.Mud:
                    return -0.6;
            }

            return 0;
        }
    }
}