/*<![CDATA[*//* #### UTILS INTERACTION #### */

/**
 * Utils classes for the interaction layer: BUndoStack, BFocusManager, BSelectionManager
 * @file utils.js
*/
include("core.js");


/**
 * Focus manager, it manages dinamic popup divs. Contains functions to clear
 * dinamic popups depending on page element that gets focused.<br/>
 * <b>Component behaviour.</b><br/>
 * When you add a dinamic div (or similar) over some element (eg. a menu, a
 * preview box ...) and you want it to be closed when its "focus scope" (the
 * element and its neighbours in your interaction logic) lost focus you could 
 * define a Command that does your stuff and add it as a "clearer" to this 
 * manager.
 * At every mousedown event that occours on the Body every registered "clearer"
 * is exectuded and unregistered.
 * If the mousedown is on yours element's "focus scope" and you don't want your
 * popup to be closed you could intercept the mousedown (the event occours before
 * on your div and then on the body) and signal the manager to exclude your clearer
 * from the imminent clearing (see BFocusManager::exlude method). Your clearer
 * won't be executed and your popups panel will be leaved on.
 * A clearer exists until it is executed then it will be erased.
 * Every mousedown is a potential focus change and therefore it is a clearing moment,
 * you have to exclude your clearer at every clearing moment (so handle the mousedown
 * until you need your popup on).
 * You can also directly execute only your clearer ( BFocusManager::executeClearer)
 * or invokes a clearing moment calling the BFocusManager::clear method.
 *  
 * @class BFocusManager
*/
var BFocusManager = new Class({
    initialize: function(){
        //this._clearers = new Hash({});
        this._clearers = [];
        this._buffer = [];
        this._excluded = [];
        $(window.document).addEvent('mousedown', this.clear.bind(this));
    },
    _clearers: null,
    _buffer : null,
    _excluded: null,
    /**
     * @function {public} addClearer
     * @param {Command} cmd - The clearer Command object
     * @param {Boolean} exclude - Indicates to exclude the new clearer from
     * the next clearing (useful when you define your clearer inside a mousedown
     * hendler execution)
     * @return Boolean true if the clearer has been regitered correctly, else false.
    */
    addClearer: function( cmd , exclude ){
//      if( $type( fn ) == 'function'){
//          this._clearers.set( key , fn );
        if(cmd && cmd.execute){
            this._clearers.include(cmd);
            if( exclude )
                this.exclude(cmd);
            return true;
        }else
            return false;
    },
    /**
     * Remove a clearer command from the list
     * @function {public} removeClearer
    */
    removeClearer: function( cmd ){
        this._clearers.remove(cmd);
        this._excluded.remove(cmd);
    },
    /**
     * Indicates to not execute the clearer passed as argument on the next clearing
     * @function {public} exclude
     * @param {Command} cmd - The Command object to do not execute
     *
    */
    exclude : function( cmd ){
        if( cmd && cmd.execute)
            this._excluded.include(cmd);
    },
    /**
     * Check if the BFocusManager 
     * @function {public Boolean} containsClearer
    */
    containsClearer:function(cmd){
        return this._clearers.contains(cmd);
    },
    /**
     * Execute the clearer passed as argument only if has previously been added,
     * without considering if it has been excluded.
     * @function {public} executeClearer
    */
    executeClearer: function(cmd){
        if( this.containsClearer(cmd) ){
            cmd.execute();
            this._clearers.remove( cmd );
            return true;
        } else
            return false;
    },
    /**
     * Execute a clearing. All the registerd clearers Command will be executed but the
     * ones previously excluded by invoking the BFocusManager::exclude method.
     * @function {public} clear
    */
    clear : function(){
     //   alert("ciao");
        var ex_l = this._excluded.length;
        for( i = 0, currExc = null; i< this._clearers.length; ++i){
            
            /*if( $chk( currExcIndex = this._excluded.indexOf(this._clearers[i])) ){
                this._buffer.include(this._excluded[ currExcIndex ]);
            }else{*/
            if( !this._excluded.contains( this._clearers[i])){
                this._clearers[i].execute();
            }
        }
        this._clearers.empty();
        this._clearers = $A( this._excluded );
        this._excluded.empty();
    }
});


