using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;

namespace Game200
{
    public class Map
    {
        public Vec dim;
        public Segment[,] x, y;
        public Texture2D background;
        Color[] colorData;

        public Map() {}

        public void SetColorData()
        {
            colorData = new Color[dim.X * dim.Y * C.length * C.length];
            for (int i = 0; i < colorData.Length; i++)
                colorData[i] = Color.Black;
            background = new Texture2D(C.graphicsDevice, dim.Y * C.length, dim.X * C.length);
            background.SetData(colorData);
        }

        /*public Map(int dimX, int dimY)
        {
            dim = new Vec(dimX, dimY);
            x = new Segment[dim.X, dim.Y];
            y = new Segment[dim.X, dim.Y];
            for (int i = 0; i < dim.X; i++)
                for (int j = 0; j < dim.Y; j++)
                {
                    x[i, j] = new Segment(i, j, true, (i == dim.X - 3 || i == 2 || (i == 10 && j <= 30) || (i == 30 && j >= 30)));
                    y[i, j] = new Segment(i, j, false, (j == dim.Y - 3 || j == 2));
                }
        }*/

        public void Update()
        {
            foreach (Segment s in x)
                s.Update();
            foreach (Segment s in y)
                s.Update();
        }

        public bool IsDone()
        {
            foreach (Segment s in x)
                if (s.isWallCopy && !s.isDone)
                    return false;
            foreach (Segment s in y)
                if (s.isWallCopy && !s.isDone)
                    return false;
            return true;
        }

        public float ShineRay(Source source, float angle)
        {
            int distXX = 1, distYY = 1;
            float distXY = 1 / (float)Math.Tan(angle), distYX = (float)Math.Tan(angle);
            if ((float)Math.Sin(angle) < 0)
            {
                distXX *= -1;
                distXY *= -1;
            }
            if ((float)Math.Cos(angle) < 0)
            {
                distYX *= -1;
                distYY *= -1;
            }
            Vector2 touchDist = new Vector2(100000);
            Vec impX = new Vec(-1, -1), impY = new Vec(-1, -1);
            Vector2 realX = new Vector2(-1), realY = new Vector2(-1);
            for (int i = 0;; i++)
            {
                int posX = source.position.X + i * distXX;
                float posY = source.position.Y + i * distXY;
                realX = new Vector2(posX, posY);
                if ((int)posX < 0 || (int)posX >= dim.X || (int)posY < 0 || (int)posY >= dim.Y)
                    break;
                if (x[posX, (int)posY].isWallCopy)
                {
                    impX = new Vec(posX, (int)posY);
                    touchDist.X = Vector2.Distance(new Vector2(posX, posY), new Vector2(source.position.X, source.position.Y));
                    break;
                }
            }
            for (int i = 0;; i++)
            {
                int posY = source.position.Y + i * distYY;
                float posX = source.position.X + i * distYX;
                realY = new Vector2(posX, posY);
                if ((int)posX < 0 || (int)posX >= dim.X || (int)posY < 0 || (int)posY >= dim.Y)
                    break;
                if (y[(int)posX, posY].isWallCopy)
                {
                    impY = new Vec((int)posX, posY);
                    touchDist.Y = Vector2.Distance(new Vector2(posX, posY), new Vector2(source.position.X, source.position.Y));
                    break;
                }
            }
            if (touchDist.X < touchDist.Y)
            {
                if (touchDist.X < 100000)
                {
                    x[impX.X, impX.Y].curColor = source.color;
                    return Vector2.Distance(realX, source.position.ToVector2());
                }
                else
                    return C.screenDim.X / C.length + 1;
            }
            else
            {
                if (touchDist.Y < 100000)
                {
                    y[impY.X, impY.Y].curColor = source.color;
                    return Vector2.Distance(realY, source.position.ToVector2());
                }
                else
                    return C.screenDim.X / C.length + 1;
            }
        }

        public void ShineRoom(Source source)
        {
            Color[] newColorData = new Color[colorData.Length];
            for (int i = 0; i < newColorData.Length; i++)
                newColorData[i] = Color.Transparent;
            for (int i = 0; i < dim.X * C.length; i++)
            {
                ShineRay2(source, new Vec(i, 0), newColorData);
                ShineRay2(source, new Vec(i, dim.Y * C.length - 1), newColorData);
            }
            for (int i = 0; i < dim.Y * C.length; i++)
            {
                ShineRay2(source, new Vec(0, i), newColorData);
                ShineRay2(source, new Vec(dim.X * C.length - 1, i), newColorData);
            }
            for (int i = 0; i < dim.Y * C.length; i++)
                for (int j = 0; j < dim.X * C.length; j++)
                    colorData[i + j * dim.Y * C.length] =
                        Combine(colorData[i + j * dim.Y * C.length], newColorData[j + i * dim.X * C.length]);
            //for (int i = 0; i < colorData.Length; i++)
            //    colorData[i] = Combine(colorData[i], newColorData[i]);
            background.SetData(colorData);
        }

