/**
 * Basic javascript inheritance implementation
 */

(function(){ 

  var inheritanceMap = {};
  var internalClassCounter = 0;
  
  var extend = function(destination, source, deep){
    var deep = Boolean(deep), copy;
    for (var name in source) {
      
      if (name === 'prototype') continue; //disallow auto-copy of prototypes
      
      if (deep && source[name] && (typeof source[name] === "object")){
        copy = source[name].constructor === Array ? [] : {};
        destination[name] = extend(copy,source[name],deep);
      } else destination[name] = source[name];
    }
    return destination;
  }
  
  /**
     * Arguments:
     *
     * class source 
     * string method || array (object parent , string method)
     * [arg1 .. argN] - array of optional additional arguments to be passed to parent method
     */
  
  var callParent = function(usePrototype){ 
    return function() {

      var source = null, parents = null, obj = false, method = null, args = Array.prototype.slice.apply(arguments);
      source = args.shift();
      
      if (!source)
        throw Error("Unable to call parent method, no source class specified.");

      //searching for parents
      parents = inheritanceMap[source.__internalClassNum];

      if (!parents || parents.constructor !== Array || parents.length === 0 )
        throw Error("Unable to call parent method, class has no parents.");
        
      var call = args.shift();
      if (!call)
        throw Error("Unable to call parent method, no method specified.");
          
      if (call.constructor === Array){ //[obj,method] passed
        method = call[1];
        var o = call[0];
        for (var i=0, l = parents.length;i<l;i++){        
          if (parents[i] === o) {
            if (usePrototype && o.prototype)
              o = o.prototype;
            if (o && o[method] && o[method].constructor === Function ) obj = o;
          }
        }
      } else { // no object specified  explicitly, searching for method in parents
        method = call;
        for (var i=0, l = parents.length;i<l;i++){
          var o = usePrototype ? parents[i].prototype : parents[i];
          if (o && o[method] && o[method].constructor === Function ) obj = o;
        }
      }
        
      if (obj && obj[method] && obj[method].constructor === Function) 
        obj[method].apply(this,args[0]);
      else 
        throw Error("Unable to call parent method");
    }
    
  }
  
  
  this.Class = {
    
    /**
     * Arguments:
     *
     * object staticData 
     * object instanceData 
     * class or [class1..classN] parents
     *
     * Alternative arguments:
     *
     * class or [class1..classN] parents
     *
     */
    
    create:function(){
      
      var staticData, instanceData, parents;
      
      //if firts argument is a static data
      if (arguments[0] && arguments[1] && arguments[0].constructor === Object && arguments[1].constructor === Object)
      {
        staticData = arguments[0];
        instanceData = arguments[1];
        if (arguments[2])
        {
          if (arguments[2].constructor === Function) 
            parents = [arguments[2]];
          else
            parents = arguments[2];
        }
      }
      
      //if first argument is a list of parents
      if (!parents && (arguments[0].constructor === Array || arguments[0].constructor === Function)){
        if (arguments[0].constructor === Function) 
          parents = [arguments[0]];
        else
          parents = arguments[0];
      }
      //class itself
      var _class = function(){ return this.__construct.apply(this,arguments); }
      //extending class with parent methods
      if (parents){
        for (var i=0;i<parents.length;i++) {        
          extend(_class,parents[i],true);
          if (parents[i].prototype) //non-static class
            extend(_class.prototype,parents[i].prototype,true);
        }
      }
      
      //extending class with static and instance data
      if (staticData)
        extend(_class,staticData);
      if (instanceData)
        extend(_class.prototype,instanceData);
      
      //reassigning overriden contructor
      _class.prototype.constructor = _class;
      
      //adding ability to call parent methods
      if (!_class.prototype.__parent || _class.prototype.__parent.constructor !== Function)
        _class.prototype.__parent = callParent(true);
      
      if (!_class.__parent || _class.__parent.constructor !== Function)
        _class.__parent = callParent();
      
      //extending inheritance map
      _class.__internalClassNum = internalClassCounter++;
      inheritanceMap[_class.__internalClassNum] = parents;
       
      return _class;
    }
    
  }

})();