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


namespace Rokojori
{  
  public class ActionSequenceRunner
  {
    public ActionSequence sequence;
    public List<Action> actions;
    public List<SequenceAction> sequencables;
    public int sequencablesIndex = 0;
    bool cancelled = false;
    bool cancelledSequence = false;
    
    int _runID = -1;
    
    SequenceAction _runningAction;
    int _runningActionID = -1;

    public void Cancel()
    {
      if ( cancelled || _runningAction == null )
      {
        return;
      }

      cancelled = true;
      _runningAction.CancelAction( _runningActionID );
    }

    void CancelRun()
    {
      if ( cancelledSequence )
      {
        return;
      }

      if ( ! cancelled )
      {
        cancelled = true;
      }

      cancelledSequence = true;
      sequence.DispatchCancelled( _runID );
      sequence.ClearRun( this );
    }


    public void ProcessNext()
    {
      // RJLog.Log( "@" + sequence.Name, "Index:", sequencablesIndex );

      if ( sequencablesIndex == 0 )
      {
        _runID = sequence.DispatchStart();

        if ( sequencables.Count == 0 )
        {
          actions.ForEach( a => Action.Trigger( a ) );
          sequence.DispatchEnd( _runID );
          sequence.ClearRun( this );
          return;
        }
      }

      if ( sequencablesIndex >= sequencables.Count )
      {
        TriggerAllAfter( sequencables[ sequencables.Count -1 ] );
        sequence.DispatchEnd( _runID );
        sequence.ClearRun( this );
        return;
      }

      if ( cancelled )
      {
        CancelRun();
        return;
      }     
      

      var sequenceAction = sequencables[ sequencablesIndex ];
      StartAction( sequenceAction );      
    } 

    Dictionary<SequenceAction,System.Action<SequenceActionFinishedEvent>> callbacks = 
    new Dictionary<SequenceAction, System.Action<SequenceActionFinishedEvent>>(); 

    void StartAction( SequenceAction action )
    {
      var capturedAction = action;

      System.Action<SequenceActionFinishedEvent> callback = 
        ( SequenceActionFinishedEvent ev ) =>
        {
          
          
          //RJLog.Log( "On Done", id, success );

          if ( ev.id != _runningActionID )
          {
            // RJLog.Error( "Invalid ID", id, "!=", _runningActionID );
            return;
          }

          _runningActionID = -1;

          if ( ev.success )
          {
            sequencablesIndex ++;
            ProcessNext();
          }
          else 
          {
            sequence.DispatchCancelled( _runID );
            sequence.ClearRun( this );
          }

          var callbackReference = callbacks[ capturedAction ];          
          capturedAction.onSequenceDone.RemoveAction( callbackReference );

          callbacks.Remove( capturedAction );
        };

      RunNext( action, callback );

      /*      
      _runningAction = action;
      callbacks[ _runningAction ] = callback;
      _runningAction.OnSequenceDone += callback;
      TriggerAllBefore( _runningAction );
      Action.Trigger( _runningAction );
      _runningActionID = _runningAction.GetLastSequenceActionID();
      */
    }

    void RunNext( SequenceAction action, System.Action<SequenceActionFinishedEvent> callback )
    {
      _runningAction = action;
      callbacks[ _runningAction ] = callback;
      _runningAction.onSequenceDone.AddAction( callback );


      TriggerAllBefore( _runningAction );
      Action.Trigger( _runningAction );
      _runningActionID = _runningAction.GetLastSequenceActionID();
    }

    void TriggerAllBefore( SequenceAction action )
    {
      var actionIndex = actions.IndexOf( action );

      for ( int i = actionIndex - 1; i >= 0; i -- )
      {        
        if ( typeof( SequenceAction ).IsAssignableFrom( actions[ i ].GetType() ) )
        {
          return;
        }

        // RJLog.Log( "Triggering Action", actions[ i ].Name );

        Action.Trigger( actions[ i ] );
      }
      
    }

    void TriggerAllAfter( SequenceAction action )
    {
      var actionIndex = actions.IndexOf( action );

      for ( int i = actionIndex + 1; i < actions.Count; i ++ )
      {        
        if ( typeof( SequenceAction ).IsAssignableFrom( actions[ i ].GetType() ) )
        {
          return;
        }

        // RJLog.Log( "Triggering Action", actions[ i ].Name );
        Action.Trigger( actions[ i ] );
      }
      
    }


  }

  [GlobalClass, Icon("res://addons/rokojori_action_library/Icons/ActionSequence.svg") ]
  public partial class ActionSequence:SequenceAction
  { 
    /** <summary for="field actions">Actions to execute</summary>*/
    [Export]
    public Action[] actions = new Action[ 0 ];

    [Export]    
    public bool triggerDirectChildren = true;

    List<ActionSequenceRunner> running = new List<ActionSequenceRunner>();

    protected override void _OnTrigger()
    {      
      var run = new ActionSequenceRunner();
      run.sequence = this;
      run.actions = new List<Action>( actions );
      
      if ( triggerDirectChildren )
      {
        Nodes.ForEachDirectChild<Action>( this, a => run.actions.Add( a ) );
      }

      run.sequencables = Lists.FilterType<Action,SequenceAction>( run.actions );

      


     // RJLog.Log( "Running", HierarchyName.Of( this ), run.sequencables.Count );
      running.Add( run );
      run.ProcessNext();
    } 

    public override void CancelAction( int id )
    {
      running.ForEach( r => r.Cancel() );
    }

    public void ClearRun( ActionSequenceRunner run )
    {
      running.Remove( run );
    }
    
  }

  

}