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


namespace Rokojori
{  
  [Tool]
  [GlobalClass]
  public partial class StrategyTopDownCamera:VirtualCamera3D
  {
    public enum ConstrainMode
    {
      Unconstrained,
      Axis_Aligned_Box,
      Circle_XZ_Range_Y
    }

    [Export]
    public Vector3 target;
    
    [Export]
    public float yaw = 0;

    [Export]
    public float pitch = 0; 

    [Export]
    public float distance = 10; 
    float smoothDistance = 10;

    [ExportCategory("Move")]
    
    [Export]
    public ConstrainMode constrainMode = ConstrainMode.Unconstrained;

    [Export]
    public Node3D constrainMin;

    [Export]
    public Node3D constrainMax;

    

    [Export]
    public Sensor moveUpButton;
    [Export]
    public Sensor moveDownButton;
    [Export]
    public Sensor moveRightButton;
    [Export]
    public Sensor moveLeftButton;

    [Export]
    public float buttonMoveSpeed = 1f;


    [Export]
    public Sensor mouseMoveButton;

    [Export]
    public float mouseMoveSpeed = 0.001f;

    [Export]
    public bool flipHorizontal = false;

    [Export]
    public bool flipVertical = false;

    [Export]
    public bool moveAtBorders = true;

    [Export]
    public float moveAtBorderSpeed = 0.001f;

    [Export]
    public float borderSizePercentage = 5;

    [ExportCategory("Orbit")]

    [Export]
    public Sensor orbitRightButton;

    [Export]
    public Sensor orbitLeftButton;

    [Export]
    public Sensor orbitMouseButton;

    [Export]
    public float mouseOrbitSpeedScale = -0.5f;
  
    [Export]
    public float orbitSpeed = 0.001f;


    [ExportCategory("Zoom")]

    [Export]
    public float zoomStepInPercentage = 10;
  
    [Export]
    public float minDistance = 0.001f;

    [Export]
    public float maxDistance = 200f;

    [Export]
    public Sensor zoomInButton;

    [Export]
    public Sensor[] zoomInModifierButtons = new Sensor[ 0 ];

    [Export]
    public Sensor zoomOutButton;

    [Export]
    public Sensor[] zoomOutModifierButtons = new Sensor[ 0 ];

    [Export]
    public float zoomSmoothingCoefficient = 0.1f;
    Smoother smoother = new Smoother();

    public override void _Process( double delta )
    {
      Move();
      Orbit( (float) delta );
      Zoom();

      Apply( (float) delta );

      if ( ! hasMotionDelta )
      {
        motionDelta.X = 0;
        motionDelta.Y = 0;
      }

      hasMotionDelta = false;   
    }

    bool hasMotionDelta = false;
    Vector2 motionDelta = Vector2.Zero;

    float borderMoveHorizontal = 0;
    float borderMoveVertical = 0;
        
    float GetBorderSize()
    {
      var size = GetViewport().GetVisibleRect().Size;

      var borderX = ( size.X * borderSizePercentage ) / 100f;
      var borderY = ( size.Y * borderSizePercentage ) / 100f; 

      return Mathf.Min( borderX, borderY );
    }

    public override void _Input( InputEvent inputEvent )
    {     
      if ( inputEvent is InputEventMouseMotion )
      {
        var eventMouseMotion = inputEvent as InputEventMouseMotion;
        motionDelta = eventMouseMotion.ScreenRelative;

        if ( moveAtBorders )
        {
          var position = eventMouseMotion.Position;

          var screenSize = GetViewport().GetVisibleRect().Size;
          var borderSize = GetBorderSize();

          borderMoveHorizontal = - MathX.PolarAxis( position.X < borderSize, position.X > screenSize.X - borderSize ); 
          borderMoveVertical   = - MathX.PolarAxis( position.Y < borderSize, position.Y > screenSize.Y - borderSize ); 
        }
       
        hasMotionDelta = true;
      }
      
    }


    void Move()
    {
      var deltaX = 0f;
      var deltaY = 0f;

      if ( Sensors.IsActive( mouseMoveButton ) )
      {
        deltaX = motionDelta.X * mouseMoveSpeed;
        deltaY = motionDelta.Y * mouseMoveSpeed;
      }
      else
      {
        deltaX = Sensors.PolarAxis( moveLeftButton, moveRightButton ) + borderMoveHorizontal * moveAtBorderSpeed;
        deltaY = Sensors.PolarAxis( moveUpButton, moveDownButton ) + borderMoveVertical * moveAtBorderSpeed; 
      }

      var forward = Math3D.GetYPlaneForward( this );
      var right   = Math3D.GetYPlaneRight( this );

      var flipH = flipHorizontal ? -1 : 1;
      var flipV = flipVertical   ? -1 : 1;

      var xAmount = deltaX * smoothDistance * right * flipH;
      var zAmount = deltaY * smoothDistance * forward * flipV;

      target += ( xAmount + zAmount );

      ConstrainTarget();

    } 

    void ConstrainTarget()
    {
      if ( ConstrainMode.Unconstrained == constrainMode )
      {
        return;
      }

      if ( ConstrainMode.Axis_Aligned_Box == constrainMode )
      {
        target = Box3.Constrain( target, constrainMin.GlobalPosition, constrainMax.GlobalPosition );
        return;
      }

      if ( ConstrainMode.Circle_XZ_Range_Y == constrainMode )
      {
        var direction   = ( constrainMax.GlobalPosition - constrainMin.GlobalPosition );
        var directionXZ = direction; directionXZ.Y = 0;
        var distanceXZ  = directionXZ.Length();

        var targetDirection   = target - constrainMin.GlobalPosition;
        var targetDirectionXZ = targetDirection; targetDirectionXZ.Y = 0;

        target.Y = Mathf.Clamp( target.Y, constrainMin.GlobalPosition.Y, constrainMax.GlobalPosition.Y );

        if ( targetDirectionXZ.Length() > distanceXZ )
        {
          targetDirectionXZ = targetDirectionXZ.Normalized() * distanceXZ;
          var constrainedPosition = constrainMin.GlobalPosition + targetDirectionXZ;
          target = new Vector3( constrainedPosition.X, target.Y, constrainedPosition.Z );
        }  

      } 

    }

    void Orbit( float delta )
    {
      var direction = Sensors.PolarAxis( orbitLeftButton, orbitRightButton );

      if ( Sensors.IsActive( orbitMouseButton ) )
      {
        yaw += motionDelta.X * mouseOrbitSpeedScale;
      }

      yaw += direction * orbitSpeed * delta;
    }

    void Zoom()
    {
      var zoomButtonAxis = Sensors.PolarAxis( zoomOutButton, zoomInButton );

      distance *= Mathf.Pow( 1 + zoomStepInPercentage / 100f, zoomButtonAxis );

      distance = Mathf.Clamp( distance, minDistance, maxDistance );
    }


    void Apply( float delta )
    {
      smoothDistance = smoother.SmoothWithCoefficient( smoothDistance, distance, zoomSmoothingCoefficient, delta );
      GlobalRotation = new Vector3( Mathf.DegToRad( pitch ), Mathf.DegToRad( yaw ), 0 );

      var forward = Math3D.GetGlobalForward( this ) * smoothDistance;
      GlobalPosition = target + forward;
    }
  }
}