/*********************************************
 * Object Oriented Programming in Javascript *
 *                                           *
 *  N (Package)                              *
 *  |                                        *
 *  +- A (Abstract class)                    *
 *  |                                        *
 *  +- B (Class extends A)                   *
 *  |                                        *
 *  +- I (Interface)                         *
 *  |                                        *
 *  +- C (Class implements I)                *
 *  |                                        *
 *  +- D (Class extends B implements I)      *
 *  |                                        *
 *  +- J (Interface)                         *
 *  |                                        *
 *  +- K (Interface extends I, J)            *
 *  |                                        *
 *  +- L (Interface)                         *
 *  |                                        *
 *  +- E (Class implements K, L)             *
 *  |                                        *
 *  +- F (Final class)                       *
 *  |                                        *
 *  +- M (Module)                            *
 *                                           *
 *********************************************/

// Namespace N
var N = N || {};

// Abstract class A
N.A = function () {

    if (notExtending) throw Error("Abstract class");

    var self = this;

    /***********************************/

    // Private members
    var __privAtt;
    var __privMethod = function () {};

    // Public members
    self.pubAtt = null;
    self.pubMethod = function () { throw Error("Abstract method") };

    /***********************************/

    // No constructor A

};

// Class B extends A
N.B = function () {

    var self = this;

    // Extending A, avoiding private static variables
    var super = new N.A();
    for (var m in super) self[m] = super[m];

    /***********************************/

    //...
    self.B = function () {}; // Constructor
    self.pubMethod = function () {
        //...
        super.pubMethod(); // pubMethod parent class
        //...
    };
    //...

    /***********************************/

    // Executes constructor B
    if (notExtending) self.B.apply(self, arguments);

}

// Prototype chain B -> A
function I () {};
I.prototype = N.A.prototype;
N.B.prototype = new I();
N.B.prototype.constructor = N.B;

// Interface I
N.I = {

    //...
    CONST: null,
    pubMethod: function () { throw Error("Abstract method") }
    //...

}

// Class C implements I
N.C = function () {

    var self = this;
    for (var m in N.I) self[m] = N.I[m]; // Implementing I

    /***********************************/

    //...
    self.C = function () {}; // Constructor
    self.pubMethod = function () {}
    //...

    /***********************************/

    if (notExtending) self.C.apply(self, arguments);

}

// Class D extends B implements I
N.D = function () {

    var self = this;
    for (var m in N.I) self[m] = N.I[m]; // Implementing I
    var super = new N.B(); // Extending B
    for (var m in super) self[m] = super[m];

    /***********************************/

    //...
    self.D = function () { // Constructor
        //...
        super.B(); // Constructor parent class
        //...
    };
    self.pubMethod = function () {};
    //...

    /***********************************/

    if (notExtending) self.D.apply(self, arguments);

}

// Prototype chain D -> B -> A
I.prototype = N.B.prototype;
N.D.prototype = new I();
N.D.prototype.constructor = N.D;

// Interface J
N.J = {

    //...
    CONST2: null,
    pubMethod2: function () { throw Error("Abstract method") }
    //...

}

// Interface K extends I, J
N.K = {

    //...
    CONST3: null,
    pubMethod3: function () { throw Error("Abstract method") }
    //...

}

for (var m in N.I) N.K[m] = N.I[m]; // Extending I
for (var m in N.J) N.K[m] = N.J[m]; // Extending J

// Interface L
N.L = {

    //...
    CONST4: null,
    pubMethod4: function () { throw Error("Abstract method") }
    //...

}

// Class E implements K, L
N.E = function () {

    var self = this;
    for (var m in N.K) self[m] = N.K[m]; // Implementing K
    for (var m in N.L) self[m] = N.L[m]; // Implementing L


    /***********************************/

    //...
    self.E = function () {}; // Constructor
    self.pubMethod = function () {};
    self.pubMethod2 = function () {};
    self.pubMethod3 = function () {};
    self.pubMethod4 = function () {};
    //...

    /***********************************/

    if (notExtending) self.E.apply(self, arguments);

}

// Final class F
N.F = function () {

    if (Extending) throw Error("Final class");

    var self = this;

    /***********************************/

    //...

    /***********************************/

    self.F.apply(self, arguments);

};

/*********************************************
 * Note1: "notExtending" or "Extending" is   *
 * any mechanism that can tell us that the   *
 * class is being extended, no big deal ;-)  *
 *                                           *
 * Note2: we can always use helper functions *
 * to extend, implement, maintain prototype  *
 * chain and execute constructor, reducing   *
 * considerably the extra code               *
 ********************************************/

// Module M
N.M = (function () {

    var self = {};

    /***********************************/

    // Private members
    var __privVar;
    var __privFunc = function () {
        //...
        self.pubFunc();
        //...
    };

    // Public members
    self.pubVar = null;
    self.pubFunc = function () {
        //...
        __privFunc();
        //...
    };

    /***********************************/

    return self;

})();