using System.Collections;
using System.Collections.Generic;
using Godot;
using TriangleNet.Geometry;
using TriangleNet.Meshing;
using TriangleNet.Meshing.Algorithm;

namespace Rokojori
{
  using ClipperPath = List<ClipperLib.IntPoint>;
  using ClipperShape = List<List<ClipperLib.IntPoint>>;

  public enum ShapeFillRule
  {
    NonZero,
    EvenOdd    
  }

	public class Shape2
	{
    public List<Path2> paths = new List<Path2>();
    public ShapeFillRule fillRule = ShapeFillRule.NonZero;

    public Shape2( params Path2[] paths )
    {
      this.paths.AddRange( paths );
    }

    public Shape2( List<Path2> paths )
    {
      this.paths.AddRange( paths );
    }

    public Shape2 CleanUp( ShapeFillRule fillRule = ShapeFillRule.NonZero )
    {
      var clipperPath = ToClipperPaths( this );

      var clipper = new ClipperLib.Clipper();

      var fillType = _ConvertFillRule( fillRule );

      clipperPath = ClipperLib.Clipper.SimplifyPolygons( clipperPath, fillType );

      return FromClipperPaths( clipperPath );
    }


    Polygon _CreateTNetPolygon( bool simplify = true )
    {
      // RJLog.Log( "Creating polygon", paths.Count );

      if ( paths.Count == 0 )
      {
        RJLog.Log( "No paths" );
        return null;
      }

      var polyPaths = paths;

      if ( simplify && false )
      {
        var resultPaths = Lists.Map( polyPaths, p => Path2.ToClipperPath( p ) );
        
        resultPaths = ClipperLib.Clipper.SimplifyPolygons( resultPaths, ClipperLib.PolyFillType.pftEvenOdd );

        polyPaths = new List<Path2>();

        resultPaths.ForEach(
        ( r ) =>
          {
            polyPaths.Add( Path2.FromClipperPath( r ) );
          }
        );

      }

      // RJLog.Log( "Using paths", polyPaths.Count );

      var polygon = new Polygon();
      
  
      var index = 0;
      var firstIsClockWise = polyPaths.Count > 0 && polyPaths[ 0 ].isClockwise;

      polyPaths.ForEach(
        ( path )=>
        {
          var vertices = Lists.Map( path.points, p => new Vertex( p.X, p.Y ) );
          var center = path.center;
          var isClockwise = path.isClockwise;
          var isHole = firstIsClockWise != isClockwise;

          //  RJLog.Log( "Adding contour", center, vertices.Count, isHole ? "Hole" : "Fill" );


          polygon.Add( new Contour( vertices, index ), isHole );

          
          index ++;
        }
      );

			return polygon;
    }

    public static List<List<ClipperLib.IntPoint>> ToClipperPaths( Shape2 s )
    {
      var paths = new List<List<ClipperLib.IntPoint>>();

      s.paths.ForEach( p => paths.Add( Path2.ToClipperPath( p ) ) );

      return paths;
    }

    public static Shape2 FromClipperPaths( List<List<ClipperLib.IntPoint>> paths )
    {
      var shape = new Shape2();

      paths.ForEach( p => shape.paths.Add( Path2.FromClipperPath( p ) ) );

      return shape;
    }

    static ClipperLib.PolyFillType _ConvertFillRule( ShapeFillRule fillRule )
    {
      return ShapeFillRule.EvenOdd == fillRule ? ClipperLib.PolyFillType.pftEvenOdd : ClipperLib.PolyFillType.pftNonZero;
    }

    public string ToSVGPath()
    {
      var svgPaths = Lists.Map( paths, p => p.ToSVGPath() );
      return Lists.Join( svgPaths, " " );
    }
    
    public static Shape2 UnionAll( List<Path2> paths )
    { 
      paths.ForEach( p =>
       {
         if ( ! p.isClockwise )
         {
           p.Reverse();
         }

        //  RJLog.Log( pathIndex++, p.isClockwise ); 
       } 
      );

      var shape = new Shape2();
      shape.fillRule = ShapeFillRule.EvenOdd;
      // shape.paths = paths;
      // shape.CleanUp();

      
      shape.paths.Add( paths[ 0 ] );

      for ( int i = 1; i < paths.Count; i++ )
      {
        var otherShape = new Shape2();
        otherShape.fillRule = ShapeFillRule.EvenOdd;
        otherShape.paths.Add( paths[ i ] );
        var pathsBefore = shape.paths.Count;
        shape = Shape2.Boolean( shape, otherShape, Geometry2D.PolyBooleanOperation.Union );

        // RJLog.Log( "Union:", pathsBefore, shape.paths.Count );

        
      }

      return shape;
    }

