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

namespace Rokojori
{  
  public enum SplineAutoOrientationMode
  {
    Tangent,
    Next_Neighbor
  }

  [Tool]
  [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Spline.svg") ]
  public partial class Spline : Node3D
#if TOOLS 
 , GizmoDrawer
#endif

  { 

    public static Spline Create( Node parent, List<Vector3> points, bool closed = false )
    {
      var spline = parent.CreateChild<Spline>();

      spline.closed = closed;
      spline.autoOrienation = true;

      points.ForEach(
        ( p )=>
        {
          var sp = spline.CreateChild<SplinePoint>();
          sp.GlobalPosition = p;
        }
      );

      return spline;
    }

    [Export]
    public bool closed = false;

    [Export]
    public int editorResolution = 20;

    [Export]
    public bool autoOrienation = false;

    [Export]
    public SplineAutoOrientationMode autoOrientationMode = SplineAutoOrientationMode.Tangent;

    [Export( PropertyHint.Range, "-1,1")]
    public float autoOrientationTangentAdjustment = 0f;

    [Export]  
    public Vector3 autoOrientationUp = Vector3.Up;

    [Export]
    public bool updateAlways = false;

    [Export]
    public int xzPathBakeResolution = 50;


    public void SetEditorPointSize( float size )
    {
      #if TOOLS
      this.ForEachDirectChild<SplinePoint>( p => p.editorSplinePointSize = size );
      #endif
    }

    SplineCurve splineCurve;
    Vector3 min;
    Vector3 max;

    public Box3 GetBounds()
    {
      GetCurve();

      return Box3.Create( min, max );
    }


    List<SplinePoint> GetFilteredSplinePoints()
    {
      var unfilteredSplinePoints = Nodes.GetDirectChildren<SplinePoint>( this ); 

      if ( unfilteredSplinePoints.Count == 0 )     
      {
        return unfilteredSplinePoints;
      }


      var splinePoints = new List<SplinePoint>();
      var lastPoint = 0;

      splinePoints.Add( unfilteredSplinePoints[ 0 ] );

      for ( int i = 1; i < unfilteredSplinePoints.Count; i++ )
      {
        var last = unfilteredSplinePoints[ lastPoint ];
        var p = unfilteredSplinePoints[ i ];
        var distance = ( last.GlobalPosition - p.GlobalPosition ).Length();

        if ( distance > 0.0001f )
        {
          splinePoints.Add( p );
          lastPoint = i;
        }
      }

      // this.LogInfo( "Filtered Spline Points:", unfilteredSplinePoints.Count, ">>", splinePoints.Count );
      return splinePoints;
    }

    public void ResetCurve()
    {
      splineCurve = null;
    }

    public SplineCurve GetCurve()
    {
      if ( splineCurve == null )
      {
        var splinePoints = GetFilteredSplinePoints();
        splineCurve = new SplineCurveCreator().Create( splinePoints, closed );

        min = splineCurve.MinPointPosition();
        max = splineCurve.MaxPointPosition();
      }

      return splineCurve;

    }

    

    SplineCurve splineCurveXZ;

    public SplineCurve GetCurveXZ()
    {
      if ( splineCurveXZ == null )
      {
        var splinePoints = GetFilteredSplinePoints();
        splineCurveXZ = new SplineCurveCreator().Create( splinePoints, closed ).CloneForXZ( 0 );
      }

      return splineCurveXZ;

    }

    Path2 xzPath;
    Convex2Group convex2Group;

    public Path2 GetXZPath2()
    {
      if ( xzPath == null )
      {
        xzPath = Path2.AsXZFrom( GetCurve(), closed, xzPathBakeResolution );
      }

      return xzPath;
    }

    public Convex2Group GetConvex2Group()
    {
      if ( convex2Group == null )
      {
        convex2Group = Convex2Group.FromPath( GetXZPath2() );
      }

      return convex2Group;
    }

    public void ClearCurveCache()
    {
      splineCurve = null;
      splineCurveXZ = null;
      xzPath = null;
      convex2Group = null;
    }

#if TOOLS
    
    public void DrawGizmo( EditorNode3DGizmoPlugin gizmoPlugin, EditorNode3DGizmo gizmo )
    {
      ClearCurveCache();

      var curve = GetCurve();

      gizmo.Clear();

      if ( curve.points.Count <= 1 )
      {
        return;
      }
      

      

      var linePoints = new List<Vector3>();

      int resolution = editorResolution <= 0 ? 20 : editorResolution;

      renderedResolution = editorResolution;

      var lastPoint = ToLocal( curve.PositionAt( 0 ) );

      for ( int i = 1; i < resolution; i++ )
      {
        var t = i / (float) (resolution - 1 );
        var point = ToLocal( curve.PositionAt( t ) ); 

        linePoints.Add( lastPoint );
        linePoints.Add( point );

        lastPoint = point;
      }

      for ( int i = 0; i < curve.points.Count; i++ )
      {
        var p = curve.points[ i ];
        linePoints.Add( ToLocal( p.position ) );
        linePoints.Add( ToLocal( p.tangentBefore.position ) );

        linePoints.Add( ToLocal( p.position ) );
        linePoints.Add( ToLocal( p.tangentNext.position ) );
      }

      var material = gizmoPlugin.GetMaterial( "main", gizmo );

      gizmo.AddLines( linePoints.ToArray(), material, false );
    }

    List<Vector3> cachedPositions = new List<Vector3>();
    
    
    int renderedResolution = -100;

    

    public override void _Process( double delta )
    {
      var changed = updateAlways;
      var index = 0;

      if ( renderedResolution != editorResolution )
      {
        changed = true;
      }

      if ( autoOrienation )
      {
        AutoOrientate();        
      }

      Nodes.ForEachDirectChild<SplinePoint>( this, 
        sp => 
        {
          if ( changed )
          {
            return;
          }

          if ( index >= cachedPositions.Count )
          {
            changed = true;
            return;
          }

          if ( cachedPositions[ index ] != sp.GlobalPosition )
          {
            changed = true;
          }

          index ++;
        }
      );

      if ( ! changed )
      {
        return;
      }

      cachedPositions = Nodes.MapDirectChildren<SplinePoint,Vector3>( this, sp => sp.GlobalPosition );

      // RJLog.Log( "Updating gizmos" );
      UpdateGizmos();      
    }
#endif

    public void AutoOrientate()
    {
      var list = Nodes.GetDirectChildren<SplinePoint>( this );

      if ( list.Count <= 1 )
      {
        return;
      }
        
      var up = autoOrientationUp.Normalized();

      if ( SplineAutoOrientationMode.Next_Neighbor == autoOrientationMode )
      {
       

        for ( int i = 0; i < list.Count; i++ )
        {
          var point = list[ i ];

          if ( i == ( list.Count - 1 ) )
          {
            if ( closed )
            {
              var first = list[ 0 ];
              var firstDirection = ( first.GlobalPosition - point.GlobalPosition ).Normalized();
              point.LookTowards( firstDirection, up );
            }

            continue;
          }          

          var next = list[ i + 1 ];
          
          var direction = ( next.GlobalPosition - point.GlobalPosition ).Normalized();

          point.LookTowards( direction, up );
        }
      }
      else if ( SplineAutoOrientationMode.Tangent == autoOrientationMode )
      {

        for ( int i = 0; i < list.Count; i++ )
        {
          var point = list[ i ];     

          var tangentForward = Vector3.Zero;

          if ( i == ( list.Count - 1 ) && ! closed )
          {
            tangentForward = - SplineCurveCreator.GetTangentDirectionSmoothed( 1, autoOrientationTangentAdjustment, list, i, true, false );
          }
          else
          {
            tangentForward = SplineCurveCreator.GetTangentDirectionSmoothed( 1, autoOrientationTangentAdjustment, list, i, false, closed );
          }          
          
          if ( tangentForward.Length() == 0 )
          {
            continue;
          }

          point.LookTowards( tangentForward, up );
        }
      }

      
    }

  }
}