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


namespace Rokojori
{  
  [Tool]
  [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/TimeLineManager.svg") ]
  public partial class TimeLineManager:NetworkNode
  {    
    [Export]
    public TimeLine[] timeLines = new TimeLine[ 0 ];

    [Export]
    public TimeLine gametimeTimeline;

    [Export]
    public TimeLine realtimeTimeline;

    [Export]
    public bool computeRealtimeWithEngineDelta;


    List<TimeLineRunner> _runners = new List<TimeLineRunner>();

    int _idCounter = 0;    
    bool _initialized = false;

    public override void _Ready()
    {
      Initialize();
    }

    public static TimeLineManager Get()
    {
      return Unique<TimeLineManager>.Get();
    }

    float _lastUpdate = 0;
    DateTime lastUpdated = DateTime.Now;
    float _estimatedDelta = 0;
    float _estimatedDeltaFilter = 0.9f;
    float _unscaledDelta = 0;
    double _lastRealtimePosition = 0;
    double _realtimePosition = 0;

    float _lastTimeScale = 1;

    QueueList<System.Action> _processCallbacks = new QueueList<System.Action>();

    bool _inProcess = false;

    public bool inProcess => _inProcess;

    bool _paused = false;
    public bool paused => _paused;

    public override void _Process( double delta )
    {       
      _inProcess = true;

      _paused = GetTree().Paused;

      UpdateRealTime( delta );

      if ( ! Engine.IsEditorHint() && gametimeTimeline != null )
      {
        Engine.TimeScale = GetRunner( gametimeTimeline ).modulatedSpeed;

      }
      
      _runners.ForEach( r => r.UpdateTimeLine( unscaledTimeDelta ) );     

      _processCallbacks.IterateAndResolve( t => t() ); 

      _inProcess = false;
    }
    
    public void AddProcessCallback( System.Action a )
    {
      _processCallbacks.QueueInsert( a );
    }

    public void RemoveProcessCallback( System.Action a )
    {
      _processCallbacks.QueueRemoval( a );
    }

    public int GetTimeLineIndex( TimeLine timeLine )
    {
      return timeLines == null ? -1 : Arrays.IndexOf( timeLines, timeLine );
    }

    public void Modulate( TimeLine timeline, AnimationCurve c, Action<bool> onReady )
    {
      var runner = GetRunner( GetTimeLineIndex( timeline ) );
      runner.Modulate( c, onReady );
    }    

    void UpdateRealTime( double engineDelta )
    {      
      var now = DateTime.Now;
      var unscaled = (float) ( ( now - lastUpdated ).TotalSeconds );
      lastUpdated = now;

      if ( unscaled < 1f/20f )
      {
        _estimatedDelta += _estimatedDeltaFilter * ( unscaled - _estimatedDelta );
      }      

      if ( computeRealtimeWithEngineDelta )
      {
        _unscaledDelta = (float)(  Engine.TimeScale == 0 ? _estimatedDelta : ( engineDelta / Engine.TimeScale ) );
      }
      else
      {
        _unscaledDelta = _estimatedDelta;
      }
      

      _lastRealtimePosition = _realtimePosition;
      _realtimePosition += _unscaledDelta;      
    }

    public float unscaledTimeDelta => _unscaledDelta;
    public double realtime => _realtimePosition;


    

    void Initialize()
    {
      if ( _initialized )
      {
        return;
      }

      _initialized = true;
      _runners = Lists.Map( timeLines, tl => new TimeLineRunner( tl, this  ) );

      // this.LogInfo( "Created runners:", _runners.Count );
    } 

    public static TimeLine Ensure( TimeLine timeline )
    {
      if ( timeline != null )
      {
        return timeline;
      }

      var tm = Get();

      if ( tm == null )
      {
        return new TimeLine();
      }

      return tm.gametimeTimeline;
    }

    public TimeLineRunner GetRunner( TimeLine timeline )
    {
      return GetRunner( _runners.FindIndex( r => r.timeLine == timeline ) );
    }

    public TimeLineRunner GetRunner( int index )
    {
      if ( index < 0 || index >= _runners.Count )
      {
        return null ;
      }

      return _runners[ index ];
    }

    int _CreateID()
    {      
      _idCounter ++;
      return _idCounter;
    }

    public static int CreateID()
    {
      var tm = Get();
      return tm._CreateID();
    }
    
  }
}