using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Text;
using Godot;
using System.Linq;

namespace Rokojori
{  
  [Tool]
  [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/Shake.svg")]
  public partial class Shake:SequenceAction, Animator
  { 
    [Export]
    public ShakeEffect shakeEffect;

    [Export]
    public Node3D[] targets = new Node3D[ 0 ];

    List<TransformData> _targetValues;

    int _actionID = -1;
    int _currentSpanAnimationID = -1;

    public void OnAnimatorStart(){}
    public void OnAnimatorEnd(){}
    public void OnAnimatorCancel(){}

    public override string[] _GetConfigurationWarnings()
    {
      var warnigns = new List<string>();

      if ( shakeEffect == null )
      {
        warnigns.Add( HierarchyName.Of( this ) + ":" + "No shake effect assigned");
      }

      for ( int i = 0; i < targets.Length; i++ )
      {
        if ( targets[ i ] == null )
        {
         warnigns.Add( HierarchyName.Of( this ) + ":" +"A null target is assigned in the targets array at index " + i );
        }
        
      }

      return warnigns.ToArray();
    }

    protected override void _OnTrigger()
    {
      if ( _actionID != -1 )
      {
        CancelAction( _actionID );
      }

      var sequenceID = DispatchStart();
      _actionID = sequenceID; 

      var random = LCG.Randomized();

      var networkSeed = random.GetSeed();

      var randomization  = shakeEffect.shakeAmountCurve.Randomize( random );
      var timeline = shakeEffect.timeline;

      var duration = shakeEffect.shakeAmountCurve.GetRandomizedEndTime( randomization );
      var curve = shakeEffect;

      var start = shakeEffect.timeline.position;

      var keyFrames = new List<KeyFrame<TransformData>>();
      var elapsed = 0f;

      while ( elapsed < duration )
      {
        var key = new KeyFrame<TransformData>();
        key.data = shakeEffect.GetShakeData( elapsed, randomization, random );        
        key.time = elapsed;
        keyFrames.Add( key );

        elapsed += 1f / shakeEffect.GetShakeChangeFPSRandomized( elapsed, random );
      }
      
      var kfAnimation = new KeyFrameAnimation<TransformData>();
      kfAnimation.keyFrames = keyFrames;
      kfAnimation.FlagSorted();

      KeyFrame<TransformData> lastKeyFrame = null;

      AnimationManager.StartAnimation( this, targets, AnimationMember.Transform );

      if ( _targetValues == null || _targetValues.Count == 0 )
      {
        _targetValues = Lists.Map( targets, t => TransformData.From( t, shakeEffect.globalPosition, shakeEffect.globalRotation ) );
      }
      


      _currentSpanAnimationID = TimeLineManager.ScheduleSpanIn( timeline, 0, duration, 
        ( TimeLineSpan span, TimeLineSpanUpdateType type )=>
        {     
          if ( span.id != _currentSpanAnimationID )
          {
            return;
          }
          
          if ( ! AnimationManager.IsAnimating( this, targets, AnimationMember.Transform ) )
          {
            return;
          }


          if ( TimeLineSpanUpdateType.End == type )
          {
            AnimationManager.EndAnimation( this, targets, AnimationMember.Transform );

            for ( int i = 0; i < targets.Length; i++ )
            {            
              var o = _targetValues[ i ];

              o.Set( targets[ i ], shakeEffect.globalPosition, shakeEffect.globalRotation );
            }

            _targetValues.Clear();
            _actionID = -1;
            _currentSpanAnimationID = -1;

            DispatchEnd( sequenceID );

            return;
          }

         
          var offset = shakeEffect.timeline.position - start;
          var keyFrame = kfAnimation.GetKeyFrameAt( offset );

          if ( keyFrame == lastKeyFrame && ! shakeEffect.smooth )
          {
            return;
          }

          var offsetData = keyFrame.data;
          var delta = shakeEffect.timeline.delta;

          for ( int i = 0; i < targets.Length; i++ )
          {
            var combined = offsetData.Clone();
            
            var o = _targetValues[ i ];
            
            if ( shakeEffect.scaleShakeIsRelative )
            {
              combined.scale *= _targetValues[ i ].scale;
            }

            combined.rotation *= MathX.DegreesToRadians;

            var beforeAdd = combined.Clone();
            
            combined.Add( o );
            
            // this.LogInfo( "Applying:", beforeAdd, " + ", o, "=", combined );


            if ( shakeEffect.smooth )
            {
              var rawSmooth = Mathf.Clamp( shakeEffect.smoothingStrength, 0, 1 );
              rawSmooth = Mathf.Pow( 1f - rawSmooth, 3 );
              var smoothStrength = rawSmooth * 250f;

              var current = TransformData.From( targets[ i ], shakeEffect.globalPosition, shakeEffect.globalRotation );
              combined.position = Smoother.SmoothTimeVariant( current.position, combined.position, delta, smoothStrength );
              combined.rotation = Smoother.SmoothTimeVariant( current.rotation, combined.rotation, delta, smoothStrength );
              combined.scale = Smoother.SmoothTimeVariant( current.scale, combined.scale, delta, smoothStrength );
            }

            combined.Set( targets[ i ], shakeEffect.globalPosition, shakeEffect.globalRotation );
          }

          lastKeyFrame = keyFrame;
        },
        this
      ).id;

    }

    public override void CancelAction( int actionID )
    {
      if ( actionID != _actionID )
      {
        return;
      }

      AnimationManager.EndAnimation( this, targets, AnimationMember.Transform );  
      _actionID = -1;
      _currentSpanAnimationID = -1;
    }
  }
}