/**
 * Interface for ControllerObject that manage a selectable view
 * @interface BSelectableControllerObject
*/
var BSelectableControllerObject = new Interface({
	/**
	 * Select the element. The meaning of selection is leaved to the interaction
	 * logic.
	 * @function {public} select
	*/
	select: function(){},
	/**
	 * Unselect the element. The meaning of unselection is leaved to the interaction
	 * logic.
	 * @function {public} unselect
	*/
	unselect: function(){},
	/**
	 * Tell if a BSelectableControllerObject is currently selected or not.
	 * @function {public Boolean} isSelected
	 * @return True if the BSelectableControllerObject is selected
	*/
	isSelected : function(){}
});


/**
 * Magager for the selection of elements, it is a collector of BSelectableControllerObject
 * objects but doesn't make assumption on the selection meaning, it simply provide
 * an interface to store, remove and retrieve object that the client want to assume as 
 * selected. Furthermore, it provides to define a scope view Element out of a clear selection command adding it to 
 * the BFocusManager passed as argument an
 * @class BSelectionManager
*/
var BSelectionManager = Class({
	Implements: Events,
	/* @var {private BSelectableControllerObject[]} _selected */
    _selected: null,
	/* @var {private Element} _scope */
    _scope :null,
	/* @var {private BFocusManager} _focusManager */
    _focusManager : null,
	/* @var {private String} name */
    _name : "",
	/* @var {private BClearSelectionCommand} _clearSelectionCommand */
    _clearSelectionCommand : null,

	/**
	 * @constructor 
	 * @param {Element} scope - The Element that define the scope of selection context.
	 *	If user action is focused in this scope the selection is mantained. Out of this scope
	 *  it is cleared.
	 * @param {BFocusManager} focusManager - The BFocusManager that manage the focus
	 * @param {optional String} name - The name of this selection context (helpful for debug).
	*/    
    initialize:function(scope, focusManager, name){
        this._selected = Array();
		this._scope = scope;
		this._focusManager = focusManager;
		this._name = name;
		// setup command for clearing
    		this._clearSelectionCommand = new BClearSelectionCommand(this);
		this._selectionPreventClearingHandler = (function(){
			this._focusManager.exclude(this._clearSelectionCommand);
		}).bind(this);
		
		if( this._focusManager && this._scope){
    			this._scope.addEvent("mousedown", this._selectionPreventClearingHandler);
		}
    },
    /**
     * Set the scope of this selection manager (after setting a scope, the previous one won't be considered anymore)
     * @function {public} setScope
     * @param {Element} element - The html element that wrap the selection context scope
    */
    setScope: function( element ){
    	this._scope.removeEvent("mousedown", this._selectionPreventClearingHandler);
    	this._scope = element;
    	this._scope.addEvent("mousedown", this._selectionPreventClearingHandler);
    },
    /**
     * Placeholder for the handler that prevent the clearing of selection if user acts inside the scope
     * {private Function} _selectionPreventClearingHandler
    */
    _selectionPreventClearingHandler: $lambda(),
    /**
     * Add a BSelectableControllerObject to the selection
     * @function {public} add
     * @param {BSelectableControllerObject} selectable - The object to add
    */
    add: function(selectable){
    	this.fireEvent("add", [this, selectable]);
        // if not present, add the clear selection command to the focus manager
        if(! this._focusManager.containsClearer(this._clearSelectionCommand))
            this._focusManager.addClearer( this._clearSelectionCommand );
    	return this._selected.include(selectable);
    },
    
    /**
     * Remove a BSelectableControllerObject from the selection
     * @function {public} remove
     * @param {BSelectableControllerObject} selectable - The object to remove
    */
    remove: function(selectable){
    	this.fireEvent("removing", [this, selectable]);
    	return this._selected.erase(selectable);
    },
    /**
     * Clear the selection. It invokes the BSelectableControllerObject::unselect
     * method on every object in the selection.
     * @function {public} clearSelection
    */
    clearSelection: function(){
    	var selected = $A(this._selected);
    	for( var i= 0; i< selected.length; ++i){
	    	this.fireEvent("removing", [this, selected[i]]);
    		selected[i].unselect();
    	}
    	this._selected.empty();
    },
    /**
     * Return the array with the selected objects.
     * @function {public BSelectableControllerObject[]} getSelected
     * @return A BSelectableControllerObject array.
    */
    getSelected: function(){
    	return this._selected;
    },
    /**
     * @function {public} contains
    */
    contains: function(selectable){
    	return this._selected.contains(selectable);
    },
    /**
     * Reuturn the name of thi selection context
     * @function {public} getName 
    */
    getName: function(){
    	return this._name;
    }
    
});

