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

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

  {
    public class DeformerMappingData
    {
      public Vector3 localPosition;
      public Vector3 localNormal;
      public float normalizedSplineParameter;
      public float weight;
    }

    public enum WeightsMappingMethod
    {
      Max_Distance
    }

    [Export]
    public bool update = false;

    [Export]
    public bool updateAlways = false;

    [ExportGroup( "Input")]

    [Export]
    public Spline[] sourceSplines = new Spline[ 0 ];

    [Export]
    public MeshInstance3D sourceMesh;

    [Export]
    public bool updateSourceMesh;

    [ExportGroup( "Output")]
    [Export]
    public Spline[] deformerSplines = new Spline[ 0 ];

    [Export]
    public MeshInstance3D outputMesh;

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


    [ExportGroup( "Spline Settings")]

    [Export]
    public float splineMaxDistance = 1000f;

    [Export]
    public float splineMinDistance = 0f;

    [Export]
    public int splineMappingResolution = 40;

    [Export]
    public int splineMappingDepth = 3;




    DeformerMappingData[] deformerMappings = new DeformerMappingData[ 0 ];
    MeshGeometry meshGeometry;

    DeformerMappingData CreateSourceMapping( Spline s, Vector3 worldPosition, Vector3 worldNormal )
    {
      var curve = s.GetCurve();
      var closestParameter = curve.GetClosestParameterTo( worldPosition, splineMappingResolution, splineMappingDepth );
      var pointIndex = curve.NormalizedToPointIndex( closestParameter );
      var pose = curve.GetPoseByPointIndex( pointIndex );
      
      var localPosition = pose.ApplyInverse( worldPosition );
      var localNormal   = pose.rotation.Inverse() * worldNormal;

      var mappingData = new DeformerMappingData();
      
      mappingData.localPosition = localPosition;
      mappingData.localNormal   = localNormal;
      mappingData.normalizedSplineParameter = closestParameter;
      mappingData.weight = 0;

      return mappingData;
    }

    void CreateSourceMappings()
    {
      meshGeometry = MeshGeometry.From( sourceMesh  );

      var mappingSize = meshGeometry.vertices.Count * sourceSplines.Length;

      if ( deformerMappings == null || deformerMappings.Length != mappingSize )
      {
        deformerMappings = new DeformerMappingData[ mappingSize];
      }

      this.LogInfo( "Mappings:", deformerMappings.Length, meshGeometry );
       
      for ( int i = 0; i < meshGeometry.vertices.Count; i++ )
      {
        var weights = 0f;

        for ( int j = 0; j < sourceSplines.Length; j++ )
        {
          var vertex = meshGeometry.vertices[ i ];
          var normal = meshGeometry.normals[ i ];
          var mapping = CreateSourceMapping( sourceSplines[ j ], vertex, normal );
          var curve = sourceSplines[ j ].GetCurve();
          var distance = curve.PositionAt( mapping.normalizedSplineParameter ) - vertex;
          var inverseWeight = MathX.NormalizeClamped( distance.Length(), splineMinDistance, splineMaxDistance );
          mapping.weight = 1f - inverseWeight; 
          weights += mapping.weight;
          deformerMappings[ i * sourceSplines.Length + j  ] = mapping;
        }

        if ( weights > 0 && weights != 1f )
        {
          for ( int j = 0; j < sourceSplines.Length; j++ )
          {
            var mapping = deformerMappings[ i * sourceSplines.Length + j  ];
            mapping.weight /= weights;
          }
        }
      }
    }

    void CreateDeformed()
    {
      // RJLog.Log( "CreateDeformed" );

      var cloned = meshGeometry.Clone();

      for ( int i = 0; i < cloned.vertices.Count; i++ )
      {
        var vertex = Vector3.Zero;
        var normal = Vector3.Zero;

        for ( int j = 0; j < deformerSplines.Length; j++ )
        {
          var mapping = deformerMappings[ i * deformerSplines.Length + j  ];
          var curve = deformerSplines[ j ].GetCurve();

          if ( targetSmoothing > 0 )
          {            
            var pose = curve.SmoothedPoseAt( mapping.normalizedSplineParameter, targetSmoothing * 0.5f, 2, targetSmoothing ); 
            pose.ApplyTwist( curve.TwistAt( mapping.normalizedSplineParameter ) );
            vertex += pose.Apply( mapping.localPosition ) * mapping.weight;
            normal += pose.rotation * mapping.localNormal * mapping.weight;

          }
          else
          { 
            var pose = curve.PoseAt( mapping.normalizedSplineParameter );
            pose.ApplyTwist( curve.TwistAt( mapping.normalizedSplineParameter ) );
            vertex += pose.Apply( mapping.localPosition ) * mapping.weight;
            normal += pose.rotation * mapping.localNormal * mapping.weight;
          } 
          
        }

        cloned.vertices[ i ] = vertex;
        cloned.normals[ i ] = normal.Normalized();
      }

      // RJLog.Log( cloned.vertices.Count );
      outputMesh.Mesh = cloned.GenerateMesh();
    }

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

      gizmo.Clear();


    }
    
    public override void _Process( double delta )
    {
      if ( updateSourceMesh )
      {        
        updateSourceMesh = false;

        CreateSourceMappings();
      }

      if ( update || updateAlways )
      {
        update = false;
        UpdateMesh();
      }

      UpdateGizmos();
    }

    void UpdateMesh()
    {
      if ( meshGeometry == null )
      {
        CreateSourceMappings();
      }

      if ( meshGeometry == null )
      {
        return;
      }

      CreateDeformed();
    }

#endif
  }
}