using System.Collections;
using System.Collections.Generic;
using Godot;
using System;
using System.Linq;


using TriangleNet.Geometry;
using TriangleNet.Meshing;
using TriangleNet.Voronoi;
using TriangleNet.Topology.DCEL;

namespace Rokojori
{
  public class Voronoi2D
  { 
    public class Edge
    {
      public int start;
      public int end;
    }

    public List<Vector2> cellPoints;
    public List<Vector2> boundaryPoints = new List<Vector2>();
    public List<Cell> cells = new List<Cell>();
    public List<Edge> edges = new List<Edge>();

    public class Cell
    {
      public int index;
      public List<int> neighbors = new List<int>();
      public List<int> boundaries = new List<int>();

      protected Voronoi2D _voronoi2D;

      public Cell( Voronoi2D v, int index )
      {
        _voronoi2D = v;
        this.index = index;
      }

      public List<Vector2> GetBoundaryPositions()
      {
        return boundaries.Map( b => _voronoi2D.boundaryPoints[ b ] );        
      }

      public Vector2 center => _voronoi2D.cellPoints[ index ];

      public Box2 GetBounds()
      {
        return Box2.FromPoints( GetBoundaryPositions() );
      }

    }

    
    public static Voronoi2D CreateFromCellPoints( List<Vector2> cellPoints )
    {
      var polygon = new Polygon();
      cellPoints.ForEach( c => polygon.Add( new TriangleNet.Geometry.Vertex( c.X, c.Y ) ) );

      var v = new Voronoi2D();
      v.cellPoints = cellPoints;

      var mesh = (TriangleNet.Mesh) polygon.Triangulate();

      var voronoi = new StandardVoronoi( mesh );

      var boundaryMap = new Dictionary<int,Vector2>();
      var edgesMap = new Dictionary<string,Edge>();

      foreach ( var face in voronoi.Faces )
      {
        var site = face.generator;
        var cell = new Cell( v, site.ID );        

        var edge = face.edge;

        if ( edge == null )
        {
          continue;
        }

        var start = edge;
        
        
        AddEdge( cell, start, boundaryMap, edgesMap );

        
        var it = start.next;
        
        while ( it != start && it != null )
        {
          AddEdge( cell, it, boundaryMap, edgesMap );
          it = it.next;
        }

        v.cells.Add( cell );
      }

      for ( int i = 0; i < boundaryMap.Count; i++ )
      {
        if ( ! boundaryMap.ContainsKey( i ) )
        {
          RJLog.Log( "Invalid index for boundary", i );
          continue;
        }

        v.boundaryPoints.Add( boundaryMap[ i ] );  

      }

      foreach ( var edge in edgesMap )
      {
        v.edges.Add( edge.Value );
      }

      return v;
    } 

    static void AddEdge( Cell cell, HalfEdge halfEdge, Dictionary<int,Vector2> boundaryMap, Dictionary<string,Edge> edgeMap )
    {
      if ( halfEdge == null )
      {
        return;
      }

      if ( ! boundaryMap.ContainsKey( halfEdge.origin.id ) )
      {
        boundaryMap[ halfEdge.origin.id ] = new Vector2( (float) halfEdge.origin.x, (float) halfEdge.origin.y );
      }

      var end = halfEdge.next;

      if ( end != null )
      {
        int[] ids = [ halfEdge.origin.id, halfEdge.next.origin.id ];

        if ( ids[ 0 ] > ids[ 1 ] )
        {
          var b = ids[ 0 ];
          ids[ 0 ] = ids[ 1 ];
          ids[ 1 ] = b;
        }

        var edgeID = ids[ 0 ] + " " + ids[ 1 ];

        if ( ! edgeMap.ContainsKey( edgeID ) )
        {
          var edge = new Edge();
          edge.start = halfEdge.origin.id;
          edge.end = halfEdge.next.origin.id;
          edgeMap[ edgeID ] = edge;
        }

      }

      cell.neighbors.Add( halfEdge.Twin.id );

      cell.boundaries.Add( halfEdge.origin.id );
    }
  }
}