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

namespace Rokojori
{  
  public enum TubeSegmentMode
  {
    Fixed_Division,
    Depending_On_Length,
    Maximum_Of_Both,
    Minimum_Of_Both
  }

  public enum ShapeOrientationMode
  {
    Auto,
    Original,
    Reverse
  }

  [Tool]
  [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Spline.svg") ]
  public partial class Tube : Node3D
  {
    [Export]
    public MeshInstance3D output;

    [Export]
    public Spline spline;

    [Export]
    public TubeSegmentMode segmentMode = TubeSegmentMode.Fixed_Division;

    [Export]
    public bool useFullUVQuads = false;

    [Export]
    public int fixedSplineSegmentDivisions = 20;

    [Export]
    public float splineSegmentLength = 2;


    [Export]
    public bool undistortSplineSegments = true;

    [Export]
    public Curve twistCurve;

    [Export]
    public float radius;

    [Export]
    public int radialSegments = 8;

    [Export]
    public Curve radiusSizeCurve;

    [Export]
    public Curve radiusWidthCurve;

    [Export]
    public Curve radiusHeightCurve;

    [Export]
    public bool scaleRadiusByPathTransforms = true;

    [Export]
    public TubeShape[] shapes = new TubeShape[ 0 ];

    [Export]
    public bool update;

    [Export]
    public bool updateAlways;


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

      var curve = GetCurve();

      gizmo.Clear();

      var linePoints = new List<Vector3>();

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

      renderedResolution = editorResolution;

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

      for ( int i = 1; i < resolution; i++ )
      {
        var t = i / (float) (resolution - 1 );
        var point = ToLocal( curve.SampleAt( 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 );

      */
    }

    public override void _Process( double delta )
    {
      if ( ! ( updateAlways || update ) )
      {
        return;
      }

      if ( spline == null )
      {
        return;
      }

      update = false;

      
      var mg = CreateMesh();
      output.Mesh = mg.GenerateMesh();

      UpdateGizmos();

    }
    
#endif

    MeshGeometry CreateMesh()
    {
      var curve = spline.GetCurve();
      int splineSegments = fixedSplineSegmentDivisions;

      if ( TubeSegmentMode.Fixed_Division != segmentMode )
      {
        splineSegments = Mathf.CeilToInt( curve.ComputeLength( curve.points.Count * 3 ) / splineSegmentLength );

        if ( TubeSegmentMode.Maximum_Of_Both == segmentMode )
        {
          splineSegments = Mathf.Max( splineSegments, fixedSplineSegmentDivisions );
        } 
        if ( TubeSegmentMode.Minimum_Of_Both == segmentMode )
        {
          splineSegments = Mathf.Min( splineSegments, fixedSplineSegmentDivisions );
        }
      }

      var shapesList = new List<TubeShape>();

      if ( shapes != null && shapes.Length > 0 )
      {
        shapesList.AddRange( shapes );
        Lists.Sort( shapesList, s => s.tubePosition );

        shapesList.ForEach( s => s.ClearCache() );
        
      }

      
      var mg = MeshGeometry.CreateFromUVFunction(
        ( Vector2 uv ) =>
        {
          var t = undistortSplineSegments ? curve.ComputeTforNormalizedCurveLength( uv.Y, splineSegments ) : uv.Y;
          var index = curve.NormalizedToPointIndex( t );
          var pose = curve.PoseAt( t );        
          var radiusSize = radius * ( radiusSizeCurve == null ? 1 : radiusSizeCurve.Sample( t ) );
          var sizeX = radiusSize;
          var sizeY = radiusSize;
          var twistOffset = 0f;

          if ( radiusWidthCurve != null )
          {
            sizeX *= radiusWidthCurve.Sample( t );
          }

          if ( radiusHeightCurve != null )
          {
            sizeY *= radiusHeightCurve.Sample( t );
          }

          if ( scaleRadiusByPathTransforms )
          {
            
            var splineScale = curve.SmoothStepScaleByPointIndex( index );

            if ( scaleRadiusByPathTransforms )
            {
              sizeX *= splineScale.X;
              sizeY *= splineScale.Y;
            }
          }

          if ( twistCurve != null )
          {
            twistOffset = Mathf.DegToRad( twistCurve.Sample( t ) );
          }

          twistOffset += Mathf.DegToRad( curve.SmoothStepTwistByPointIndex( index ) * 360 );

          var twistRotation = Math3D.RotateZ( twistOffset ).Normalized();

          var scale = new Vector3( sizeX, sizeY, 0 );

          if ( shapesList.Count > 0 )
          { 
            if ( shapesList.Count == 1 )
            {
              return shapesList[ 0 ].GetPose( undistortSplineSegments, splineSegments, radialSegments, pose, uv, scale, twistRotation );
            }

            var lerpResult = Lists.LerpIndex( shapesList, uv.Y, s => s.tubePosition );


            var closestShape = shapesList[ lerpResult.closestIndex ];
            var secondShape  = shapesList[ lerpResult.secondIndex ];

            var closestPose = closestShape.GetPose( undistortSplineSegments, splineSegments, radialSegments, pose, uv, scale, twistRotation );
            var secondPose  = secondShape.GetPose( undistortSplineSegments, splineSegments, radialSegments, pose, uv, scale, twistRotation );

            var smoothLerp = Mathf.SmoothStep( 0f, 1f, lerpResult.lerpAmount );
            return Pose.Lerp( closestPose, secondPose, smoothLerp );
            
          }          
          else 
          {
            var angle = uv.X * 2f * Mathf.Pi * ( sizeX / sizeY );
            var circlePose = Pose.Create( Vector3.Zero, pose.rotation * Math3D.RotateZ( angle ) );
            var circleStart = Vector3.Up * radiusSize * scale;

            var positionOnCircle = circlePose.Apply( circleStart );

            return Pose.Create( positionOnCircle + pose.position, circlePose.rotation );
          }

        }, 
        radialSegments, splineSegments, useFullUVQuads 
      );

      return mg;
    }

  }
}