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

namespace Rokojori
{ 

  [Tool]
  [GlobalClass,Icon("res://addons/rokojori_action_library/Icons/UI.svg")]
  public partial class UI : Control, IDisposable
  {     

    public static readonly CachedResource<Texture2D> whiteTexture = new CachedResource<Texture2D>(
      "res://addons/rokojori_action_library/Assets/Textures/white.svg"
    );

    public static readonly CachedResource<Texture2D> blackTexture = new CachedResource<Texture2D>(
      "res://addons/rokojori_action_library/Assets/Textures/black.svg"
    );

    [Export]
    public UISettings settings;

    [Export]
    public float fontZoom = 1;

    public enum UpdateMode
    {
      Always,
      Optimized,
      Only_Manual_Updates
    }
    [Export]
    public UpdateMode updateMode = UpdateMode.Always;

    [Export] 
    public bool useParentSize = false;

    [Export]
    public Vector2 uiSize = Vector2.Zero;
    
    [ExportGroup("Functions")]
    [ExportToolButton( "Mouse:Stop => Pass")]
    public Callable StopToPassButton => Callable.From( ()=>{ MakeStopToPass(); } );

    [Export]
    public Node[] stopToPassRoots = [];

    [ExportGroup("Read Only")]

    [Export]
    public float X_computedFontSizePixels = 1;   

    List<ICustomDisposer> _customDisposers = [];

    public void AddForDisposing( ICustomDisposer customDisposer )
    {
      _customDisposers.Add( customDisposer );
    }

    protected override void Dispose( bool disposing )
    {
      _customDisposers.ForEach( cd => cd.Dispose() );
      _customDisposers = [];
    }

    [Export]
    public string[] customDisposerIDs = [];
    

    public UINumber GetUINumber( UIStyleNumberProperty property )
    {
      if ( UIStyleNumberProperty.FontSize == property )
      {
        var uiNumber = new UINumber();
        uiNumber.value = X_computedFontSizePixels;
        uiNumber.unit = "px";

        // this.LogInfo( "Font Size:", uiNumber.value, uiNumber.unit );
        return uiNumber;
      }

      return null;
    }

    public void MakeStopToPass()
    {
      var roots = stopToPassRoots;

      if ( roots == null || roots.Length == 0 )
      {
        roots = [ this ];
      }

      foreach ( var n in roots )
      {
        if ( n is Control c )
        {
          if ( c.MouseFilter == MouseFilterEnum.Stop )
          {
            c.MouseFilter = MouseFilterEnum.Pass;
          }
        }

        n.ForEach<Control>( 
          ( c )=>
          {
            if ( c.MouseFilter == MouseFilterEnum.Stop )
            {
              c.MouseFilter = MouseFilterEnum.Pass;
            }
          }
        );
      }
    }

    Vector2 cachedSize;

    public static UI Get( Control c )
    {
      return UIHolder.GetUI( c );
    }

    

    public static UIStylePropertyContainer GetStylePropertyContainerParent( Control c )
    {
      var it = c as Node;
      var walker = NodesWalker.Get();

      it = walker.Parent( it );

      if ( it is UI )
      {
        return null;
      }

      while ( it != null )
      {
        if ( it is UIStylePropertyContainer )
        {
          return it as UIStylePropertyContainer;
        }

        it = walker.Parent( it );
        
        if ( it is UI )
        {
          it = null;
        }
      }

      return null;
    }


    public static TimeLine GetTimeLine( Control c, TimeLine timeLine )
    {
      if ( timeLine != null )
      {
        return timeLine;
      }

      var ui = Get( c );

      if ( ui == null || ui.settings == null )
      {
        return null;
      }

      return ui.settings.defaultTimeline;
    }

    Vector2 windowSize = new Vector2();

    bool _resizeFlag = false; 
    bool _fontSizeFlag = false;

    public bool sizeChanged => _resizeFlag;
    public bool fontSizeChanged => _fontSizeFlag;

    

    public override void _Process( double delta )
    {      
      if ( settings == null )
      {
        uiSize = Vector2.Zero;
        return;
      }
      
      _resizeFlag = false;
      _fontSizeFlag = false;

      var newWindowSize = GetWindowSize();
      
      if ( windowSize != newWindowSize )
      {
        _resizeFlag = true;
      }

      if ( useParentSize  )
      {
        var parentSize = cachedSize;

        if ( GetParent() as Control != null )
        {
          parentSize = GetParent<Control>().Size;
        }
        else 
        {
          parentSize = newWindowSize;
        }

        if ( parentSize != cachedSize )
        {
          Size = parentSize;
          cachedSize = parentSize;
          _resizeFlag = true;
        }
        
      }

      onProcess.DispatchEvent( (float)delta );
      UpdateFontSize();
      UpdateUIElements();

      uiSize = Size;

      // customDisposerIDs = _customDisposers.Map( c => c.GetUID() + ":" + c.GetInfo() ).ToArray();
      
    }

    public void UpdateExternal( float delta)
    {
      onProcess.DispatchEvent( delta );
      UpdateFontSize();
    }
    
