using Godot;
using Rokojori;
 
namespace Rokojori
{ 

  [Tool]
  [GlobalClass] 
  public partial class UINumber : Resource
  { 
    [Export]
    public float value = 0;

    [Export]
    public string unit = "";
    

    [ExportGroup("Animation")]

    [Export]
    public bool isAnimated;

    [Export]
    public Curve animationCurve;
    
    [Export]
    public float animationDuration;

    [Export]
    public float animationOffset;

    [Export]
    public TimeLine timeLine;

    public bool IsNone =>  unit == "none";


    public UINumber GetMultiplied( float scale )
    {
      var uiNumber = new UINumber();
      uiNumber.CopyFrom( this );
      uiNumber.value = value * scale;

      return uiNumber;
    }

    public static UINumber Create( float value, string unit = "" )
    {
      var uiNumber = new UINumber();
      uiNumber.unit = unit;
      uiNumber.value = value;
      return uiNumber;

    }

    public static UINumber EM( float em )
    {
      return Create( em, "em" );
    }

    public static UINumber PW( float pw )
    {
      return Create( pw, "pw" );
    }

    public static UINumber PH( float ph )
    {
      return Create( ph, "ph" );
    }

    public float ComputeRaw( Control control, float alternative )
    {
      return UINumber.Compute( control, this, alternative );
    }
    

    public static bool IsNullOrNone( UIStylePropertyContainer container, UIStyleNumberProperty property )
    {
      return IsNullOrNone( UIStyle.GetUINumberProperty( container, property ) );
    }

    public static bool IsNullOrNone( UINumber number )
    {
      if ( number == null )
      {
        return true;
      }

      return number.IsNone;
    }

    public static int ComputeInt( Control control, UINumber number, float alternative = 0, float relative = 100 )
    {
      return Mathf.RoundToInt( Compute( control, number, alternative, relative ) );
    }

    public static int ComputeInt( Control control, UIStyleNumberProperty property, float alternative = 0, float relative = 100 )
    {
      return Mathf.RoundToInt( Compute( control, property, alternative, relative ) );
    }

    public bool Equals( UINumber other )
    {
      if ( other == this )
      { 
        return true; 
      }

      if ( other == null )
      { 
        return false; 
      }

      if ( other.value != value )
      { 
        return false; 
      }

      if ( other.unit != unit )
      { 
        return false; 
      }

      if ( other.isAnimated != isAnimated )
      { 
        return false; 
      }

      if ( other.animationCurve != animationCurve )
      { 
        return false; 
      }

      if ( other.animationDuration != animationDuration )
      { 
        return false; 
      }

      if ( other.animationOffset != animationOffset )
      { 
        return false; 
      }

      if ( other.timeLine != timeLine )
      {
        return false; 
      }
      
      return true;

    }

    public void CopyFrom( UINumber other )
    {
      if ( other == this )
      { 
        return; 
      }

      value = other.value;
      unit = other.unit;
      isAnimated = other.isAnimated;
      animationCurve = other.animationCurve;
      animationDuration = other.animationDuration;
      animationOffset = other.animationOffset;
      timeLine = other.timeLine;      
    }

    public static float Compute( Control control, UIStyleNumberProperty property, float alternative = 0, float relative = 100 )
    {
      var container = control as UIStylePropertyContainer;
      var transition = UIStyle.GetTransition( container, property );

      var number = UIStyle.GetUINumberProperty( control as UIStylePropertyContainer, property );
      var computedValue = Compute( control, number, alternative, relative );

      var allSettings = UIStyle.GetTransitionSettingsAll( container );

      var usesTransition = transition != null || allSettings != null && allSettings.transitionAllProperties;

      if ( ! usesTransition )
      {
        return computedValue; 
      } 

      var activeNumberTransitions = container.GetActiveUINumberTransitions();

      var propertyTransition = activeNumberTransitions.Find( t => t != null && t.propertyType == property );

      if ( propertyTransition == null )
      {
        propertyTransition = new ActiveStyleTransition<UINumber, UIStyleNumberProperty>();
        propertyTransition.propertyType = property;
        propertyTransition.value = number;         
        propertyTransition.transitioning = false;

        activeNumberTransitions.Add( propertyTransition );

        return computedValue;
      } 
      else
      {
        if ( propertyTransition.value != number && ! propertyTransition.transitioning && UIStyle.GetTransitionSettings( container, property ) != null )
        {
          var transitionSettings = UIStyle.GetTransitionSettings( container, property );
          propertyTransition.timeLine = transitionSettings.timeLine;       
          propertyTransition.start = transitionSettings.timeLine.position;
          propertyTransition.end = propertyTransition.start + transitionSettings.duration;
          propertyTransition.transitioning = true;          
          propertyTransition.curve = transitionSettings.curve;
        }
      }

      if ( propertyTransition.value == number )
      {
        propertyTransition.transitioning = false;
        return computedValue;
      }

      var computedTransitionValue = Compute( control, propertyTransition.value, alternative, relative );

      var transitionPhase = propertyTransition.timeLine.ComputeRange( propertyTransition.start, propertyTransition.end );
     
      if ( transitionPhase >= 1 )
      {
        activeNumberTransitions.Remove( propertyTransition );
      }

      var amount = MathX.Clamp01( transitionPhase );
      var curveAmount = amount;

      if ( propertyTransition.curve != null )
      {
        curveAmount = propertyTransition.curve.Sample( curveAmount );
      }

      return Mathf.Lerp( computedTransitionValue, computedValue, curveAmount );
    }


