/**
 * Copyright © 2016 maijindou.com All rights reserved.
 * See COPYING.txt for license details.
 */
define([
    'underscore',
    'mageUtils',
    'mage/utils/wrapper'
], function (_, utils, wrapper) {
    'use strict';

    var Class;

    /**
     * Returns property of an object if
     * it's his own property.
     *
     * @param {Object} obj - Object whose property should be retrieved.
     * @param {String} prop - Name of the property.
     * @returns {*} Value of the property or false.
     */
    function getOwn(obj, prop) {
        return _.isObject(obj) && obj.hasOwnProperty(prop) && obj[prop];
    }

    /**
     * Creates constructor function which allows
     * initialization without usage of a 'new' operator.
     *
     * @param {Object} protoProps - Prototypal propeties of a new consturctor.
     * @returns {Function} Created consturctor.
     */
    function createConstructor(protoProps, consturctor) {
        var constr = consturctor;

        if (!constr) {

            /**
             * Default constructor function.
             */
            constr = function () {
                var obj = this;

                if (!_.isObject(obj) || Object.getPrototypeOf(obj) !== constr.prototype) {
                    obj = Object.create(constr.prototype);
                }

                obj.initialize.apply(obj, arguments);

                return obj;
            };
        }

        constr.prototype = protoProps;
        constr.prototype.constructor = constr;

        return constr;
    }

    Class = createConstructor({

        /**
         * Entry point to the initialization of consturctors' instance.
         *
         * @param {Object} [options={}]
         * @returns {Class} Chainable.
         */
        initialize: function (options) {
            this.initConfig(options);

            return this;
        },

        /**
         * Recursively extends data specified in constructors' 'defaults'
         * property with provided options object. Evaluates resulting
         * object using string templates (see: mage/utils/template.js).
         *
         * @param {Object} [options={}]
         * @returns {Class} Chainable.
         */
        initConfig: function (options) {
            var defaults    = this.constructor.defaults,
                config      = utils.extend({}, defaults, options || {}),
                ignored     = config.ignoreTmpls || {},
                cached      = utils.omit(config, ignored);

            config = utils.template(config, this, false, true);

            _.each(cached, function (value, key) {
                utils.nested(config, key, value);
            });

            return _.extend(this, config);
        }
    });

    _.extend(Class, {
        defaults: {
            ignoreTmpls: {
                templates: true
            }
        },

        /**
         * Creates new constructor based on a current prototype properties,
         * extending them with properties specified in 'exender' object.
         *
         * @param {Object} [extender={}]
         * @returns {Function} New constructor.
         */
        extend: function (extender) {
            var parent      = this,
                parentProto = parent.prototype,
                childProto  = Object.create(parentProto),
                child       = createConstructor(childProto, getOwn(extender, 'constructor')),
                defaults;

            extender = extender || {};
            defaults = extender.defaults;

            delete extender.defaults;

            _.each(extender, function (method, name) {
                childProto[name] = wrapper.wrapSuper(parentProto[name], method);
            });

            child.defaults = utils.extend({}, parent.defaults || {});

            if (defaults) {
                utils.extend(child.defaults, defaults);
                extender.defaults = defaults;
            }

            return _.extend(child, {
                __super__:  parentProto,
                extend:     parent.extend
            });

        }
    });

    return Class;
});