    public readonly EventSlot<InputEvent> onInputEvent = new EventSlot<InputEvent>();
    public readonly EventSlot<float> onProcess = new EventSlot<float>();

    public override void _Input( InputEvent ie )
    {
      onInputEvent.DispatchEvent( ie );
    }   

    public void BindOwnChildren()
    {
      BindChildrenOf( this );
    }

    public void BindChildrenOf( Node node )
    {
      node.ForEach<UIImage>( img => img.SetUI( this ) ); 
      node.ForEach<UIInputInfo>( info => info.SetUI( this ) ); 
      node.ForEach<UIText>( text => text.SetUI( this ) );
      node.ForEach<UIRegion>( region => region.SetUI( this ) );     
    }

    public override void _Ready()
    {
      var sm = Unique<SensorManager>.Get();
      
      if ( sm != null )
      {
        sm._onActiveDeviceChange.AddAction( a => UpdateUIInputs() );
      }

      UpdateUIInputs();
    } 

    void UpdateUIInputs()
    { 
      Nodes.ForEach<UIInputInfo>( this, uii => uii.updateInfo = true );
    }



    void UpdateFontSize()
    {
      if ( settings == null )
      {
        return;
      }

      var oldFontSize = X_computedFontSizePixels;
      X_computedFontSizePixels = UINumber.Compute( this, settings.fontSize ) * fontZoom;   
      
      if ( oldFontSize != X_computedFontSizePixels )
      {
        _fontSizeFlag = true;
      }

      if ( Theme != null )
      {
        Theme.DefaultFontSize = Mathf.RoundToInt( X_computedFontSizePixels );
      }
    }

    HashSet<UIStylePropertyContainerNode> _dirty = new HashSet<UIStylePropertyContainerNode>();
    HashSet<UIStylePropertyContainerNode> _updated = new HashSet<UIStylePropertyContainerNode>();

    public void SetDirty( UIStylePropertyContainerNode control )
    {
      _dirty.Add( control );
    }

    public void SetUpdated( UIStylePropertyContainerNode control )
    {
      _updated.Add( control );
    }

    void UpdateUIElements()
    {
      if ( UpdateMode.Always == updateMode )
      {
        UpdateAllUIRegions();
      }
      else if ( UpdateMode.Optimized == updateMode )
      {
        if ( _fontSizeFlag || _resizeFlag )
        {
           _dirty.Clear();
          UpdateAllUIRegions();
         
        }
        else
        {
          UpdateUIElementsOptimized();
        }
        
      }
    }

    float time = 0;
    void UpdateUIElementsOptimized()
    {
      var timeNow = TimeLine.osTime;
      var elapsed = timeNow - time;
      time = timeNow;
      // this.LogInfo( Mathf.RoundToInt( elapsed * 1000f ) );

      var parent = GetParent();

      var sorted = new List<UIStylePropertyContainerNode>( [ .. _dirty ]);

      if ( sorted.Count > 0 )
      {
        // this.LogInfo( "Updating:", sorted.Count );
      }

      _updated.Clear();
  

      sorted.Sort(  
        ( a, b ) => 
        {
          return Mathf.RoundToInt( Mathf.Sign( a.GetUIAncestorDepth() - b.GetUIAncestorDepth() ) );
        }
      );

      

      sorted.ForEach(
        ( s )=>
        {
          if ( _updated.Contains( s ) )
          {
            return;
          }

          // ( s as Control).LogInfo( "Updating:", s );

          UpdateElement( s );
        }
      );

      _updated.Clear();

      _dirty.RemoveWhere( d => ! d.IsDirty() );
      
      foreach ( var d in _dirty )
      {
        d.ResetDirtyFlags();
      }
      

    }

    void UpdateElement( UIStylePropertyContainerNode control )
    {
      if ( control is UIRegion region )
      {
        region.Layout();
      }
      else 
      {
        UILayouting.UpdateChild( control as Control ); 

        if ( UIStyle.Position( control ) == UIPosition.From_Layout )
        {
          UILayouting.SetPositionInParentAnchor( control );
        }
        
      }
      
    }

    public void UpdateAllUIRegions()
    {
      Nodes.ForEachDirectChild<UIRegion>( this, r => r.Layout() );
    }

    public static float GetWindowWidth( Control control )
    {
      if ( Engine.IsEditorHint() )
      {
        return ProjectSettings.GetSetting( "display/window/size/viewport_width" ).AsInt32();
      }
      else
      {
        return control.GetWindow().Size.X;
      }
    }

    public static float GetWindowHeight( Control control )
    {
      if ( Engine.IsEditorHint() )
      {
        return ProjectSettings.GetSetting( "display/window/size/viewport_height" ).AsInt32();
      }
      else
      {
        return control.GetWindow().Size.Y;
      }
    }

    public Vector2 GetWindowSize()
    {
      if ( Engine.IsEditorHint() )
      {
        var width = ProjectSettings.GetSetting( "display/window/size/viewport_width" ).AsInt32();
        var height = ProjectSettings.GetSetting( "display/window/size/viewport_height" ).AsInt32();

        return new Vector2( width, height );
      }
      else
      {
        return GetWindow().Size;
      }
    }
    

  }
}