﻿function CliFail( msg )
{
    alert( msg );
    while( true ){}
}

var CliActionType_Async = "launch-async";
var CliActionType_Repeat = "repeat-until-true";
var CliActionType_Unbroken = "unbroken";
var CliActionType_Group = "group";

var CliActionEP_Swallow = "swallow";

//--------------------------------------------------------------------------------
CliContinuationManager = function()
{
    /// <summary>Provides a centralised mechanism for executing a hierarchy of
    /// code that requires some form of threading.   The basic model is that
    // of continuations running with the help of window.timeout</summary>

    this._currentGroup;
    this._rootGroup;
    this._timeoutId = null;
    this._abortListeners = new Array();
    
    this._ctor();
    
    this._dbgSequence = "";
}

CliContinuationManager.prototype = 
{
    //----------------------------------------------------------------------
    _ctor : function()
    {
    },
    
    //----------------------------------------------------------------------
    Reset : function()
    {
        /// <summary>Resets the manager.   Terminates the current timout
        /// if possible.</summary>

        this._currentGroup = null;
        this._rootGroup = null;
        this._dbgSequence = "";
        this._abortListeners = new Array();
        window.clearTimeout( this._timeoutId );
    },
    
    //----------------------------------------------------------------------
    Abort : function( optionalEventParameter )
    {
        /// <summary>Aborts all active tasks and calls any registered
        /// abort event listeners</summary>
        /// <param name="optionalEventParameter">Optional paramater that
        /// will be passed to the abort listeners as the 'Parameter' member
        /// of the eveent args</param>
        
        try
        {
            var abortListeners = this._abortListeners;
            
            try { this.Reset(); } catch( e ){}
            
            var args = new CliContinuationManagerAbortEventArgs();
            args.Parameter = optionalEventParameter;
            
            for( var i = 0; i < abortListeners.length; i++ )
            {
                try
                {
                    var abortListener = abortListeners[ i ];
                    if( abortListener != null )
                        abortListener( args );
                }
                catch( e ){}
            }
        }
        catch( e ){}
    },

    //----------------------------------------------------------------------
    RegisterAbortListener : function( abortListener )
    {
        this._abortListeners[ this._abortListeners.length ] = abortListener;
    },
    
    //----------------------------------------------------------------------
    OpenGroup : function( name ) // CliContinuationGroup
    {
        /// <summary>Creates a group to which actions can be added.  This
        /// group becomes the current group to which other groups will
        /// be added when OpenGroup is next called.</summary>

        if( this._currentGroup == null )
        {
            this._rootGroup = new CliContinuationGroup( null, this );
            this._rootGroup.Name = "ROOT";
            this._currentGroup = this._rootGroup;
        }
        
        var newGroup = new CliContinuationGroup( this._currentGroup, this );
        this._currentGroup.AddAction( name, newGroup );
        this._currentGroup = newGroup;
        
        return newGroup;
    },
    
    //----------------------------------------------------------------------
    CloseGroup : function( group )
    {
        /// <summary>Closes the group.  Makes the parent of the group the
        /// current group.  If the parent is the root group, the root
        /// group is executed.</summary>

        if( group != this._currentGroup )
            CliFail( "closing groups out of sequence" );
        
        this._currentGroup = group._parentGroup;
        
         if( this._currentGroup == this._rootGroup )
            this._Run();
    },
    
    //----------------------------------------------------------------------
    _ContinueRunning : function()
    {
        try
        {
            this._Run();
        }
        catch( e )
        {
            CliDebug( "continuation manager: continue running: " + e.message );
            CliDisplayError( CliErrorAction_General, 30201 );
        }
    },
    
    //----------------------------------------------------------------------
    _Run : function()
    {
        if( this._currentGroup == null )
            return;
        
        var more = true;
        var self = this;
        
        while( more )
        {
            var action = this._currentGroup.NextAction();
            
            if( action == null )
            {
                this.AllDone();
                return;
            }
            
            try { this._dbgSequence += " --> " + action.Name; }catch( e ){ this._dbgSequence += " --> ???" }
            
            switch( action.getActionType() )
            {
                case CliActionType_Async:
                    this.ExecuteAfterShortDelay( 
                        function()
                        {
                            try
                            {
                                var dummy = null;
                                action.Run( self );
                                self._ContinueRunning();
                            }
                            catch( e )
                            {
                                CliDebug( "continuation manager: launch async: " + e.message );
                                CliDisplayError( CliErrorAction_General, 30200 );
                            }
                        } );
                    more = false;
                    break;

                case CliActionType_Repeat:
                    this.RepeatUntilTrueThenContinue( action );
                    more = false;
                    break;
                    
                case CliActionType_Unbroken:
                    if( action.ExceptionPolicy == CliActionEP_Swallow )
                    {
                        try
                        {
                            action.Run( this );
                        }
                        catch( e )
                        {
                            var dummy1 = null;
                            var dummy2 = null;
                        }
                    }
                    else
                    {
                        action.Run( this );
                    }
                    more = true;
                    break;
                    
                case CliActionType_Group:
                    this._currentGroup = action;
                    more = true;
                    break;
            }
        }
    },
    
    //----------------------------------------------------------------------
    ExecuteAfterShortDelay : function( fn )
    {
        /// <summary>Executes the function after a short delay.  The delay
        /// is performed by a timeout, and thus allow ui updates to be
        /// rendered.</summary>

        var fnWrapper = function()
        {
            try
            {
                fn();
            }
            catch( e )
            {
                CliDebug( "Continuation manager: ExecuteAfterShortDelay: " + e.message );
                CliDisplayError( CliErrorAction_General, 30202 );
            }
        };
        
        this._timeoutId = window.setTimeout( fnWrapper, 10 );
    },
    
    //----------------------------------------------------------------------
    RepeatUntilTrueThenContinue : function( action )
    {
        var self = this;
        
        var f = function()
        {
            try
            {
                var actionResult = action.Run( self ); // if this returns 'undefined' theres probably a setup error with the actions
                
                if( actionResult )
                {
                    self._ContinueRunning();
                }
                else
                {
                    self.RepeatUntilTrueThenContinue( action );
                }
            }
            catch( e )
            {
                CliDebug( "Continuation manager: RepeatUntilTrueThenContinue: " + e.message );
                CliDisplayError( CliErrorAction_General, 30203 );
            }
        };
        
        this.ExecuteAfterShortDelay( f );
    },
    
    //----------------------------------------------------------------------
    AllDone : function()
    {
        /// <summary>Called when the last action completes</summary>

        this.Reset();
    }
} //

