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


namespace Rokojori
{  
  public class TimeLineRunner
  {
    public TimeLine timeLine;
    public TimeLineManager manager;
    
    public float lastPosition = 0;
    public float position = 0;
    public bool playing = false;
    public float deltaScale = 1;
    public float speed = 1;

    List<TimeLineEvent> events = new List<TimeLineEvent>();
    List<TimeLineSpan> spans = new List<TimeLineSpan>(); 
    List<TimeLineCallback> callbacks = new List<TimeLineCallback>();

    AnimationCurve modulatorCurve;
    float modulatorTime;
    Vector3 modulatorRandomization;
    Action<bool> modulatorOnReady = null;
    float _lastModulation = 1;
    float _currentDelta = 1;
    
    public float modulatedSpeed => (float)( speed * _lastModulation * deltaScale );
    public float currentDelta => _currentDelta;

    public TimeLineRunner( TimeLine timeLine, TimeLineManager manager )
    {
      this.timeLine = timeLine;
      this.manager = manager;
      this.speed = timeLine.startSpeed;

      playing = timeLine.autoStart;
    }

    public void Modulate( AnimationCurve curve, Action<bool> onReady )
    {
      if ( modulatorOnReady != null )
      {
        modulatorOnReady( false );
      }

      modulatorCurve = curve;
      modulatorTime = 0;
      modulatorRandomization = curve.Randomize();
      modulatorOnReady = onReady;
    }

    public void UpdateTimeLine( float realtimeDelta )
    { 
      if ( ! playing || ( manager.paused && ! timeLine.executesInPause ))
      {
        return;
      }

      var modulation = 1f;

      if ( modulatorCurve != null )
      {
        modulation = modulatorCurve.Sample( modulatorTime, modulatorRandomization );

        _lastModulation = modulation;
        modulatorTime += realtimeDelta;

        if ( modulatorTime >= modulatorCurve.GetRandomizedEndTime( modulatorRandomization ) )
        {
          var mr = modulatorOnReady;
          modulatorOnReady = null;
          modulatorCurve = null;
          modulatorTime = 0;
          modulatorRandomization = Vector3.Zero;

          if ( mr != null )
          {
            mr( true );
          }
          
        }
      }
      else
      {
        _lastModulation = 1;
      }

      _currentDelta = realtimeDelta * deltaScale * speed * modulation;
      lastPosition = position;
      position += _currentDelta;

      var isForward = speed >= 0;

      if ( isForward )
      {
        ProcessForward();
      }
      
    }


    
    void ProcessForward()
    { 
      ProcessEvents();
      ProcessSpans();
      ProcessCallbacks();      
    }

    List<TimeLineCallback> _callbackAdditions = new List<TimeLineCallback>();

    void ProcessCallbacks()
    {
      if ( _callbackAdditions.Count > 0 )
      {      
        callbacks.AddRange( _callbackAdditions );
        _callbackAdditions.Clear();
      }

      callbacks.ForEach( c => c.callback( c ) );


    }

    void ProcessEvents()
    {
      if ( requestedRemovalIDs.Count > 0 )
      {
        requestedRemovalIDs.Sort();

        RJLog.Log( "Removing:", requestedRemovalIDs, "From:", events );
        //Lists.RemoveIncreasingSortedIndices( events, requestedRemovalIDs );
        events = events.Filter( e => ! requestedRemovalIDs.Contains( e.id ) );
        requestedRemovalIDs.Clear();
      }    

      List<int> eventRemovals = null;

      for ( int  i = 0; i < events.Count; i++ )
      {
        var eventPosition = events[ i ].position;
        
        if ( events[ i ].looping )
        {
          var next =     events[ i ].GetNextLoopPosition( lastPosition );
          var previous = events[ i ].GetPreviousLoopPosition( position ); 

          if ( next != previous )
          {
            continue;
          }

          eventPosition = next;
        } 
        
        if ( ! RangeDouble.ContainsExclusiveMax( lastPosition, position, eventPosition ) )
        {
          if ( events[ i ].wasInside )
          {
            eventRemovals = AddRemoval( events[ i ].persistent, i, eventRemovals );
          }

          continue;
        }

        events[ i ].callback( events[ i ] );
        events[ i ].wasInside = true;

      }

      if ( eventRemovals != null )
      {
        Lists.RemoveIncreasingSortedIndices( events, eventRemovals );
      }

      
    }

