
using Godot;
using Rokojori;
using System.Collections.Generic;
using System.Linq;

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, "", container ) );
    }

    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 ComputeInt( control, property, "", alternative, relative );
    }

    public static int ComputeInt( Control control, UIStyleNumberProperty property, string shaderPropertyName = "", float alternative = 0, float relative = 100 )
    {
      return Mathf.RoundToInt( Compute( control, property, shaderPropertyName, 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 )
    {
      return Compute( control, property, "", alternative, relative );
    }

    

    public static float Compute( Control control, UIStyleNumberProperty property, string shaderPropertyName = "", float alternative = 0, float relative = 100 )
    {
      var container = control as UIStylePropertyContainerNode;
      
      // Get selected ui number
      var uiNumber = UIStyle.GetUINumberProperty( container, property, shaderPropertyName, container );
      var computedNumber = Compute( control, uiNumber, alternative, relative );

      if ( uiNumber != null && uiNumber.isAnimated )
      {
        container.SetAnimatedFlag();
      }

      var transition = UIStyle.GetTransition( container, property, shaderPropertyName );
      var transitionAll = UIStyle.GetTransitionSettingsAll( container );

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

      if ( ! usesTransition )
      {
        return computedNumber; 
      } 

      var activeNumberTransitions = container.GetActiveUINumberTransitions();

      var propertyTransition = activeNumberTransitions.Find( t => t != null && t.propertyType.Matches( property, shaderPropertyName ) );
      propertyTransition = EnsureTransitionObject( propertyTransition, control, uiNumber, property, shaderPropertyName );    

      var endValueChanged = uiNumber != propertyTransition.value;

      if ( endValueChanged )
      {
        var transitionSettings = UIStyle.GetTransitionSettings( container, property, shaderPropertyName );
        propertyTransition.StartTransition( control, uiNumber, transitionSettings );
      }

      if ( ! propertyTransition.transitioning )
      {
        return computedNumber;
      }

      var computedTransitionValue = 0f;
      
      for ( int i = 0; i < propertyTransition.lastValues.Count; i++ )
      {
        var lastValue = Compute( control, propertyTransition.lastValues[ i ], 0f ); 
        computedTransitionValue += lastValue * propertyTransition.lastWeights[ i ];
      }

      var phase = propertyTransition.GetUnclampedTransitionPhase();
      var lerpWeight = propertyTransition.ComputeTransitionWeight( phase );

      if ( phase >= 1f )
      {
        propertyTransition.EndTransition();
      }

      var result = Mathf.Lerp( computedTransitionValue, computedNumber, lerpWeight );

      return result;

      // 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 = UI.GetTimeLine( control, transitionSettings.timeLine );       
      //     propertyTransition.start = propertyTransition.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 );
    }

    static ActiveStyleTransition<UINumber, UIStyleNumberPropertyAndName> EnsureTransitionObject( 
      ActiveStyleTransition<UINumber, UIStyleNumberPropertyAndName> propertyTransition, 
      Control control, UINumber uiNumber, UIStyleNumberProperty property, string shaderPropertyName )
    {

      if ( propertyTransition != null )
      {
        return propertyTransition;
      }

      var container = control as UIStylePropertyContainer;
      propertyTransition = new ActiveStyleTransition<UINumber, UIStyleNumberPropertyAndName>();
      propertyTransition.propertyType = UIStyleNumberPropertyAndName.Create( property, shaderPropertyName );
              
      var transitionSettings = UIStyle.GetTransitionSettings( container, property, shaderPropertyName );
      propertyTransition.value = uiNumber; 
      propertyTransition.timeLine = UI.GetTimeLine( control, transitionSettings == null ? null : transitionSettings.timeLine );        
      propertyTransition.transitioning = false;

      container.GetActiveUINumberTransitions().Add( propertyTransition );

      return propertyTransition;
    }


    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 );
      
    }


    public static float em( Control control )
    {
      var ui = UIHolder.GetUI( control );
      return ui == null ? 12 : ui.X_computedFontSizePixels;
    }

    public static float uiw( Control control )
    {
      var ui = UIHolder.GetUI( control );
      return ui == null ? 1920 : ui.Size.X;
    }

    public static float uih( Control control )
    {
      var ui = UIHolder.GetUI( control );
      return ui == null ? 1080 : ui.Size.Y;
    }


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

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

      var timeLine = UI.GetTimeLine( control, number.timeLine );
      var phase = 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",

      "uiw", "uih",

      "relative", 
      "value" 
    };

    static MapList<string,float[]> cached = new MapList<string, float[]>();

    class CachedExpressionData
    {
      public string formula;
      public Godot.Collections.Array inputs;
      public float result;

      public bool Matches( UINumber number, Godot.Collections.Array inputs )
      {
        if ( number.unit != formula )
        {
          return false;
        } 

        for ( int i = 0; i < inputs.Count; i++ )
        {
          if ( (float)inputs.ElementAt( i )  != (float)this.inputs.ElementAt( i ) )
          {
            return false;
          }
        }

        return true;
      }
    }

    static List<CachedExpressionData> cachedExpressions = new List<CachedExpressionData>();
    static int maxCachedExpressions = 50;

    static int GetCacheIndex( UINumber number, Godot.Collections.Array inputs )
    {
      return cachedExpressions.FindIndex( c => c.Matches( number, inputs ) ); 
    }

    static float ComputeBaseValue( Control control, UINumber number, float width, float height, float relative, string numberUnit = null )
    {
      if ( number == null && numberUnit == null )
      {
        return 1;
      }

      var unit = numberUnit != null ? numberUnit : number.unit;
      
      switch ( unit )
      {
        case "em":
        {
          return em( control );
        }        

        case "vw":
        {
          return width / 100f;
        }

        case "vh":
        {
          return height / 100f;
        }

        case "pw":
        {
          var parent = control.GetParent() as Control;
          var rawValue = parent == null ?  width : parent.Size.X;
          var margins = 
            UINumber.Compute( parent, UIStyleNumberProperty.Margin, 0 ) * 2 + 
            UINumber.Compute( parent, UIStyleNumberProperty.MarginLeft, 0 ) + 
            UINumber.Compute( parent, UIStyleNumberProperty.MarginRight, 0 );

          return ( rawValue - margins ) / 100f;

        }

        case "ph":
        {
          var parent = control.GetParent() as Control;
          var rawValue = parent == null ?  height : parent.Size.Y;
          var margins = 
            UINumber.Compute( parent, UIStyleNumberProperty.Margin, 0 ) * 2 + 
            UINumber.Compute( parent, UIStyleNumberProperty.MarginTop, 0 ) + 
            UINumber.Compute( parent, UIStyleNumberProperty.MarginBottom, 0 );

          return ( rawValue - margins ) / 100f;
        }

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

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

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

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

        case "uiw":
        {
          return uiw( control ) / 100f;
        }

        case "uih":
        {
          return uih( control ) / 100f;
        }

        case "%":case "relative":
        {
          return relative / 100f;
        }

        case "px":
        {
          return 1;
        }

        case "value":
        {
          return number.value;
        }

       
      }

      return 1f;
    }

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

    
      if ( number.unit.Length <= 3 && ( number.unit == "%" || number.unit == "px"  || _variables.Contains( number.unit ) ) )
      {
        return number.value * ComputeBaseValue( control, number, width, height, relative );
      }

      var parentControl = control.GetParent() as Control;;

      var inputs = GetInputs( control, number, parentControl, width, height, relative );

      var cacheIndex = GetCacheIndex( number, inputs );

      if ( cacheIndex != -1 )
      {
        var cachedResult = cachedExpressions[ cacheIndex ].result;
        cachedExpressions.SwapTowardsBegin( cacheIndex );

        // control.LogInfo( "Using cached value", cachedExpressions[ cacheIndex ].formula, ">", cachedResult );
        return cachedResult;
      }

      var expression = new Expression();

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

      expressionText = RegexUtility.Replace( expressionText, @"\b(\d+)\s*(em|v[wh]|p[wh]|c[wh]|c[xy]|ui[wh])\b", "$1*$2" );
            

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

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

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

      
      var scale = number.unit.IndexOf( "value" ) == -1 ? number.value : 1;

      var result = (float) ( scale * expression.Execute( inputs ).AsDouble() );

      Cache( number.unit, inputs, result );


      return result;
    }

    static void Cache( string formula, Godot.Collections.Array inputs, float result )
    {
      CachedExpressionData data = null;

      if ( cachedExpressions.Count < maxCachedExpressions )
      {
        data = new CachedExpressionData();
        cachedExpressions.Add( data );
      }
      else
      {
        data = GodotRandom.Get().From( cachedExpressions, 4 * maxCachedExpressions / 5, maxCachedExpressions - 1 );
      }

      data.formula = formula;
      data.inputs = inputs;
      data.result = result;
      
    }


    static Godot.Collections.Array GetInputs( Control control, UINumber number, Control parentControl, float width, float height, float relative )
    {
      var inputs = new Godot.Collections.Array();      

      _variables.ForEach(
        ( variableUnit )=>
        {
          inputs.Add( ComputeBaseValue( control, number, width, height, relative, variableUnit ) );
        }
      );

      return inputs;
    }

    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;
    }
  }

}
