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



namespace Rokojori
{
  public class SplinesDeformerMappingData
  {
    public Vector3 localPosition;
    public Vector3 localNormal;
    public float normalizedSplineParameter;
    public float weight;
  }

  public class SplinesDeformModifier:MeshGeometryModifier
  {
    public SplineCurve[] sourceSplines = new SplineCurve[ 0 ];
    public SplineCurve[] targetSplines = new SplineCurve[ 0 ];

    public SplinesDeformerSettings settings;    

    public override MeshGeometry Modify( MeshGeometry mg )
    { 
      var mappings = CreateSourceMappings( mg );
      return CreateDeformed( mg, mappings ); 
    }

    SplinesDeformerMappingData CreateSourceMapping( SplineCurve curve, Vector3 worldPosition, Vector3 worldNormal )
    {
      var closestParameter = curve.GetClosestParameterTo( worldPosition, settings.splineMappingResolution, settings.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 SplinesDeformerMappingData();
      
      mappingData.localPosition = localPosition;
      mappingData.localNormal   = localNormal;
      mappingData.normalizedSplineParameter = closestParameter;
      mappingData.weight = 0;

      return mappingData;
    }

    SplinesDeformerMappingData[] CreateSourceMappings( MeshGeometry meshGeometry)
    {
      var mappingSize = meshGeometry.vertices.Count * sourceSplines.Length;
      
      var deformerMappings = new SplinesDeformerMappingData[ mappingSize];
       
      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 curve = sourceSplines[ j ];
          var mapping = CreateSourceMapping( curve, vertex, normal );
          
          var distance = curve.PositionAt( mapping.normalizedSplineParameter ) - vertex;
          var inverseWeight = MathX.NormalizeClamped( distance.Length(), settings.splineMinDistance, settings.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;
          }
        }
      }

      return deformerMappings;
    }

    MeshGeometry CreateDeformed( MeshGeometry meshGeometry,  SplinesDeformerMappingData[] mappingData )
    {
      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 < targetSplines.Length; j++ )
        {
          var mapping = mappingData[ i * targetSplines.Length + j  ];
          var curve = targetSplines[ j ];

          if ( settings.targetSmoothing > 0 )
          {            
            var pose = curve.SmoothedPoseAt( mapping.normalizedSplineParameter, settings.targetSmoothing * 0.5f, 2, settings.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();
      }

      return cloned;
    }
    
  }
}