﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

namespace Voronoi
{

    public class Diagram {

        HashSet<Cell> cells;
        HashSet<Edge> edges;
        Dictionary<int, Vertex> vertices;

        List<Vector2> sites;
        
        Vector2 size;

        int numSites;

        int seed;

        public Diagram(Vector2 size, int numSites, int seed)
        {
            cells = new HashSet<Cell>();
            edges = new HashSet<Edge>();
            vertices = new Dictionary<int, Vertex>();

            sites = new List<Vector2>();

            this.size = size;
            this.numSites = numSites;
            this.seed = seed;     
                 
            
        }

        public void GenerateDiagram(float maxHeight)
        {
            Random.InitState(seed);

            List<uint> colors = new List<uint>();

            for (int i = 0; i < numSites; i++)
            {
                sites.Add(new Vector2(Random.Range(0, size.x), Random.Range(0, size.y)));
                colors.Add(0);
            }
            System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
            watch.Reset();
            watch.Start();

            Delaunay.Voronoi rawVoronoi = new Delaunay.Voronoi(sites, colors, new Rect(Vector2.zero, size));

            watch.Stop();
            Debug.Log("Finished generating raw diagram. Took: " + watch.Elapsed);

            watch.Reset();
            watch.Start();
            int edgeNum = 1;
            int edgeCount = rawVoronoi.Edges().Count;
            int vertIndex = 0;
            foreach (Delaunay.Edge e in rawVoronoi.Edges())
            {
                Vertex lVert = null;
                Vertex rVert = null;
                if(!e.visible)
                {
                    edgeNum++;
                    continue;
                }

                Vector2? rawlVertTest = e.clippedEnds[Delaunay.LR.Side.LEFT];
                if (rawlVertTest.HasValue)
                {
                    Vector2 rawlVert = rawlVertTest.Value;
                    lVert = null;
                    try
                    {
                        var pair = vertices.First(kvp => kvp.Value.Position == rawlVert);
                        lVert = pair.Value;
                    }
                    catch(System.InvalidOperationException)
                    {
                    }
                    if (lVert == null)
                    {
                        float elevation = Mathf.PerlinNoise(rawlVert.x, rawlVert.y) * maxHeight;
                        lVert = new Vertex(rawlVert, vertIndex, elevation);
                        vertices[vertIndex++] = lVert;
                    }
                }
                
                Vector2? rawrVertTest = e.clippedEnds[Delaunay.LR.Side.RIGHT];
                if (rawrVertTest.HasValue)
                {
                    Vector2 rawrVert = rawrVertTest.Value;
                    rVert = null;
                    try
                    {
                        var pair = vertices.First(kvp => kvp.Value.Position == rawrVert);
                        rVert = pair.Value;
                    }
                    catch(System.InvalidOperationException)
                    {
                    }
                    if (rVert == null)
                    {
                        float elevation = Mathf.PerlinNoise(rawrVert.x, rawrVert.y) * maxHeight;
                        rVert = new Vertex(rawrVert, vertIndex, elevation);
                        vertices[vertIndex++] = rVert;
                    }
                }

                Delaunay.Site lSite = e.leftSite;
                Cell lCell = null;
                if (cells.Count > 0)
                {
                    lCell = cells.FirstOrDefault(c => c.SitePosition == lSite.Coord);
                }
                if (lCell == null)
                {
                    lCell = new Cell(lSite.Coord);
                    cells.Add(lCell);
                }

                Delaunay.Site rSite = e.rightSite;
                Cell rCell = null;
                if (cells.Count > 0)
                {
                    rCell = cells.FirstOrDefault(c => c.SitePosition == rSite.Coord);
                }
                if (rCell == null)
                {
                    rCell = new Cell(rSite.Coord);
                    cells.Add(rCell);
                }

                Edge edge = new Edge(lVert, rVert, lCell, rCell);
                if(lVert != null)
                    lVert.AddEdge(edge);
                if(rVert != null)
                    rVert.AddEdge(edge);
                lCell.AddEdge(edge);
                rCell.AddEdge(edge);

                edges.Add(edge);
                

            }
            watch.Stop();
            Debug.Log("Finished converting format. Took: " + watch.Elapsed);
            rawVoronoi.Dispose();
        }

        public Mesh GenerateMesh()
        {
            System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
            watch.Reset();
            watch.Start();
            if(vertices.Count == 0)
            {
                return null;
            }
            Mesh mesh = new Mesh();
            List<Vector3> verts = new List<Vector3>();
            List<int> vertIndexes = new List<int>();
            foreach(var pair in vertices.OrderBy(kvp => kvp.Key))
            {
                Vertex vert = pair.Value;
                verts.Add(vert.V3Position);
            }
            foreach(Cell c in cells)
            {
                int cellIndex = verts.Count;
                verts.Add(c.CenterPoint);
                vertIndexes.AddRange(c.GetVertexIndexBuffer(cellIndex));
            }
            mesh.SetVertices(verts);
            mesh.SetTriangles(vertIndexes, 0);
            watch.Stop();
            Debug.Log("Mesh generation took " + watch.Elapsed);
            return mesh;
        }

    }
}
