using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;

namespace Assets.Scripts.Terrain
{
    public class VisibleRoad : MonoBehaviour
    {
        private float[] _roadHeight;

        public void CreatePolyTerrain(PolyData meshData, int width, int length, Vector2 position, Noise perlinNoise,
            List<Vector3> navTargets, float mountainModifer, bool rewriteTop = false)
        {
            _roadHeight = new float[length];
            var hs = perlinNoise.HorizontalSpacing / 2f;
            var vs = perlinNoise.VerticalSpacing / 2f;

            var startY = 0;
            var overwriteY = 0;
            if (rewriteTop)
            {
                startY = 50;
                overwriteY = -50;
            }

            var result = meshData.Terrain;
            for (var y = startY; y < length; ++y)
            {
                for (var x = 0; x < width; ++x)
                {
                    var height = Mathf.PerlinNoise((perlinNoise.Offset.x + position.x + x) / perlinNoise.Scale, (perlinNoise.Offset.x + position.y + y - overwriteY) / perlinNoise.VerticalScale) * perlinNoise.HeightMultiplier;
                    var pos = new Vector3(x * perlinNoise.HorizontalSpacing, height, y * perlinNoise.VerticalSpacing);
                    var type = (int) (Mathf.PerlinNoise((perlinNoise.Offset.y + position.x + x) / perlinNoise.Scale, (perlinNoise.Offset.x + position.y + y - overwriteY) / perlinNoise.Scale) * 3);
                    var uv = new Vector2(0.05f, 0.05f);
                    if (type == 1) uv = new Vector2(0.55f, 0.05f);
                    if (type == 2) uv = new Vector2(0.05f, 0.55f);
                    result[x, y] = new PolyMeshData()
                    {
                        CenterPosition =  new Vector3(pos.x, 0, pos.z),

                        BottomLeft = new Vector3(pos.x - hs, height, pos.z - vs),
                        BottomRight = new Vector3(pos.x + hs, height, pos.z - vs),
                        TopLeft = new Vector3(pos.x - hs, height, pos.z + vs),
                        TopRight = new Vector3(pos.x + hs, height, pos.z + vs),
                        Height = height,
                        Type = type,

                        blIndex = 0,
                        brIndex = 0,
                        tlIndex = 0,
                        trIndex = 0,

                        blUv = uv,
                        tlUv = new Vector2(uv.x, uv.y + 0.4f),
                        brUv = new Vector2(uv.x + 0.4f, uv.y),
                        trUv = new Vector2(uv.x + 0.4f, uv.y + 0.4f)
                    };
                }
            }

            meshData.Road = GeneratePolyRoads(width, length, result, perlinNoise, navTargets, rewriteTop);
            SetMountainHeights(meshData, width, length, perlinNoise, mountainModifer);
        }

        PolyMeshData[,] GeneratePolyRoads(int width, int length, PolyMeshData[,] result, Noise perlinNoise, List<Vector3> navTargets, bool rewriteTop)
        {
            var roads = new PolyMeshData[width, length];
            var x = Random.Range(15, width - 15f);
            var lastHeight = result[(int)x, 0].Height;
            var lanes = Random.Range(3, 12);
            var mergeDistance = Random.Range(10, 30);
            var mDist = 0;

            var startY = rewriteTop ? 50 : 0;
            for (var y = startY; y < length; ++y)
            {
                var height = 0f;
                if (Mathf.Abs(result[(int) x, y].Height - lastHeight) > 0.5f)
                {
                    if (x > 0 && Mathf.Abs(result[(int) x - 1, y].Height - lastHeight) < 0.5f) x++;
                    else if (x < width && Mathf.Abs(result[(int)x + 1, y].Height - lastHeight) < 0.5f) x--;
                }

                for (var lx = x - lanes; lx < x + lanes; ++lx)
                {
                    if (lx > 0 && lx < width)
                    {
                        if (result[(int) lx, y].Type == 3)
                        {
                            result[(int) lx, y].Height /= 1.25f;
                            result[(int)lx, y].Type = 4;
                            result[(int) lx, y].BottomLeft = new Vector3(result[(int) lx, y].BottomLeft.x, result[(int)lx, y].Height, result[(int) lx, y].BottomLeft.z);
                            result[(int) lx, y].BottomRight = new Vector3(result[(int) lx, y].BottomRight.x, result[(int)lx, y].Height, result[(int) lx, y].BottomRight.z);
                            result[(int) lx, y].TopLeft = new Vector3(result[(int) lx, y].TopLeft.x, result[(int)lx, y].Height, result[(int) lx, y].TopLeft.z);
                            result[(int) lx, y].TopRight = new Vector3(result[(int) lx, y].TopRight.x, result[(int)lx, y].Height, result[(int) lx, y].TopRight.z);
                        }

                        result[(int) lx, y].Type = 4;
                        roads[(int)lx, y] = AddRoadPiece(result[(int)lx, y]);
                        height += result[(int) lx, y].Height;

                    }
                }

                roads[(int) x, y].Center = true;

                _roadHeight[y] = height / lanes * 2;

                mDist++;
                if (mDist > mergeDistance)
                {
                    mDist = 0;
                    if (Random.Range(0, 100) > 50)
                        lanes++;
                    else
                        lanes--;

                    lanes = Mathf.Clamp(lanes, 3, 12);
                }

                var temp = Mathf.Sin(y / perlinNoise.RoadSine) - Mathf.Cos(y / (perlinNoise.RoadSine * Random.Range(0.75f, 1f)));
                if (x > width / 2f) x -= temp;
                else x += temp;
                //if (x + 10 + temp < width ) x += temp;
                //else if (x - 10 - temp > 0)
                //    x -= temp;
                x = Mathf.Clamp(x, 10f, width - 10f);

            }

            return roads;
        }

