using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class RoadMeshGeneration : MonoBehaviour
{
    [SerializeField] private float roadWidth;

    [Tooltip("Decreases with increase smoothness")]
    [SerializeField] private int turnSmoothFector;

    private List<Road> roads = new List<Road>();
    private List<Vector3> edgeDirections = new List<Vector3>();

    // Data for mesh creation
    private List<Vector3> vertices;

    // Data for intersection mesh creation
    private List<RoadNode> intersections;
    private List<Vector3> intersectingRoadPoints;
    CombineInstance[] interCornerCombineInstance;

    public List<Vector3> Vertices
    {
        get
        {
            return vertices;        
        }    
    }

    private List<int> triangles;

    [SerializeField] private float thresoldRadius = 0.2f;
    private float turnRadius;

    private bool firstTime = true;
 
    void Start()
    {
        turnRadius = roadWidth*0.5f + thresoldRadius;
    }

    public void GenerateMesh() 
    {
        RoadDraw roadDraw = GetComponent<RoadDraw>();

        roads = roadDraw.GetListOfRoads();
        edgeDirections = GetEdgeDirections(roads[0].Nodes);
        intersections = roadDraw.GetListOfIntersections();
        intersectingRoadPoints = roadDraw.GetListOfIntersectingRoadPoints();

        vertices = new List<Vector3>();
        triangles = new List<int>();

        Vector3 leftOffset = new Vector3(-edgeDirections[0].z, 0, edgeDirections[0].x);
        leftOffset = leftOffset.normalized * roadWidth * 0.5f;

        vertices.Add(roads[0].Nodes[0].Position + leftOffset);
        vertices.Add(roads[0].Nodes[0].Position - leftOffset);
  
        triangles.Add(0); triangles.Add(2); triangles.Add(1);

        for (int i = 0; i < roads[0].Nodes.Count - 2; i++)
        {
            GetMeshDataForEachTurn(i);
        }

        Vector3 lastLeftOffset = new Vector3(-edgeDirections[edgeDirections.Count - 1].z, 0,
                                              edgeDirections[edgeDirections.Count - 1].x);
        lastLeftOffset = lastLeftOffset.normalized * roadWidth * 0.5f;

        vertices.Add(roads[0].Nodes[roads[0].Nodes.Count - 1].Position + lastLeftOffset);
        vertices.Add(roads[0].Nodes[roads[0].Nodes.Count - 1].Position - lastLeftOffset);

        triangles.Add(vertices.Count - 1);
        triangles.Add(vertices.Count - 2);
        triangles.Add(vertices.Count - 3);

        Mesh mesh = new Mesh();
        GetComponent<MeshFilter>().mesh = mesh;

        mesh.vertices = vertices.ToArray();
        mesh.triangles = triangles.ToArray();

        interCornerCombineInstance = new CombineInstance[intersections.Count*4];
        
        for(int i = 0, j = 0; i < intersections.Count; i++, j+=4)
        {
            interCornerCombineInstance[i].mesh = GetInterCornerMesh(i, j, j + 2);
            interCornerCombineInstance[i + 1].mesh = GetInterCornerMesh(i, j, j + 3);
            interCornerCombineInstance[i + 2].mesh = GetInterCornerMesh(i, j + 1, j + 2);
            interCornerCombineInstance[i + 3].mesh = GetInterCornerMesh(i, j + 1, j + 3);
        }

        // mesh.CombineMeshes(interCornerCombineInstance);
    }

    private Mesh GetInterCornerMesh(int indexI, int indexJ, int indexK)
    {
        
        Vector3 edgeDirection1 = intersectingRoadPoints[indexJ] - intersections[indexI].Position; 
        Vector3 edgeDirection2 = intersectingRoadPoints[indexK] - intersections[indexI].Position;

        float angle = Vector3.Angle(edgeDirection1, edgeDirection2);

        bool isAntiClockWise = Vector3.Cross
            (edgeDirection1, edgeDirection2).y > 0 ? true : false;
     
        Vector3 bisectVector;

        if (isAntiClockWise)
            bisectVector = Quaternion.Euler(0, angle / 2, 0) * (new Vector3(edgeDirection1.x, 0, edgeDirection1.z));
        else 
            bisectVector = Quaternion.Euler(0, -angle/2, 0) * (new Vector3(edgeDirection1.x, 0, edgeDirection1.z));

        // Vector from node to the center of the circle
        Vector3 centerVec = bisectVector;

        centerVec = centerVec.normalized * (turnRadius / (Mathf.Sin(angle * 0.5f * Mathf.Deg2Rad)));

        // Center of the circle
        Vector3 centerPos = intersections[indexI].Position + centerVec;

        Vector3 centerToOffsetVec;

        // Rotating centerVec to find offset point
        if (isAntiClockWise)
            centerToOffsetVec = Quaternion.Euler(0, 90 - angle * 0.5f, 0) * -centerVec;
        else
            centerToOffsetVec = Quaternion.Euler(0, angle * 0.5f - 90, 0) * -centerVec;

        centerToOffsetVec = centerToOffsetVec.normalized * turnRadius;
        Vector3 offsetPoint = centerPos + centerToOffsetVec;
        Vector3 firstRightOffset = centerToOffsetVec.normalized * roadWidth * 0.5f;

        List<Vector3> verts = new List<Vector3>();
        List<int> tris = new List<int>();

        verts.Add(intersections[indexI].Position);
   
        // Finding first vertice with help of offsetPoints     
        verts.Add(offsetPoint - firstRightOffset);
        int changeInAngle = turnSmoothFector;
        int sumAngle = changeInAngle;

        for (int i = 1;  sumAngle < 180 - angle; i++)
        {
            if (isAntiClockWise)
            {
                if ((180 - angle) - sumAngle > changeInAngle)
                    centerToOffsetVec = Quaternion.Euler(0, -changeInAngle, 0) * centerToOffsetVec;
                else
                    centerToOffsetVec = Quaternion.Euler(0, -((180 - angle) - sumAngle), 0) * centerToOffsetVec;

                tris.Add(0); tris.Add(i); tris.Add(i + 1);
            }
            else
            {
                if ((180 - angle) - sumAngle > changeInAngle)
                    centerToOffsetVec = Quaternion.Euler(0, changeInAngle, 0) * centerToOffsetVec;
                else
                    centerToOffsetVec = Quaternion.Euler(0, (180 - angle) - sumAngle, 0) * centerToOffsetVec;

                tris.Add(0); tris.Add(i + 1); tris.Add(i);
            }

            Vector3 rightOffset = centerToOffsetVec.normalized * roadWidth * 0.5f;         
            verts.Add((centerPos + centerToOffsetVec) - rightOffset);

           
            sumAngle += changeInAngle;
           
        }

        Mesh mesh = new Mesh();
        mesh.vertices = verts.ToArray();
        mesh.triangles = tris.ToArray();

        GameObject cornerMesh = new GameObject("Mesh", typeof(MeshFilter), typeof(MeshRenderer));
        cornerMesh.GetComponent<MeshFilter>().mesh = mesh;
        return mesh;
    }

    private void GetMeshDataForEachTurn(int index)
    {
        float angle = Vector3.Angle(-edgeDirections[index], edgeDirections[index + 1]);

        bool isAntiClockWise = Vector3.Cross
            (-edgeDirections[index], edgeDirections[index + 1]).y > 0 ? true : false;

        // Vector from node to the center of the circle
        Vector3 centerVec = GetAngleBisector(angle, isAntiClockWise, index);
        centerVec = centerVec.normalized * (turnRadius / (Mathf.Sin(angle * 0.5f * Mathf.Deg2Rad)));

        // Center of the circle
        Vector3 centerPos = roads[0].Nodes[index + 1].Position + centerVec;

        Vector3 centerToOffsetVec;

        // Rotating centerVec to find offset point
        if (isAntiClockWise)
            centerToOffsetVec = Quaternion.Euler(0, 90 - angle * 0.5f, 0) * -centerVec;
        else
            centerToOffsetVec = Quaternion.Euler(0, angle * 0.5f - 90, 0) * -centerVec;

        centerToOffsetVec = centerToOffsetVec.normalized * turnRadius;
        
        Vector3 offsetPoint = centerPos + centerToOffsetVec;
        Vector3 firstRightOffset = centerToOffsetVec.normalized * roadWidth * 0.5f;
       
        GetVerticesAndTriangles(angle, isAntiClockWise, centerPos, centerToOffsetVec, offsetPoint, firstRightOffset);
    }

    private Vector3 GetAngleBisector(float angle, bool isAntiClockWise, int index)
    {
        Vector3 bisectVector;
        if (isAntiClockWise)
          bisectVector = Quaternion.Euler(0, angle/2, 0)*(new Vector3( -edgeDirections[index].x, 0, -edgeDirections[index].z));
        else
          bisectVector = Quaternion.Euler(0, -angle/2, 0)*(new Vector3( -edgeDirections[index].x, 0, -edgeDirections[index].z));

        return bisectVector;
    }

    private void GetVerticesAndTriangles(float angle, bool isAntiClockWise, Vector3 centerPos, Vector3 centerToOffsetVec,
        Vector3 offsetPoint, Vector3 firstRightOffset)
    {

        int vCount = 0;

        if (firstTime)
        {
            vCount = 2;
            firstTime = false;
        }
        else
            vCount = vertices.Count;

        // Finding first two vertices with help of offsetPoints
        if (isAntiClockWise)
        {
            vertices.Add(offsetPoint - firstRightOffset);
            vertices.Add(offsetPoint + firstRightOffset);
        }
        else
        {
            vertices.Add(offsetPoint + firstRightOffset);
            vertices.Add(offsetPoint - firstRightOffset);
        }

        int changeInAngle = turnSmoothFector;
        int sumAngle = changeInAngle;

        while (sumAngle < 180 - angle)
        {

            if (isAntiClockWise)
            {
                if ((180 - angle) - sumAngle > changeInAngle)
                    centerToOffsetVec = Quaternion.Euler(0, -changeInAngle, 0) * centerToOffsetVec;
                else
                    centerToOffsetVec = Quaternion.Euler(0, -((180 - angle) - sumAngle), 0) * centerToOffsetVec;
            }
            else
            {
                if ((180 - angle) - sumAngle > changeInAngle)
                    centerToOffsetVec = Quaternion.Euler(0, changeInAngle, 0) * centerToOffsetVec;
                else
                    centerToOffsetVec = Quaternion.Euler(0, (180 - angle) - sumAngle, 0) * centerToOffsetVec;
            }

            Vector3 rightOffset = centerToOffsetVec.normalized * roadWidth * 0.5f;

            if (isAntiClockWise)
            {
                vertices.Add((centerPos + centerToOffsetVec) - rightOffset);
                vertices.Add((centerPos + centerToOffsetVec) + rightOffset);
            }
            else
            {
                vertices.Add((centerPos + centerToOffsetVec) + rightOffset);
                vertices.Add((centerPos + centerToOffsetVec) - rightOffset);
            }

            sumAngle += changeInAngle;
        }

        for (int j = triangles.Count; j <= (vertices.Count - 2) * 3; j += 6)
        {
            triangles.Add(vCount);
            triangles.Add(vCount + 1);
            triangles.Add(vCount - 1);
            triangles.Add(vCount);
            triangles.Add(vCount + 2);
            triangles.Add(vCount + 1);

            vCount += 2;
        }

    }

    private List<Vector3> GetEdgeDirections(List<RoadNode> nodes)
    {
        List<Vector3> edgeDirections = new List<Vector3>();

        for (int i = 0; i < nodes.Count - 1; i++)
        {
            edgeDirections.Add(nodes[i + 1].Position - nodes[i].Position);
        }

        return edgeDirections;
    }

}