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

namespace Rokojori
{
  [Tool]
  [GlobalClass]
  public partial class LODParent:Node3D
  {
    public enum DistanceMode
    {
      Via_Array,
      Via_Curve
    }

    [Export]
    public DistanceMode mode;

    [ExportGroup("Array Mode")]
    [Export]
    public bool arrayCulling = true;
    [Export]
    public float[] arrayTresholds = new float[ 0 ];

    [ExportGroup("Curve Mode")]
    [Export]
    public bool curveCulling = true;

    [Export]
    public float curveCullDistance = 4000;

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

    [ExportGroup("LODs")]
    [Export]
    public Node3D[] lods = new Node3D[ 0 ];

    [Export]
    public float updateDistance = 10;

    [ExportGroup("Testing")]
    [Export]
    public float X_processingCameraDistance = 0;
    
    [Export]
    public float X_realCameraDistance = 0;

    [Export]
    public float X_selectedIndex = 0;

    [Export]
    public bool testMode = false;

    [Export]
    public float testDistance = 0;

    [Export]
    public bool processOnlyWhenVisible = true;

    float lastSquaredDistance = -1;
    int lastSelectedIndex = -1;

    public int changeBlock = 0;

    public int blocker = 0;

    public override void _Process( double delta )
    {
      if ( processOnlyWhenVisible && ! Visible )
      {
        return;
      }

      if ( blocker > 0 )
      {
        blocker --;
        return;
      }

      if ( lods == null || lods.Length == 0 )
      {
        return;
      }

      var camera = GetViewport().GetCamera3D();

#if TOOLS 

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

      var squaredDistance = GlobalPosition.DistanceSquaredTo( camera.GlobalPosition );
      X_realCameraDistance = Mathf.Sqrt( squaredDistance );


      if ( testMode )
      {
        squaredDistance = testDistance * testDistance;
      }

      if ( Mathf.Abs( lastSquaredDistance - squaredDistance ) < updateDistance * updateDistance )
      {        
        return;
      } 

      

      lastSquaredDistance = squaredDistance;

      var realDistance = Mathf.Sqrt( lastSquaredDistance );

      X_processingCameraDistance = realDistance;

      var selectedIndex = GetLODIndex( realDistance );

      X_selectedIndex = selectedIndex;

      if ( lastSelectedIndex == selectedIndex )
      {
        return;
      }

      lastSelectedIndex = selectedIndex;
      blocker = changeBlock;

      if ( selectedIndex >= lods.Length )
      {
        Arrays.ForEach( lods, l => NodeState.Set( l, false ) );
      }
      else
      {
        var selectedLOD = lods[ selectedIndex ];
        Arrays.ForEach( lods, l => NodeState.Set( l, selectedLOD == l ) );
      } 
    } 

    int GetLODIndex( float realDistance )
    {
      if ( DistanceMode.Via_Array == mode )
      {        
        for ( int i = 0; i < arrayTresholds.Length; i++ )
        {
          if ( realDistance <= arrayTresholds[ i ] )
          {
            return i;
          }          
        }

        return arrayCulling ? lods.Length : ( lods.Length - 1 );
      }
      else if ( DistanceMode.Via_Curve == mode )
      {
        if ( curveCulling && realDistance >= curveCullDistance )
        {
          return lods.Length;
        }

        var normalizedDistance = MathX.NormalizeClamped( realDistance, 0, curveCullDistance );
        var active = curveDistribution.Sample( normalizedDistance ) * lods.Length;
        return Mathf.Min( lods.Length - 1, Mathf.RoundToInt( active ) ); 
      }
      
      return -1;
    }

  }
}