        PolyMeshData AddRoadPiece(PolyMeshData data)
        {
            //todo: duplicate this, too lazy tonight
            var result = new PolyMeshData()
            {
                BottomLeft = data.BottomLeft,
                BottomRight = data.BottomRight,
                TopLeft = data.TopLeft,
                TopRight = data.TopRight,

                Height = data.Height,
                Type = 4,

                blUv = new Vector2(0.6f, 0.6f),
                tlUv = new Vector2(0.6f, 0.9f),
                brUv = new Vector2(0.9f, 0.6f),
                trUv = new Vector2(0.9f, 0.9f),

                tlIndex = data.tlIndex,
                trIndex = data.trIndex,
                blIndex =data.blIndex,
                brIndex = data.brIndex
            };

            return result;
        }

        void SetMountainHeights(PolyData data, int width, int length, Noise noise, float mountainModifer)
        {
            for (var y = 0; y < length; ++y)
            {
                for (var x = 0; x < width; ++x)
                {
                    if (data.Terrain[x, y].Type == 4) continue;
                    //todo: recalculate perlin mass 
                    var height = ((Mathf.PerlinNoise(data.Terrain[x, y].CenterPosition.x / noise.OverlayScale, data.Terrain[x, y].CenterPosition.z / noise.OverlayScale)) * 2) - 1;

                    if (height < 0)
                    {
                        if (data.Terrain[x, y].Type == 4)
                        {
                            ModHeight(data.Terrain[x, y], data.Road[x, y].Height + ((height * noise.OverlayMultiplier) / 10f), mountainModifer);
                            ModHeight(data.Road[x, y], data.Road[x, y].Height + ((height * noise.OverlayMultiplier) / 10f), mountainModifer);
                        }
                        else
                            ModHeight(data.Terrain[x, y], data.Terrain[x, y].Height + (height * noise.OverlayMultiplier), mountainModifer);
                    }
                    else
                    {
                        if (data.Terrain[x, y].Type == 4)
                        {
                            ModHeight(data.Terrain[x, y], data.Road[x, y].Height - ((height * noise.OverlayMultiplier) / 10f), mountainModifer);
                            ModHeight(data.Road[x, y], data.Road[x, y].Height - ((height * noise.OverlayMultiplier) / 10f), mountainModifer);
                        }
                        else
                            ModHeight(data.Terrain[x, y], data.Terrain[x, y].Height - (height * noise.OverlayMultiplier), mountainModifer);
                    }

                }
            }

            for (var y = 0; y < length; ++y)
            {
                for (var x = 0; x < width; ++x)
                {
                    var height = ((Mathf.PerlinNoise(data.Terrain[x, y].CenterPosition.x / noise.SecondOverlayScale, data.Terrain[x, y].CenterPosition.z / noise.SecondOverlayScale)) * 2) - 1;
                    if (data.Terrain[x, y].Type == 4)
                    {
                        height *= (noise.SecondOverlayMultiplier / noise.RoadMultiplier);
                        ModHeight(data.Road[x, y], data.Road[x, y].Height + height, mountainModifer);
                        ModHeight(data.Terrain[x, y], data.Road[x, y].Height - 1f, mountainModifer);
                        continue;
                    }

                    height *= noise.SecondOverlayMultiplier;
                    ModHeight(data.Terrain[x, y], data.Terrain[x, y].Height + height, mountainModifer);
                }
            }
        }

        void ModHeight(PolyMeshData cell, float height, float mountainMod)
        {
            cell.Height = height;
            cell.BottomLeft = new Vector3(cell.BottomLeft.x, cell.Height, cell.BottomLeft.z);
            cell.BottomRight = new Vector3(cell.BottomRight.x, cell.Height, cell.BottomRight.z);
            cell.TopLeft = new Vector3(cell.TopLeft.x, cell.Height, cell.TopLeft.z);
            cell.TopRight = new Vector3(cell.TopRight.x, cell.Height, cell.TopRight.z);

        }
    }

    public class PolyData
    {
        public PolyMeshData[,] Terrain;
        public PolyMeshData[,] Road;
    }
}
