/**
 * @class stpo
 * @singleton
 * `stpo` is a reserved namespace in which a project's registered PageObjects will be available.
 * While you can use the {@link ST.PageObject} Manager directly to retrieve instances of individual PageObjects,
 * the `stpo` object provides a more convenient way to access the PageObject instances.
 *
 *      // retrieve PageObject instance from Manager
 *      var login = ST.PageObject.get('login');
 *      // alternatively, retrieve PageObject instance from `stpo` object
 *      var login = stpo.login;
 */
stpo = {};  // eslint-disable-line 
 
/**
 * @class ST.PageObject
 * The PageObject Manager is a cache of registered PageObjects. The Manager provides a number of methods for interating
 * with PageObjects for use within tests. 
 */
ST.pageobject.Manager = ST.PageObject = {
    _pageObjects: {},
    _scopeName: 'stpo',
 
    /**
     * @method register
     * Registers a PageObject with the global Manager. Once registered, the PageObject can be retrieved via ST.PageObject.get.
     * @param {String/Function} pageObject The PageObject (either type or class) to register with the Manager's cache. NOTE: 
     * PageObjects are auto-registered with the cache when the class is defined, so there is typically no need to call this 
     * method directly
     */
    register: function (pageObject, overwrite) {
        var objs = pageObject, 
            i, isDupe, type, obj, instance, name;
 
        if (!objs) {
            throw new Error('No PageObject was provided for registration');
        }
 
        if (!ST.isArray(objs)) {
            objs = [objs];
        }
 
        for (i=0; i<objs.length; i++) {
            obj = objs[i];
            // accept string or constructor 
            obj = typeof obj === 'string' ? ST.clsFromString(obj) : obj;
            // try to instantiate an instance 
            try {
                instance = new obj();
            } catch (e) {
                throw new Error('There was an error creating the page object:\n ' + e);
            }
            // if instantiation was successful, get the type of the page object 
            type = instance.getType();
 
            if (!overwrite) {
                isDupe = !!this._pageObjects[type];
 
                if (isDupe) {
                    throw new Error('The PageObject provided (' + type + ') would duplicate an already-registered PageObject');
                }
            }
 
            name = instance.getName();
 
            // if we made it this far, register the page object 
            this._pageObjects[type] = {
                instance: instance,
                type: type,
                name: name,
                clsName: instance.$className
            };
            // add to global scope for shorthand purposes 
            window[this._scopeName][name] = instance;
        }
    },
 
    /**
     * @method deregisterAll
     * Removes all registered PageObjects from the Manager's registration cache
     */
    deregisterAll: function () {
        var objs = this._pageObjects,
            key;
 
        for (key in objs) {
            this.deregister(key, true);
        }
    },
 
    /**
     * @method deregister
     * Removes a PageObject from the Manager's registration cache
     * @param {String/Object/Function...} pageObject The PageObject to remove from the registration cache.
     */
    deregister: function (pageObject, ignoreErrors) {
        var type, instance,
            hasExistenceError = false,
            ignore = typeof ignoreErrors === 'undefined' ? false : !!ignoreErrors;
 
        if (!pageObject) {
            throw new Error('No PageObject was provided for deregistration');
        }
 
        if (pageObject === 'basepageobject') {
            if (!ignoreErrors) {
                throw new Error('Base PageObject cannot be deregistered');
            }
 
            return false;
        }
 
        if (typeof pageObject === 'string') {
            instance = this._findByName(pageObject);
            type = instance && instance.instance.getType();
        } else if (typeof pageObject === 'object' && pageObject.$isPageObject) {
            type = pageObject.getType();
        } else if (typeof pageObject === 'function') {
            type = pageObject.prototype.type;
        }
 
        if (type) {
            instance = this.get(type);
 
            if (!instance) {
                hasExistenceError = true;
            } else {
                // remove from internal cache 
                delete this._pageObjects[type];
                // remove from global scope to be a good citizen 
                delete window[this._scopeName][instance.getName()];
            }
        } else {
            hasExistenceError = true;
        } 
 
        if (hasExistenceError && !ignore) {
            throw new Error('The requested PageObject could not be deregistered because it does not exist in the registration cache');
        }
    },
 
    /**
     * @method get
     * Returns a PageObject from the Manager's registration cache by type, name, or className
     * @param {String} type The "type" of the PageObject to retrieve. The name or className of the PageObject may also be used
     * @return {Object} Registered PageObject instance
     */
    get: function (type) {
        var obj = this._pageObjects[type];
 
        if (!type) {
            throw new Error('No "type" provided for PageObject Manager lookup');
        }
 
        if (!obj) {
            obj = this._findByName(type);
        }
 
        return obj && obj.instance || null;
    },
 
    /**
     * @method getType
     * @private
     * Returns a PageObject from the Manager's registration cache by type, name, or className
     * @param {String} name The PageObject type, name, or className by which to locate the registered PageObject
     * @return {Object}
     */
    _findByName: function (name) {
        var pageObjects = this._pageObjects,
            obj, key, match;
 
        for (key in pageObjects) {
            obj = pageObjects[key];
 
            if (key === name || obj.name === name || obj.clsName === name) {
                match = obj;
                break;
            }
        }
 
        return match || null;
    }
};