var BClearSelectionCommand = new Class({
    ImplementsInterface: Command,
    _selMng: null,
    initialize: function( selMng ){
        this._selMng = selMng;
    },
    execute: function(){
        this._selMng.clearSelection();
    },
    toString: function(){
        return "Clear selection on the model representation of:"+this._selMng.getName();
    }

});

/**
 * Undo/Redo command manager. Allow you to provide an undo redo capability.
 * @class UndoRedoStack
*/
var BUndoRedoStack = new Class({
    _commandStack: Array(),
    _redoStack: Array(),
    initialize:function(){},
    execute: function(command){
        command.execute();
        this._commandStack.push(command);
        this._redoStack.empty();
    },
    /**
     * Add to the executed stack a sequence of commands that you
     * consider a compound command already executed without execute the single
     * components.
     * Allow you to execute the single commands in different time (eg. useful
     * for visual effects) but to consider them as a single compound command in
     * the undo/redo logic.
     * @function pushCompoundCommand
    */
    pushCompoundCommand: function(commands){
        this._commandStack.push(commands);
        this._redoStack.empty();
    },
    executeCompund: function(commands){
        var cmpCmd = Array();
        var exe_fn = (function(commands){
            for(var i=0; i < commands.length;++i){
                if($typeof( command) == 'array')
                    exe_fn( command[i] );
                else
                    command[i].execute();
            }
        }).bind(this);
/*        for(var i=0; i < command.length;++i){
            command[i].execute();
            cmpCmd[i] = command[i];
        }*/
        this._commandStack.push(cmpCmd);
        this._redoStack.empty();            
    },
    undo: function(){
        if (!this._commandStack.length)
            return;
        var commands = [this._commandStack.pop()];
        var undo_fn = (function(commands){
            for(var i=commands.length; i < 0;--i){
                if($typeof( command) == 'array')
                    undo_fn( command[i] );
                else
                    command[i].undo();
            }
        }).bind(this);
        undo_fn( commands );
     
/*        var command = this._commandStack.pop();
        if($typeof( command) == 'array'){
            for(var i=command.length; i >0 ; --i){
                command[i].undo();
            }
        }else
            command.undo();*/
        this._redoStack.push(command);
    },
    redo : function(){
        if (!this._redoStack.length)
            return;
        var commands = [this._redoStack.pop()];
        var redo_fn = (function(commands){
            for(var i=0; i < commands.length;++i){
                if($typeof( command) == 'array')
                    redo_fn( command[i] );
                else
                    command[i].redo();
            }
        }).bind(this);
        redo_fn( commands );
        this._commandStack.push(command);
    }
});


// # Export modules' names
this.BUndoRedoStack = BUndoRedoStack;
this.BFocusManager = BFocusManager;
this.BSelectionManager = BSelectionManager;
this.BSelectableControllerObject = BSelectableControllerObject;
/*]]>*/
