/* OPS - event manager - 0.1
 * Copyright : @Andreacfm
 * Interface Class from: Javascript Design Patterns of Diaz and Harmes 
 * /
 * 
/*
 * Interface Support
 * This class is used to provide a solid interface method to validate objects instance against.
 */
Interface = function(name, methods) {
    if(arguments.length != 2) {
        throw new Error("Interface constructor called with " + arguments.length
          + "arguments, but expected exactly 2.");
    }
    
    this.name = name;
    this.methods = [];
    for(var i = 0, len = methods.length; i < len; i++) {
        if(typeof methods[i] !== 'string') {
            throw new Error("Interface constructor expects method names to be " 
              + "passed in as a string.");
        }
        this.methods.push(methods[i]);        
    }    
};    

Interface.ensureImplements = function(object) {
    if(arguments.length < 2) {
        throw new Error("Function Interface.ensureImplements called with " + 
          arguments.length  + "arguments, but expected at least 2.");
    }

    for(var i = 1, len = arguments.length; i < len; i++) {
        var interface = arguments[i];
        if(interface.constructor !== Interface) {
            throw new Error("Function Interface.ensureImplements expects arguments "   
              + "two and above to be instances of Interface.");
        }
        
        for(var j = 0, methodsLen = interface.methods.length; j < methodsLen; j++) {
            var method = interface.methods[j];
            if(!object[method] || typeof object[method] !== 'function') {
                throw new Error("Function Interface.ensureImplements: object " 
                  + "does not implement the " + interface.name 
                  + " interface. Method " + method + " was not found.");
            }
        }
    } 
};


/* 
 * Declare OPS object/package
 */

var OPS = (function(){
	
/*  
 place here static var
*/
	
return{
		
/*
 * Class OPS.Core
 */

	Core : {
		/* 
		 *Publisher object that accept subscriptions.
		 *For any registered event is created a new Publisher Object.
		 *The deliver method run all the array of subscriptors objects passing the event as argument and eventually 
		 *run the callback required. 
		 */		
		Publisher : function () {	
			
			/*
			 * Internal array where are placed the reference to the subscribed object.
			 */
			var subscribers = [];
			
			this.subscribe = function(obj){
				
				/*
				 * Check to prevent that a listener is subscribed more than 1 time.
				 */
				for ( i = 0 ; i < subscribers.lenght ; i ++){
					if ( subscribers[i] === obj ) {
					    return;
					 }
				}
				
				subscribers.push(obj);
			}
			
			/*
			 * When event is dispatched the deliver method is fired.
			 * Get the subscribers array and loop over it.
			 * Fire the subscribed functions passing the event object as argument.
			 * If the event has a callback fire also the that with the event object as argument. 
			 */
				
			this.deliver = function(ev) {
				
				for ( i = 0 ; i < subscribers.length ; i ++ ){
						
					subscribers[i](ev);
					
					var call = ev.getCallback();
			
					if(typeof(call) == 'function'){
						call(ev);
					}
				}	
				return this;
			}
		},					
		interfaces : {
			/*
			 * Repository for the OPS interfaces sentence.
			 */
			eventInterface : new Interface('EventObj', ['getName','getData','getCallback'])
		}	
	},
		
/*
 * Class OPS.Events
 */

	Events : (function(){
		/* 
		 * private var eventsRepo 
		 * Internal events repository
		 */
		var eventsRepo = {};	
									
		return{
			
			/* 
			 * Add a new event. 
			 * Check that interfaces is implemented. 
			 * For any event is stored a OPS.Core.Publisher instance with the event name as unique reference..
			 * IMPORTANT:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
			 * ONLY THE NAME IS STORED  // DATA AND CALLBACK WILL BE TAKEN FROM THE EVENT PASSED AS ARGUMENT ON DISPATCHEVENT.
			 * :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 
			 */	
			addEvent : function(ev){

				Interface.ensureImplements(ev,OPS.Core.interfaces.eventInterface);

				/*
				 * if a event with the same name exists throw an exeption
				 */
				if(eventsRepo[ev.getName()]){
					
					throw('An Even with name "' + ev.getName() + '" already exists. Event name must be unique.');
				
				}

				eventsRepo[ev.getName()] = new OPS.Core.Publisher();

			},

			/* 
			 * Dispatch Event
			 * Check that the event dispatched is a valid Event Class.
			 * Call the deliver method of the relative Publisher Class
			 */ 
			dispatchEvent : function(ev){
				
				Interface.ensureImplements(ev,OPS.Core.interfaces.eventInterface);
				
				var publisher = eventsRepo[ev.getName()];
				
				publisher.deliver(ev);
	 
			},
			
			/*
			 *Utility method that return the array of the registered Events.
			 */
			getRegisteredEvents : function(){
				
				return eventsRepo;
			
			},
			
			/*
			 * Subscribe the passed object to the event class passed as second argument.
			 */
						
			subscribe : function( obj , ev ){
				
				Interface.ensureImplements(ev,OPS.Core.interfaces.eventInterface);
				
				var publisher = eventsRepo[ev.getName()];
				
				publisher.subscribe(obj);	
			}		
		
		}
	
	})(),
		
		/*
		 * Class OPS.Factory
		 */		
		Factory : {
			
			/*
			 * Class OPS.Factory.Event
			 */			
			Event : function(name,data,callback){		

				var name, data , callback;								

				this.getName = function(){
						return name;
					}

				this.setName = function(n){
					name = n;
				}	

				this.getData = function(){
						return data;
					}

				this.setData = function(d){
						data = d;
					}	

				this.getCallback = function(){
						return callback;
					}

				this.setCallback = function(c){
						callback = c;
					}

				this.setName(name);
				this.setData(data);
				this.setCallback(callback);				

			}				

		},
		
		/*
		 * Class OPS.Factory.Data
		 * Simple object to store arrays of datas.
		 */			
		Data :{
			
			store :  function(d){
	
	 			var data = d || {};
					
				this.getData = function(index){

					if(index){

						return data[index];

					}else{

						return data

					} 		
				}
			}
		}	
	}
		
})();