        public void ShineRay2(Source source, Vec position, Color[] newColorData)
        {
            Vec souPos = source.position * C.length;
            if (Math.Abs(souPos.X - position.X) < Math.Abs(souPos.Y - position.Y))
            {
                float distX = (float)(souPos.X - position.X) / (float)(souPos.Y - position.Y);
                int distY;
                if (souPos.Y < position.Y)
                    distY = 1;
                else
                    distY = -1;
                for (int i = 0; ; i += distY)
                {
                    int posY = souPos.Y + i;
                    float posX = souPos.X + i * distX + 0.5f;
                    if (!IsInBounds((int)posX, posY))
                        break;
                    if (((int)posX % C.length == 0 && x[(int)posX / C.length, posY / C.length].IsWall()) ||
                        (posY % C.length == 0 && y[(int)posX / C.length, posY / C.length].IsWall()))
                        break;
                    if ((int)posX % C.length == 0 && posY % C.length == 0 && (IsYWall((int)posX - C.length, posY) || IsXWall((int)posX, posY - C.length)))
                        break;

                    newColorData[(int)posX + posY * dim.X * C.length] =
                        ChangeLuminosity(source.color, i * i * (distX * distX + distY * distY));
                }
            }
            else
            {
                if (souPos.X == position.X && souPos.Y == position.Y)
                    return;
                float distY = (float)(souPos.Y - position.Y) / (float)(souPos.X - position.X);
                int distX;
                if (souPos.X < position.X)
                    distX = 1;
                else
                    distX = -1;
                for (int i = 0; ; i += distX)
                {
                    int posX = souPos.X + i;
                    float posY = souPos.Y + i * distY + 0.5f;
                    if (!IsInBounds(posX, (int)posY))
                        break;
                    if ((posX % C.length == 0 && x[posX / C.length, (int)posY / C.length].IsWall()) ||
                        ((int)posY % C.length == 0 && y[posX / C.length, (int)posY / C.length].IsWall()))
                        break;
                    if (posX % C.length == 0 && (int)posY % C.length == 0 && (IsYWall(posX - C.length, (int)posY) || IsXWall(posX, (int)posY - C.length)))
                        break;
                    newColorData[posX + (int)posY * dim.X * C.length] =
                        ChangeLuminosity(source.color, i * i * (distX * distX + distY * distY));
                }
            }
        }

        bool IsXWall(int posX, int posY)
            => !IsInBounds(posX, posY) || x[posX / C.length, posY / C.length].IsWall();

        bool IsYWall(int posX, int posY)
            => !IsInBounds(posX, posY) || y[posX / C.length, posY / C.length].IsWall();

        Color ChangeLuminosity(Color color, float distSq)
        {
            Color ans = new Color(color, color.A);
            float maxDistSquared = 1000 * 1000;
            distSq = Math.Min(maxDistSquared, distSq);
            //float coeff = (float)Math.Max(Math.Max((5 - (float)Math.Pow(distSq, 1f / 8)), 0), 0);
            //float coeff = (float)Math.Max(1 - Math.Exp(-maxDistSquared / distSq) * 10, 0);
            //float coeff = (1 - (float)Math.Sqrt(distSq / maxDistSquared));
            //float coeff = (distSq < maxDistSquared) ? 1 : 0;
            float coeff = 0.8f;
            ans.R = (byte)Math.Min(color.R * coeff + 0.5f, 255);
            ans.G = (byte)Math.Min(color.G * coeff + 0.5f, 255);
            ans.B = (byte)Math.Min(color.B * coeff + 0.5f, 255);
            return ans;
            //return color;
        }

        Color Combine(Color a, Color b)
        {
            return new Color(Mix(a.R, b.R), Mix(a.G, b.G), Mix(a.B, b.B), Mix(a.A, b.A));
        }

        byte Mix(int a, int b)
        {
            return (byte)Math.Min((int)a + b, 255);
        }

        bool IsInBounds(int posX, int posY)
            => posX >= 0 && posX < dim.X * C.length && posY >= 0 && posY < dim.Y * C.length;

        public void DrawBackground()
        {
            C.spriteBatch.Draw(background, Vector2.Zero, Color.White);
        }

        public void Draw()
        {
            foreach (Segment s in x)
                s.Draw();
            foreach (Segment s in y)
                s.Draw();
        }
    }
}