//--------------------------------------------------------------------------------
CliContinuationManagerAbortEventArgs = function()
{
    this.Parameter = null;
}


//----------------------------------------------------------------------
CliContinuationGroup = function( parentGroup, continuationManager )
{
    this._cm = continuationManager;
    this._parentGroup = parentGroup;
    
    this._activities = new Array();
    this._runIndex = 0;
    
    // these fields are to keep this class compatible with CliAction
    this.Name = "";
    this.ExceptionPolicy = "";
}

CliContinuationGroup.prototype = 
{
    //----------------------------------------------------------------------
    NextAction : function()
    {
        if( this._runIndex >= this._activities.length )
            return this._parentGroup;
        
        var nextAction = this._activities[ this._runIndex ];
        this._runIndex++;
        return nextAction;
    },

    //----------------------------------------------------------------------
    AddYield : function()
    {
        this.AddAction( this.Name + "/yield/nop", new CliAction( CliActionType_Async, function(){}, null ) );
        this.AddAction( this.Name + "/yield/termdetect", new CliRepeatUntilTrueAction( function(){ return true; }, null ) );
    },
    
    //----------------------------------------------------------------------
    AddUnbrokenAction : function( fnAction, optionalParameter ) // returns an action accessor object
    {
        var actionObject = new CliAction( CliActionType_Unbroken, fnAction, optionalParameter );
        this.AddAction( this.Name + "/unbroken", actionObject );
        
        return new CliActionCreationReturn( actionObject );
    },
    
    //----------------------------------------------------------------------
    AddAsyncAction : function( fnAction, fnActionEndDetect, optionalParameter )
    {
        this.AddAction( this.Name + "/async/action", new CliAction( CliActionType_Async, fnAction, optionalParameter ) );
        this.AddAction( this.Name + "/async/termdetect", new CliRepeatUntilTrueAction( fnActionEndDetect, optionalParameter ) );
    },
    
    //----------------------------------------------------------------------
    AddAction : function( name, action )
    {
        /// <summary>Adds an action/activity to the group.  The passed
        /// action must have a Run() method and call Next() when it
        /// has completed all it's steps</summary>
        
        if( name != null )
            action.Name = name;
        
        if( this.IsRunning() )
        {
            action.Name = action.Name + "/run-" + this._runIndex;
            this._activities.splice( this._runIndex, 0, action );
        }
        else
        {
            action.Name = action.Name + "/" + this._activities.length;
            this._activities[ this._activities.length ] = action;
        }
    },
    
    //----------------------------------------------------------------------
    Run : function()
    {
        if( this.IsRunning() )
            CliFail( "running out of sequence" );
     
        return;
    },
    
    //----------------------------------------------------------------------
    IsRunning : function()
    {
        return this._runIndex > 0;
    },
    
    getActionType : function()
    {
        return "group";
    }
} //




CliActionCreationReturn = function( actionObject )
{
    this._actionObject = actionObject;
}
CliActionCreationReturn.prototype =
{
    SwallowExceptions : function()
    {
        this._actionObject.ExceptionPolicy = CliActionEP_Swallow;
    }
}

CliAction = function( actionType, fnAction, optionalParameter )
{
    this._fn = fnAction;
    this._optionalParameter = optionalParameter;
    this._actionType = actionType;
    this.Name = "";
    this.ExceptionPolicy = "";
}
CliAction.prototype =
{
    Run : function( continuationManager )
    {
        this._fn( this._optionalParameter );
    },
    
    getActionType : function()
    {
        return this._actionType;
    }
}

CliRepeatUntilTrueAction = function( fnAction, optionalParameter )
{
    this._fn = fnAction;
    this._optionalParameter = optionalParameter;
    this.Name = "";
    this.ExceptionPolicy = "";
}
CliRepeatUntilTrueAction.prototype =
{
    Run : function( continuationManager )
    {
        return this._fn( this._optionalParameter );
    },
    
    getActionType : function()
    {
        return CliActionType_Repeat;
    }
}

/*
CliUnbrokenAction = function( fnAction, optionalParameter )
{
    this._fn = fnAction;
    this._optionalParameter = optionalParameter;
    this.Name = "";
    this.ExceptionPolicy = "";
}
CliUnbrokenAction.prototype =
{
    Run : function( continuationManager )
    {
        this._fn( this._optionalParameter );
    },
    
    getActionType : function()
    {
        return "unbroken";
    }
}



CliAsynchronousAction = function( fnAction, optionalParameter )
{
    this._fn = fnAction;
    this._optionalParameter = optionalParameter;
    this.Name = "";
    this.ExceptionPolicy = "";
}
CliAsynchronousAction.prototype =
{
    Run : function( continuationManager )
    {
        this._fn( this._optionalParameter );
    },
    
    getActionType : function()
    {
        return "launch-async";
    }
}
*/