    void ProcessSpans()
    { 
      if ( spans.Count == 0 )
      {
        return;
      }

      List<int> spanRemovals  = null;

      var timelineSpan = new RangeDouble( lastPosition, position );
      var span = new RangeDouble( 0, 1 );
      var isForward = lastPosition < position;

      for ( int  i = 0; i < spans.Count; i++ )
      {
        span.min = spans[ i ].start;
        span.max = spans[ i ].end;

        var overlaps = timelineSpan.Overlaps( span );

        if ( ! overlaps )
        {
          if ( spans[ i ].wasInside )
          {
            spanRemovals = AddRemoval( spans[ i ].persistent, i, spanRemovals );            
          }

          continue;
        }

        var isStart = timelineSpan.Contains( spans[ i ].start );
        var isEnd   = timelineSpan.Contains( spans[ i ].end );

        
        var spanType = TimeLineSpanUpdateType.InSpan;

        if ( isStart && isEnd )
        {
          spanType = TimeLineSpanUpdateType.CompletelyInside;
        }
        else if ( isStart )
        {
          spanType = TimeLineSpanUpdateType.Start;
        }
        else if ( isEnd )
        {
          spanType = TimeLineSpanUpdateType.End;
        }

        spans[ i ].wasInside = true;

        spans[ i ].callback( spans[ i ], spanType );        

      }

      if ( spanRemovals != null )
      {
        Lists.RemoveIncreasingSortedIndices( spans, spanRemovals );
      }
    }

    
    List<int> AddRemoval( bool isPersistent, int index, List<int> list )
    {
      if ( isPersistent )
      {
        return list;
      }

      if ( list == null )
      {
        list = new List<int>();
      }

      list.Add( index );

      return list;
    }

    List<int> requestedRemovalIDs = new List<int>();

    public void RemoveEvent( int eventID )
    {
      requestedRemovalIDs.Add( eventID );

      RJLog.Log( "requestedRemovals add:", eventID );
    }

    public TimeLineCallback _ScheduleCallback( Action<TimeLineCallback> callback, int eventID )
    {
      var tle = new TimeLineCallback();
      tle.id = eventID;
      tle.callback = callback;
      tle.timeLine = timeLine;

      _callbackAdditions.Add( tle );

      return tle;
    }

    public TimeLineEvent _ScheduleEvent( float position, int eventID, bool isPersistent, Action<TimeLineEvent> callback )
    {
      var tle = new TimeLineEvent();
      tle.position = position;
      tle.id = eventID;
      tle.persistent = isPersistent;
      tle.callback = callback;
      tle.timeLine = timeLine;

      events.Add( tle );

      return tle;
    }

    public TimeLineEvent _ScheduleLoopEvent( float loopDuration, float loopOffset, int eventID, bool isPersistent, Action<TimeLineEvent> callback )
    {
      var tle = new TimeLineEvent();
      tle.position = loopOffset;
      tle.looping = true;
      tle.loopDuration = loopDuration;
      tle.id = eventID;
      tle.persistent = isPersistent;
      tle.callback = callback;
      tle.timeLine = timeLine;
      events.Add( tle );

      return tle;
    }

    public TimeLineSpan _ScheduleSpan( float start, float end, int eventID, bool isPersistent, Action<TimeLineSpan, TimeLineSpanUpdateType> callback )
    {
      var tse = new TimeLineSpan();
      tse.start = start;
      tse.end   = end;
      tse.id = eventID;
      tse.persistent = isPersistent;
      tse.wasInside = false;
      tse.callback = callback;
      tse.timeLine = timeLine;
      
      spans.Add( tse );

      return tse;
    }
  }
}