    public static Shape2 Boolean( Shape2 a, Shape2 b, Geometry2D.PolyBooleanOperation booleanOperation, bool simplify = true )
    {
      // RJLog.Log( "Using Clipper Library" );

      var clipperPathsA = ToClipperPaths( a );
      var clipperPathsB = ToClipperPaths( b );

      var resultPaths = new List<List<ClipperLib.IntPoint>>();

      var type = ClipperLib.ClipType.ctUnion;

      if ( Geometry2D.PolyBooleanOperation.Difference == booleanOperation )
      {
        type = ClipperLib.ClipType.ctDifference;
      }
      else if ( Geometry2D.PolyBooleanOperation.Intersection == booleanOperation )
      {
        type = ClipperLib.ClipType.ctIntersection;
      }
      else if ( Geometry2D.PolyBooleanOperation.Xor == booleanOperation )
      {
        type = ClipperLib.ClipType.ctXor;
      }

      var subjectPolyType = _ConvertFillRule( a.fillRule );
      var clipPolyType    = _ConvertFillRule( b.fillRule );

      // RJLog.Log( "ShapeBool", "type: " + type, "boolOp: " + booleanOperation, "A|B >>",clipperPathsA.Count, clipperPathsB.Count );
			var clipper = new ClipperLib.Clipper();
			clipper.AddPaths( clipperPathsA, ClipperLib.PolyType.ptSubject, true); 
			clipper.AddPaths( clipperPathsB, ClipperLib.PolyType.ptClip, true); 
			clipper.Execute( type, resultPaths, subjectPolyType, clipPolyType );

      if ( simplify )
      {
        resultPaths = ClipperLib.Clipper.SimplifyPolygons( resultPaths );
      }
      

      var s = new Shape2();

      resultPaths.ForEach(
        ( r ) =>
        {
          s.paths.Add( Path2.FromClipperPath( r ) );
        }
      );

      return s;
    }

    public static Shape2 FromSVGPath( string pathData, float resolution )
    {
      var shape = new Shape2();
      var commands = SVGPathParser.Parse( pathData );

      if ( commands != null )
      { 
        /*          
        RJLog.Log( "Commands:", commands.Count );

        var commandsInfo = new List<string>();

        var cindex = 0;
        commands.ForEach(
          ( c )=>
          {
            commandsInfo.Add( "[" + cindex + "] " + c.type + ":\n" + RJLog.Stringify( c.paramaters ) );
            cindex ++;
          }
        );

        RJLog.Log( Lists.Join( commandsInfo, "\n" ) );
        
        */
        
        var extractor = new SVGPathExtractor();
        
        List<Vector2> pathPoints = null;

        var index = 0;
        extractor.onInstruction.AddAction(
          ( si )=>
          {
            var newPathIsStarting = SVGPathInstructionType.MoveTo == si.type || 
            (
              SVGPathInstructionType.MoveTo != si.type && pathPoints == null
            );

           
            if ( newPathIsStarting )
            {
              if ( pathPoints != null )
              {
                shape.paths.Add( new Path2( pathPoints ) );
              }

              pathPoints = new List<Vector2>();
            
            }

            var debugPoints = new List<Vector2>();

            si.AddPoints( debugPoints, resolution );

            var cm = si.sourceCommand;
            var parameterLength = SVGPathCommand.GetParameterLengthForCommand( cm.type );

            var cmInfo = "[" + cm.pathIndex + "]" + si.sourceCommand.type;

            if ( parameterLength > 0 )
            {
              cmInfo += " ";

              var offset = parameterLength * si.sourceCommandIndex;
              
              for ( int i = 0; i < parameterLength; i++ )
              {
                if ( i != 0 )
                {
                  cmInfo += ", ";
                }

                cmInfo += RegexUtility.NumberToString( cm.paramaters[ offset + i ] );
              }
            }

            // RJLog.Log( index, newPathIsStarting ? "+" : " ",  si.type, debugPoints, ">>", cmInfo );

            index++;

            si.AddPoints( pathPoints, resolution );
            
          }
        );

        extractor.Process( commands );

        if ( pathPoints != null )
        {
          shape.paths.Add( new Path2( pathPoints ) );
        }

        
      }
      else
      {
        RJLog.Log( "No commands found for:", pathData );
        var messages = SVGPathParser.GetMessages( pathData );
        RJLog.Log( messages );

      }

      // RJLog.Log( "Shape has", shape.paths.Count, "paths >> ", Lists.Map( shape.paths, p => p.points.Count + "" ) );


      return shape;
    }

    public MeshGeometry CreateFillMeshGeometry()
    {
      /*if ( paths.Count == 1 )
      {
        // RJLog.Log( "Only 1 path" );
        return paths[ 0 ].CreateMeshGeometry();
      }*/

      var polygon = _CreateTNetPolygon();

      if ( polygon == null )
      {
        RJLog.Log( "Could not create polygon" );
        return null;
      }

      var options = new ConstraintOptions();
    	var quality = new QualityOptions();
      var triangulator = new Dwyer(); 

			IMesh mesh = polygon.Triangulate( options, quality, triangulator );

			int vertexCount   = mesh.Vertices.Count;
			int triangleCount = mesh.Triangles.Count;

      var meshGeometry = new MeshGeometry();

      meshGeometry.vertices = new List<Vector3>( vertexCount );
      meshGeometry.uvs = new List<Vector2>( vertexCount );
      meshGeometry.indices = new List<int>( triangleCount * 3 );
      meshGeometry.normals = new List<Vector3>( vertexCount );

			foreach ( var v in mesh.Vertices )
			{
        var x = (float)v.x;
        var y = (float)v.y;

        meshGeometry.vertices.Add( new Vector3( x, 0, y ) );
        meshGeometry.uvs.Add( new Vector2( x, y ) );
        meshGeometry.normals.Add( Vector3.Up );
			}


			foreach ( var t in mesh.Triangles )
			{
        var vertices = t.vertices;

        var indicesList = new List<string>();
        

        for ( int i = 0; i < vertices.Length; i++ )
        {
          meshGeometry.indices.Add( t.GetVertexID( i ) );
        }
			}


      return meshGeometry;
    }
  }
}