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

namespace Rokojori
{
  [Tool]
  [GlobalClass]
  public partial class LODMultiMesh:MultiMeshInstance3D
  {
    [Export]
    public float minDistance = 0;

    [Export]
    public float cullDistance = 100;

    [Export]
    public Curve fillCurve = MathX.Curve( 0, 1 );

    float _distance = -10;

    [Export]
    public float distanceTreshold = 0.01f;

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

    [Export]
    public float downMax = 0.1f;

    [Export]
    Vector3[] cachedOrigins = null;

    [Export]
    Vector3[] cachedBasis = null;

    public void CacheTransforms()
    {
      var mm = Multimesh;
      cachedOrigins = new Vector3[ mm.InstanceCount ];
      cachedBasis = new Vector3[ mm.InstanceCount * 3 ];

      
      for ( int i = 0; i < mm.InstanceCount; i++ )
      {
        var trsf = mm.GetInstanceTransform( i );

        cachedBasis[ i * 3 + 0 ] = trsf.Basis.Column0;
        cachedBasis[ i * 3 + 1 ] = trsf.Basis.Column1;
        cachedBasis[ i * 3 + 2 ] = trsf.Basis.Column2;

        cachedOrigins[ i ] =( trsf.Origin );
      }
    }

    public override void _Process(double delta)
    {
      var camera = GetViewport().GetCamera3D();

#if TOOLS 

      if ( Engine.IsEditorHint() )
      {
        camera = EditorInterface.Singleton.GetEditorViewport3D().GetCamera3D();
      }
       
#endif



      var distance = ( camera.GlobalPosition - GlobalPosition ).Length();

      if ( Mathf.Abs( _distance - distance ) < distanceTreshold )
      {
        return;
      } 

      _distance = distance;

      var mapped = MathX.RemapClamped( distance, minDistance, cullDistance, 1, 0 );
    
      var mm = Multimesh;

      // this.LogInfo( "MM", (int) distance, ( (int) ( 100 * mapped ) ) + "%", (int)( fillCurve.Sample( mapped ) * mm.InstanceCount) + " instances" );
      

      if ( mm == null )
      {
        return;
      }

      if ( distance >= cullDistance )
      {        
        mm.VisibleInstanceCount = 0;
        return;
      }


      var count = Mathf.RoundToInt( fillCurve.Sample( mapped ) * mm.InstanceCount );

      count = Mathf.Clamp( count, 0, mm.InstanceCount );

      var fadeStart = count * ( 1 - scaleRange );

      for ( int i = 0; i < count; i++ )
      {
        var cbasis = new Basis( cachedBasis[ i * 3 + 0 ], cachedBasis[ i * 3 + 1 ], cachedBasis[ i * 3 + 2 ] );

        var trsf = new Transform3D( cbasis, cachedOrigins[ i ] );

        if ( i >= fadeStart )
        {
          var amount = MathX.RemapClamped( i, fadeStart, count, 0, 1 );
          var basis = trsf.Basis.Scaled( new Vector3( 1, Mathf.Max( ( 1f - amount ), 0.0001f ), 1 )); 
          
          var offsetTrsf = new Transform3D( basis, trsf.Origin + downMax * Vector3.Down);

          mm.SetInstanceTransform( i, offsetTrsf );
        }
        else
        {
          mm.SetInstanceTransform( i, trsf );
        }
        
      }

      mm.VisibleInstanceCount = count;
    }
  }
}