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

namespace Rokojori
{
  [Tool]
  [GlobalClass]
  public partial class CharacterMovement:CharacterControllerAction
  {
    [Export]
    public float onFloorMultiply = 1f;

    [Export]
    public float inAirMultiply = 0.01f;

    [Export]
    public CameraTargetOffset cameraTargetOffset;

    [ExportGroup( "Direction Source" )]
    [Export]
    public Node3D directionSource;

    public enum DirectionProcessing
    {
      None,
      Zero_Y_And_Normalize,
      Project_On_TransformPlane
    }    

    [ExportGroup("Movement")]
    
    [Export]
    public float moveSpeed = 10f;

    [Export]
    public float minSpeed = 0f;


    [Export]
    public CharacterMovementType controllerMovementType;

    [Export]
    public CharacterMovementType mouseKeyboardMovementType;

    [Export]
    public CharacterMovementType currentMovementType;

    [Export]
    public CharacterMovementType[] deviceMovementTypes = [];

    [Export]
    public Smoothing onFloorMovementSmoothing = new FrameSmoothing();

    [Export]
    public Smoothing inAirMovementSmoothing = new FrameSmoothing();

    bool _onFloor = false;
    bool _moving = false;

    [Export]
    public float movingSpeedTreshold = 0.01f;

    [Export]
    public float currentSpeed = 0f;

    [Export]
    public Action onStartedMoving;

    [Export]
    public Action onStoppedMoving;

    public bool isOnFloor => _onFloor;
    public bool isMoving => _moving;


    [ExportGroup( "Rotation" )]

    [Export]
    public bool adjustRotation = true;

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

    [Export(PropertyHint.Range, "0,600")]
    public int airRotationSmoothingFrames = 120;

    FrameSmoothing rotationSmoothing = new FrameSmoothing();

    protected CharacterMovementData characterMovementData = new CharacterMovementData();

    
    [Export]
    public bool lookAlwaysForward = false;
    [Export]
    public Smoothing lookAlwaysForwardSmoothing;

    Vector3 _smoothedMovement;
    public Vector3 smoothedMovement => _smoothedMovement;

    int _frameID = 0;
    
    protected override void _OnTrigger()
    {
      var onFloor = body.IsOnFloor();
      _onFloor = onFloor;

      characterMovementData.Reset( this );

      var sm = Unique<SensorManager>.Get();
      if ( sm == null )
      {
        return;
      }
      
      var activeDevice = sm.lastActiveDevice;  

      currentMovementType = deviceMovementTypes.FindNonNull( d => d.deviceSelector.Selects( activeDevice ) );

      if ( currentMovementType == null )
      {
        // this.LogInfo( "No device found:", activeDevice == null ? "null" : activeDevice.GetType().Name );
        return;
      }

      currentMovementType.ProcessMovement( characterMovementData );

      if ( cameraTargetOffset != null )
      {
        cameraTargetOffset.SetMovingForward( characterMovementData.isMovingForward ? 1f : 0f );
      } 

      var movementMultiply = onFloor ? onFloorMultiply : inAirMultiply;

      characterMovementData.movement *= movementMultiply;
      

      if ( adjustRotation )
      {
        var nextRotation = Math3D.LookRotation( characterMovementData.forwardDirection );

        

        var speed = MathX.Clamp01( characterMovementData.movement.Length() / moveSpeed );

        if ( speed > 0 )
        {
          Quaternion rotation;

          if ( onFloor )
          {
            rotationSmoothing.frames = Mathf.Round( forwardToRotationSmoothingFrames.Sample( speed ) );                      
          }
          else
          {
            rotationSmoothing.frames = airRotationSmoothingFrames;
          }

          rotation = rotationSmoothing.Smooth( nextRotation, controller.delta );  
          body.SetGlobalQuaternion( rotation );
        }

       
      }

      if ( lookAlwaysForward )
      {
        var nextRotation = directionSource.GlobalYawQuaterntion();
        body.SetGlobalQuaternion( Smoothing.Apply( lookAlwaysForwardSmoothing, nextRotation, controller.delta ) );
      }

      // var smoothedMovement = Vector3.Zero;

      var movement = characterMovementData.movement;
      var delta = controller.delta;

      if ( onFloor )
      {
        _smoothedMovement = onFloorMovementSmoothing.Smooth( movement, delta );        
      }
      else
      {
        _smoothedMovement = inAirMovementSmoothing.Smooth( movement, delta ); 
      }

      var wasMoving = _moving;
      currentSpeed = _smoothedMovement.Length();

      // this.LogInfo( _frameID, currentMovementType.ResourceName, " >> speed:", currentSpeed._FF(), "movement:", movement, "delta:", delta._FFF() );
      _frameID ++;

      _moving = currentSpeed > movingSpeedTreshold;



      if ( wasMoving != _moving )
      {
        if ( _moving )
        {
          Trigger( onStartedMoving );
        }
        else
        {
          Trigger( onStoppedMoving );
        }
      }

      Velocity( _smoothedMovement, onFloor );
      
    }
  }

}