 /* Code from
	* http://www.developer.com/lang/jscript/article.php/10939_3592016_1 
	*/

/* Use it like this:
	* function Command() {
	*   if (!Command.NextID) Command.NextID = 0; //define class variable
	*   this.id = ++Command.NextID;              //define instance variable
	*   // unsynchronized API
	*   this.doit = function(){ alert("DOIT called"); // Override me
	*   this.undo = function(){ alert("UNDO called"); // Override me 
	*   this.redo = function(){ this.doit();          // Override me
	*   // synchronized API
	*   this.syncDoIt = function(){ new Mutex(this,"doit"); }
	*   this.syncUnDo = function(){ new Mutex(this,"undo"); }
	*   this.syncReDo = function(){ new Mutex(this,"redo"); }
	* }
*/


function Map() {
  this.map  = new Object();
  // Map API
  this.add     = function(k,o){ this.map[k] = o; }
  this.remove  = function( k ){ delete this.map[k]; }
  this.get     = function( k ){ return k==null ? null : this.map[k]; }
  this.first   = function(   ){ return this.get( this.nextKey( ) ); }
  this.next    = function( k ){ return this.get( this.nextKey(k) ); }
  this.nextKey = function( k ){ for (i in this.map) {
                                  if (!k) return i;
                                  if (k==i) k=null;    /*tricky*/
                                }
                                return null;
                              }
}
function Mutex( cmdObject, methodName ) {
  // define static variable and method
  if (!Mutex.Wait) Mutex.Wait = new Map();
  Mutex.SLICE = function( cmdID, startID ) {
    Mutex.Wait.get(cmdID).attempt( Mutex.Wait.get(startID) );
  }
  // define instance method
  this.attempt = function( start ) {
    for (var j=start; j; j=Mutex.Wait.next(j.c.id)) {
      if (j.enter || (j.number && (j.number < this.number ||
           (j.number == this.number && j.c.id < this.c.id) ) ) )
       return setTimeout("Mutex.SLICE("+this.c.id+","+j.c.id+")",10);
    }
    this.c[ this.methodID ]();    //run with exclusive access
    this.number = 0;              //release exclusive access
    Mutex.Wait.remove( this.c.id );
  }
  // constructor logic
  this.c        = cmdObject;
  this.methodID = methodName;
  Mutex.Wait.add( this.c.id, this );    //enter and number are
  this.enter    = true;
  this.number   = (new Date()).getTime();
  this.enter    = false;
  this.attempt( Mutex.Wait.first() );
}
