
using Godot;
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace Rokojori
{ 
  public class UIDragging
  {  
    public class MappedEvent
    {
      public MouseButton mouseButton;
      public EditableUIDraggingEvent uiEvent;
    }

    public class ButtonState
    {
      public MouseButton mouseButton;
      public bool dragging;
    }

    public class UIDraggingCallbacks: ICustomDisposer
    {
      public UI ui;
      public Control control;

      public bool wasDisposed = false;

      public bool notValid => 
        wasDisposed || 
        onMouseClick == null || 
        onDragging == null || 
        onProcess == null || 
        mouseEvents == null || mouseEvents.FilterNulls().Count == 0 || 
        mouseEventsDragging == null || mouseEventsDragging.FilterNulls().Count == 0;

      public Action<InputEvent> onMouseClick;
      public Callable callable;
      public Action<InputEvent> onDragging;
      public Action<float> onProcess;
      // public Dictionary<MouseButton,EditableUIDraggingEvent> mouseEvents = new Dictionary<MouseButton, EditableUIDraggingEvent>();

      public List<MappedEvent> mouseEvents = new List<MappedEvent>();
      public List<ButtonState> mouseEventsDragging = new List<ButtonState>();
      // public Dictionary<MouseButton,bool> mouseEventsDragging = new Dictionary<MouseButton, bool>();

      public string uid = IDGenerator.GenerateID();
      public string GetUID(){ return uid; }

      string info = "";
      public string GetInfo(){ return info;}

      public void Set( Control c, Func<UIDraggingEvent,bool> callback )
      {
        var callbacks = this;
        callbacks.control = c;
        callbacks.ui = c.FindParentThatIs<UI>();

        if ( callbacks.ui == null )
        {
          RJLog.Log( HierarchyName.Of( c ), ">> no UI" );
          return;
        }

        callbacks.ui.AddForDisposing( callbacks );

        info = HierarchyName.Of( c );
          

        callbacks.onProcess = (float delta )=>
        {
          callbacks.mouseEvents.ForEach(
            ( m )=>
            {
              callback( m.uiEvent );
            }
          );
          // foreach ( var m in callbacks.mouseEvents )
          // {
          //   callback( callbacks.mouseEvents[ m.Key ] );
          // }
        };

        callbacks.onDragging = ( InputEvent ie ) => 
        { 
          
          if ( ie is InputEventMouseButton mb && ! mb.Pressed )
          {
            var mdIndex = callbacks.mouseEventsDragging.FindIndex( me => me.mouseButton == mb.ButtonIndex );
            var meIndex = callbacks.mouseEvents.FindIndex( md => md.mouseButton == mb.ButtonIndex );
            callbacks.mouseEventsDragging[ mdIndex ].dragging = false;

            callbacks.mouseEvents[ meIndex ].uiEvent.UpdatePosition( mb.GlobalPosition, true );

            callback( callbacks.mouseEvents[ meIndex ].uiEvent );

            if ( ! callbacks.hasAnyMouseDragging )
            {
              callbacks.ui.onInputEvent.RemoveAction( callbacks.onDragging );
              callbacks.ui.onProcess.RemoveAction( callbacks.onProcess ); 
              callbacks.hasDraggingCallback = false;

              if ( c is UIStylePropertyContainer sc )
              {
                sc.GetUISelectorFlags().Remove( UISelectorFlag.Dragging );
              }
            }
          }

          if ( ie is InputEventMouseMotion mo )
          {
            var globalPosition = mo.GlobalPosition;

            // foreach ( var m in callbacks.mouseEvents )
            // {
            //   callbacks.mouseEvents[ m.Key ].UpdatePosition( globalPosition );
            //   // callback( callbacks.mouseEvents[ m.Key ] );
            // }

            callbacks.mouseEvents.ForEach(
            ( m )=>
            {
              m.uiEvent.UpdatePosition( globalPosition );
            }
          );
          }

          
        };

        callbacks.onMouseClick = ( InputEvent ie )=>
        {
          if ( ! ( ie is InputEventMouseButton b && b.Pressed ) )
          {
            return;
          }
          
          var mb = ie as InputEventMouseButton;

          var button = mb.ButtonIndex;
          
          var draggingEvent = callbacks.GetMouseEvent( button );

          draggingEvent.SetStart( mb.GlobalPosition );
          var result = callback( draggingEvent );

          if ( ! result )
          {
            return;
          }

          if ( c is UIStylePropertyContainer sc )
          {
            sc.GetUISelectorFlags().AddIfNotPresent( UISelectorFlag.Dragging );
          }

          var buttonIndex = callbacks.mouseEventsDragging.FindIndex( bs => bs.mouseButton == button );

          if ( buttonIndex == -1 )
          {
            var bs = new ButtonState();
            bs.mouseButton = button;
            callbacks.mouseEventsDragging.Add( bs );
            buttonIndex = callbacks.mouseEventsDragging.Count - 1;

          }

          callbacks.mouseEventsDragging[ buttonIndex ].dragging = true;

          if ( ! callbacks.hasDraggingCallback )
          { 
            callbacks.ui.onInputEvent.AddAction( callbacks.onDragging );
            callbacks.ui.onProcess.AddAction( callbacks.onProcess );
          }
          
        };


        callbacks.Connect();

        wasDisposed = false;

      }

      public void Dispose()
      {
        wasDisposed = true;

        if ( control != null && Node.IsInstanceValid( control ) )
        {
          control.Disconnect( "gui_input", callable );
        }

        

        // ui = null;
        // control = null;        

        
        onMouseClick = null;
        onDragging = null;
        onProcess = null;

        mouseEvents.Clear();
        
        mouseEventsDragging.Clear();
        
        // mouseEvents = null;
        // mouseEventsDragging = null;       

      }
      
      public bool hasAnyMouseDragging 
      {
        get 
        {
          foreach ( var mb in mouseEventsDragging )
          {
            if ( mb.dragging )
            {
              return true;
            }

          }

          return false;
        }
      }

      public bool hasDraggingCallback = false;

      public EditableUIDraggingEvent GetMouseEvent( MouseButton mb )
      {
        var index = mouseEvents.FindIndex( m => m.mouseButton == mb );

        if ( index == -1)
        {
          var mappedEvent = new MappedEvent();
          mappedEvent.mouseButton = mb;
          mappedEvent.uiEvent = new EditableUIDraggingEvent( mb );
          mouseEvents.Add( mappedEvent );
          index = mouseEvents.Count - 1;
        }

        mouseEvents[ index ].uiEvent.SetUI( ui );

        return mouseEvents[ index ].uiEvent;
      }

      public void Clear()
      {
        control.Disconnect( "gui_input", callable );
      }

      public void Connect()
      {
        callable = Callable.From( onMouseClick );
        control.Connect( "gui_input", callable );
      }
    }

    public class UIDraggingEvent
    {
      public enum Phase
      {
        Start,
        Dragging,
        End
      }

      protected Phase _phase = Phase.Start;

      public bool isStart => Phase.Start == _phase;
      public bool isDragging => Phase.Dragging == _phase;
      public bool isEnd => Phase.End == _phase;
      protected UI _ui;
      public UI ui => _ui;

      protected MouseButton _mouseButton;
      public MouseButton mouseButton => _mouseButton;
      public bool isLeftMouseButton => MouseButton.Left == _mouseButton;
      public bool isMiddleMouseButton => MouseButton.Middle == _mouseButton;
      public bool isRightMouseButton => MouseButton.Right == _mouseButton;
      public Vector2 customOffset;

      protected int _touchID = -1;

      protected Vector2 _startPosition;
      protected Vector2 _position;
      protected Vector2 _lastPosition;
      protected Vector2 _furthestPointFromStart;
      protected float _biggestDistance;

      public Vector2 startPosition => _startPosition;
      public Vector2 position => _position;
      public Vector2 lastPosition => _lastPosition;

      public Vector2 movementSinceLastFrame => _position - lastPosition;
      public Vector2 distanceToStart => _position - _startPosition;

      public Vector2 furthestPointToStart => _furthestPointFromStart;
      public float maximumMovement => _biggestDistance;

      public UIDraggingEvent( MouseButton mb )
      {
        _mouseButton = mb;
      }

      public UIDraggingEvent( int touchID )
      {
        this._touchID = touchID;
      }
      
    } 

    public class EditableUIDraggingEvent:UIDraggingEvent
    {

      public EditableUIDraggingEvent( MouseButton mb ):base( mb ){}
      public EditableUIDraggingEvent( int touchID ):base( touchID ){}

      public void SetUI( UI ui )
      {
        _ui = ui;
      }

      public void SetStart( Vector2 startPosition )
      {
        _startPosition = startPosition;
        _position = startPosition;
        _lastPosition = startPosition;
        _furthestPointFromStart = Vector2.Zero;
        _phase = Phase.Start;
      }

      public void UpdatePosition( Vector2 position, bool isEnd = false )
      {
        _lastPosition = _position;
        _position = position;

        var d = position.DistanceTo( _startPosition );

        if ( d > _biggestDistance )
        {
          _biggestDistance = d;
          _furthestPointFromStart = position;
        }   

        _phase = isEnd ? Phase.End : Phase.Dragging;
      }
    }

    public static UIDraggingCallbacks OnLeftMouseButton( Control c,  Action<UIDraggingEvent> callback )
    {
      return OnOneMouseButton( c, MouseButton.Left, callback );
    }

    public static UIDraggingCallbacks OnMiddleMouseButton( Control c,  Action<UIDraggingEvent> callback )
    {
      return OnOneMouseButton( c, MouseButton.Middle, callback );
    }

     public static UIDraggingCallbacks OnRightMouseButton( Control c,  Action<UIDraggingEvent> callback )
    {
      return OnOneMouseButton( c, MouseButton.Right, callback );
    }

    public static UIDraggingCallbacks OnOneMouseButton( Control c, MouseButton mouseButton, Action<UIDraggingEvent> callback )
    {
      var oneButtonCallback = ( UIDraggingEvent ev ) =>
      {        
        if ( ev.mouseButton != mouseButton )
        {
          return false;
        }

        callback( ev );

        return true;
      }; 

      return OnAnyMouseButton( c, oneButtonCallback );
    }

    public static UIDraggingCallbacks OnAnyMouseButton( Control c, Func<UIDraggingEvent,bool> callback )
    {
      
      var callbacks = new UIDraggingCallbacks();
      callbacks.Set( c, callback );

      // callbacks.control = c;
      // callbacks.ui = c.FindParentThatIs<UI>();

      // callbacks.ui.AddForDisposing( callbacks );
        

      // callbacks.onProcess = (float delta )=>
      // {
      //   foreach ( var m in callbacks.mouseEvents )
      //   {
      //     callback( callbacks.mouseEvents[ m.Key ] );
      //   }
      // };

      // callbacks.onDragging = ( InputEvent ie ) => 
      // { 
        
      //   if ( ie is InputEventMouseButton mb && ! mb.Pressed )
      //   {
      //     callbacks.mouseEventsDragging[ mb.ButtonIndex ] = false;

      //     callbacks.mouseEvents[ mb.ButtonIndex ].UpdatePosition( mb.GlobalPosition, true );

      //     callback( callbacks.mouseEvents[ mb.ButtonIndex ] );

      //     if ( ! callbacks.hasAnyMouseDragging )
      //     {
      //       callbacks.ui.onInputEvent.RemoveAction( callbacks.onDragging );
      //       callbacks.ui.onProcess.RemoveAction( callbacks.onProcess ); 
      //       callbacks.hasDraggingCallback = false;
      //     }
      //   }

      //   if ( ie is InputEventMouseMotion mo )
      //   {
      //     var globalPosition = mo.GlobalPosition;

      //     foreach ( var m in callbacks.mouseEvents )
      //     {
      //       callbacks.mouseEvents[ m.Key ].UpdatePosition( globalPosition );
      //       // callback( callbacks.mouseEvents[ m.Key ] );
      //     }
      //   }

        
      // };

      // callbacks.onMouseClick = ( InputEvent ie )=>
      // {
      //   if ( ! ( ie is InputEventMouseButton b && b.Pressed ) )
      //   {
      //     return;
      //   }
        
      //   var mb = ie as InputEventMouseButton;

      //   var button = mb.ButtonIndex;
        
      //   var draggingEvent = callbacks.GetMouseEvent( button );

      //   draggingEvent.SetStart( mb.GlobalPosition );
      //   var result = callback( draggingEvent );

      //   if ( ! result )
      //   {
      //     return;
      //   }

      //   callbacks.mouseEventsDragging[ button ] = true;

      //   if ( ! callbacks.hasDraggingCallback )
      //   { 
      //     callbacks.ui.onInputEvent.AddAction( callbacks.onDragging );
      //     callbacks.ui.onProcess.AddAction( callbacks.onProcess );
      //   }
        
      // };


      // callbacks.Connect();

      // c.Connect( "gui_input", Callable.From( callbacks.onMouseClick ) );
      // c.GuiInput += callbacks.onMouseClick;

      return callbacks;
    }
  }
}