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

namespace Rokojori
{
	public abstract class Curve2
	{
    float _gradientSamplingRange = Mathf.Pow( 10f, -8f );
    public abstract Vector2 SampleAt( float t );
    
    public virtual void SampleMultiple( int numSamples, List<Vector2> output )
    {
      SampleMultiple( new Range( 0, 1 ), numSamples, output );
    }
    
    public virtual void SampleMultiple( Range range, int numSamples, List<Vector2> output )
    {
      var diff = range.length / ( numSamples - 1 ) ;
      
      for ( var i = 0 ; i < numSamples; i++ )
      {
        var t = range.min + i * diff;
        output.Add( SampleAt( t ) );
      }
    }

    public virtual Path2 SamplePath( int numSamples )
    {
      return SamplePath( new Range( 0, 1 ), numSamples );
    }
    
    public virtual Path2 SamplePath( Range range, int numSamples )
    {
      var diff = range.length / ( numSamples - 1 ) ;
      var points = new List<Vector2>();
      
      for ( var i = 0 ; i < numSamples; i++ )
      {
        var t = range.min + i * diff;
        var p = SampleAt( t );
        points.Add( new Vector2( p.X, p.Y ) );
        
      }

      return new Path2( points );
    }

    public virtual Vector2 GradientAt( float t )
    {
      return GradientAt( t, _gradientSamplingRange );
    } 

    public virtual Vector2 GradientAt( float t, float samplingRange )
    {
      return SampleAt( t + samplingRange ) - SampleAt( t - samplingRange );
    } 

    public virtual float ComputeLength( int numSamples )
    {
      return ComputeSubLength( Range.Of_01 , numSamples );
    }    

    public virtual float ComputeSubLength( Range range, int numSamples )
    {
      var diff = range.length / ( numSamples - 1 ) ;
      var it = SampleAt( range.min );
      var length = 0f;

      for ( var i = 1 ; i < numSamples; i++ )
      {
        var t = range.min + i * diff;
        var next = SampleAt( t );
        length += ( next - it ).Length();
        it = next;
      }

      return length;
    }
    
    public virtual int ComputeNumSamplesFor( Range range,
    float minimumDistance, int minSamples = 2, 
    int numSamplesForLengthComputation = -1 )
    {
      if ( minSamples < 2 )
      { minSamples = 2; }
      
      if ( numSamplesForLengthComputation <= 0  )
      { numSamplesForLengthComputation = minSamples * 4; }

      float distance = ComputeSubLength( range, numSamplesForLengthComputation );
      float numFloatingCuts = distance / minimumDistance;
      int numSamples = Mathf.CeilToInt( numFloatingCuts );

      return Mathf.Max( minSamples, numSamples );
    }

    public Vector2 GetClosestPositionTo( Vector2 position, int numSegments = 20, int depth = 0 )
    {
      var parameter = GetClosestParameterTo( position, numSegments, depth );

      return SampleAt( parameter );
    }

    public float GetClosestParameterTo( Vector2 point, int numSegments = 20, int depth = 3 )
    {
      return GetClosestParameterTo( 0, 1, point, numSegments, depth, 0 );
    }

    public float GetDistanceTo( Vector2 point, int numSegments = 20, int depth = 3 )
    {
      var p = GetClosestPositionTo( point, numSegments, depth );
      return ( p - point ).Length();
    }

    protected float GetClosestParameterTo( float tStart, float tEnd, Vector2 position, int numSegments, int maxDepth, int currentDepth = 0 )
    {
      var tNormalizer = ( tEnd - tStart ) / (float) ( numSegments - 1 );

      var closestIndex = -1;      
      var closestDistance = float.MaxValue;
      var minT = 0f;
      var maxT = 1f;
      var startPoint = SampleAt( tStart );

      var line = new Line2();
      var lastT = tStart;

      for ( int i = 1; i < numSegments; i++ )
      {
        var t = tNormalizer * i + tStart;
        var nextCurvePoint = SampleAt( t );

        line.Set( startPoint, nextCurvePoint );
        
        var closestPoint = line.ClosestPointToPoint( position );

        var distance = ( position - closestPoint ).LengthSquared();

        if ( distance < closestDistance )
        {
          closestDistance = distance;
          closestIndex = i - 1;
          minT = lastT;
          maxT = t;
        }

        startPoint = nextCurvePoint;
        lastT = t;
      }

      if ( maxDepth != currentDepth )
      { 
        return GetClosestParameterTo( minT, maxT, position, numSegments, maxDepth, currentDepth + 1 );
      }

      var tNormalizer2 = ( maxT - minT ) / (float)(numSegments - 1 );
      
      closestDistance = float.MaxValue;
      var closestParameter = 0.5f;

      for ( int i = 0; i < numSegments; i++ )
      {
        var detailedT = i * tNormalizer2 + minT;
        var sampledPoint = SampleAt( detailedT );

        var distance = ( sampledPoint - position ).LengthSquared();

        if ( distance < closestDistance )
        {
          closestParameter = detailedT;
          closestDistance = distance;
        }
      }

      return closestParameter;
      
      
    }

        
  }

  public class CustomCurve2:Curve2
  {
    Func<float,Vector2> _customFunction;

    public CustomCurve2( Func<float,Vector2> customFunction )
    {
      _customFunction = customFunction;      
    }

    public override Vector2 SampleAt( float t )
    {
      return _customFunction( t );
    }
  }
}