    public static float Compute( Control control, UINumber number, float alternative = 0, float relative = 100 )
    {
      if ( number == null || control == null || number.IsNone )
      {
        return alternative;
      }

      var width  = UI.GetWindowWidth( control );
      var height = UI.GetWindowHeight( control );


      return Compute( control, number, width, height, relative );
      
    }

    static UI _ui;

    public static float em()
    {
      return _ui == null ? 12 : _ui.X_computedFontSizePixels;
    }

    public static float Compute( Control control, UINumber number, float width, float height, float relative )
    {
      var value = ComputeWithoutAnimation( control, number, width, height, relative );

      if ( ! number.isAnimated || number.animationCurve == null )
      {
        return value;
      }

      var phase = number.timeLine.ComputePhase( number.animationDuration, number.animationOffset );

      return number.animationCurve.Sample( phase ) * value;

    }

    static readonly string[] _variables = new string[]
    { 
      "em",

      "vw", "vh", 
      "pw", "ph", 
      
      "cw","ch",
      "cx","cy",

      "relative", 
      "value" 
    };

    static float ComputeWithoutAnimation( Control control, UINumber number, float width, float height, float relative )
    {
      if ( number == null )
      {
        return 0;
      }

      if ( _ui == null )
      {
        _ui = NodesWalker.Get().GetInParents( control, n => n is UI ) as UI;
      }

      switch ( number.unit )
      {
        case "em":
        {
          return number.value * em();
        }

        case "vw":
        {
          return number.value * width / 100f;
        }

        case "vh":
        {
          return number.value * height / 100f;
        }

        case "pw":
        {
          var parent = control.GetParent<Control>();
          return number.value / 100f * ( parent == null ?  width : parent.Size.X );
        }

        case "cw":
        {
          var parent = control.GetParent<Control>();
          return number.value / 100f * ( parent == null ?  width : UILayouting.GetContentSize( parent ).X );
        }

         case "ch":
        {
          var parent = control.GetParent<Control>();
          return number.value / 100f * ( parent == null ?  height : UILayouting.GetContentSize( parent ).Y );
        }

        case "cx":
        {
          var parent = control.GetParent<Control>();
          return number.value / 100f * ( parent == null ?  0 : UILayouting.GetContentOffset( parent ).X );
        }

         case "cy":
        {
          var parent = control.GetParent<Control>();
          return number.value / 100f * ( parent == null ?  0 : UILayouting.GetContentOffset( parent ).Y );
        }

        case "ph":
        {
          var parent = control.GetParent<Control>();
          return number.value / 100f * ( parent == null ?  height : parent.Size.Y );
        }

        case "":
        {
          return number.value;
        }

        case "%":
        {
          return number.value * relative / 100f;
        }
      }

      var expression = new Expression();

      var expressionText = number.unit == null ? "" : RegexUtility.Replace( number.unit, "%", " * relative " ); 

      if ( ! IsValid( expressionText ) )
      {
        return 0;
      }

      var parseResult = expression.Parse( expressionText, _variables );

      if ( Error.Ok != parseResult )
      {
        return 0;
      }

      var parentControl = control.GetParent<Control>();

      var inputs = new Godot.Collections.Array();
      inputs.Add( em() );

      // vw, vh
      inputs.Add( width / 100f ); inputs.Add( height / 100f );

      // pw, ph
      inputs.Add( ( parentControl == null ? width : parentControl.Size.X ) / 100f );
      inputs.Add( ( parentControl == null ? height : parentControl.Size.Y ) / 100f );

      // cw, ch
      inputs.Add( ( parentControl == null ?  width : UILayouting.GetContentSize( parentControl ).X ) / 100f );
      inputs.Add( ( parentControl == null ?  width : UILayouting.GetContentSize( parentControl ).Y ) / 100f );

       // cx, cy
      inputs.Add( ( parentControl == null ?  0 : UILayouting.GetContentOffset( parentControl ).X ) / 100f );
      inputs.Add( ( parentControl == null ?  0 : UILayouting.GetContentOffset( parentControl ).Y ) / 100f );

      // "relative"
      inputs.Add( relative / 100f );

      // value
      if ( number.unit.IndexOf( "value" ) == -1 )
      {  
        inputs.Add( 0 );
        return number.value * (float)  expression.Execute( inputs ).AsDouble() ;
      }

      inputs.Add( number.value );

      return (float) expression.Execute( inputs ).AsDouble() ;
    }

    static bool IsValid( string expressionText )
    {
      if ( expressionText == null || expressionText == "" )
      {
        return false;
      }

      var lexer = new CSharpLexer();

      var list = lexer.LexToList( expressionText );

      if ( lexer.hasError )
      {
        return false;
      }

      lexer.GrabMatches( list, expressionText );

      for ( int i = 0; i < list.Count; i++ )
      {
        var token = list[ i ];

        if ( token.Is( LexerMatcherLibrary.CwordMatcher ) )
        {
          var value = token.match;

          if ( ! Arrays.Contains( _variables, value ) )
          {
            // RJLog.Log( "Unknown variable:", "'" + value + "'", " in: ", "'" + expressionText + "'" );
            return false;
          }
        }
      }


      return true;
    }
  }

}