/**
 * @class ST.pageobject.Base
 * A PageObject is a simple class upon which custom methods can be defined to encapsulate 
 * common tasks associated with writing tests via the Sencha Test Futures API.
 *
 * ## Anatomy
 * A custom PageObject class has 2 main requirements:
 *
 * ### Class Name
 * To define a PageObject, you will use the following syntax:
 *
 *      ST.pageobject.define('MyCustomPageObject', {
 *          type: 'custom'
 *      });
 * 
 * In this example, "MyCustomPageObject" is the name of the custom PageObject class we are creating, 
 * it's fully qualified name being:
 *
 *      ST.pageobject.MyCustomPageObject
 *
 * ### Type
 * The "type" must be a unique key, per project, that identifies the PageObject (and will be used
 * to register the PageObject with the PageObject Manager). In our example above, "custom"
 * is the custom type for our PageObject class.
 *
 * ### Custom Methods
 * Beyond the requirements for creating a custom PageObject class, you can additional define 
 * any number of custom methods that encapsulate logic that you can use within your tests in a much less verbose way.
 *
 * For example, if we have a loginButton in our page that we wish to locate, we can create a custom method on our 
 * PageObject that returns a Component Future:
 *
 *      ST.pageobject.define('MyCustomPageObject', {
 *          type: 'custom',
 *
 *          getLoginButton: function () {
 *              return ST.button('#myLoginButton');
 *          }
 *      });  
 *
 * In this example, the "loginButton" method encapsulates the location of the button component. So now, instead of having
 * to use the verbose form (ST.button(...)), we can simply use our PageObject's custom methods:
 *
 *      var myPO = ST.PageObject.get('custom');
 *      var button = myPO.getLoginButton();
 *
 * Besides locating elements, however, we can additionally use PageObjects to encapsulate more complex functionality.
 *
 *      ST.pageobject.define('MyCustomPageObject', {
 *          type: 'custom',
 *
 *          getLoginButton: function () {
 *              return ST.button('#myLoginButton');
 *          },
 *
 *          submitLogin: function () {
 *              var button = this.getLoginButton();
 *              button.click();
 *         }
 *      });  
 *
 * In this example, we added a new method (submitLogin) which not only uses the other custom method to retrieve the 
 * button Future, but also executes the click() method from the Sencha Test API upon it. There are, of course, much more
 * complex scenarios that are supported, but hopefully this gives you a good taste of what is possible with PageObjects.
 */
ST.pageobject.Base = ST.define({
    type: 'basepageobject',
    $isPageObject: true,
    
    /**
     * @method getType
     * Returns the custom type used to uniquely identify this PageObject class
     * @return {String}
     */
    getType: function () {
        return this.type || this.name.toLowerCase();
    },
 
    /**
     * @method getName
     * Returns the short name for this PageObject class (e.g., ST.pageobject.Custom => "Custom")
     * @return {String}
     */
    getName: function () {
        return this.name;
    }
});
 
ST.pageobject._validateLocatorFn = function (def) {
    var hasLocator = !!def.locator,
        hasType = !!def.type;
 
    return hasLocator && hasType;
}
 
ST.pageobject._createLocatorFn = function (cls, key, def) {
    var type = def.type,
        locator = def.locator,
        fn;
 
    fn = function () {
        return ST[type](locator);
    }
 
    cls.prototype[key] = fn;
}
 
ST.pageobject.define = function (pageObjectName, body) {
    if (!body.extend) {
        // TODO: Do we want to allow extension of page objects? 
        // it's free, but will it cause headaches? 
        body.extend = ST.pageobject.Base;
    }
 
    var locators = body._locators,
        // by default, page objects will be auto-registered with manager when the class  
        register = typeof body.register !== 'undefined' ? body.register : true;
 
    delete body.register;
    delete body._locators;
 
    var cls = ST.define(body), // deletes body.extend 
        parts = pageObjectName.split('.'),
        methodScope = ST,
        classScope = ST.pageobject,
        type, className, locatorDef, locatorKey;
 
    while (parts.length > 1) {
        type = parts.shift();
 
        if (!classScope[type]) {
            classScope[type] = {};
        }
        if (!methodScope[type]) {
            methodScope[type] = {};
        }
    }
 
    type = parts[0];
 
    className = cls.prototype.$className = 'ST.pageobject.' + pageObjectName;
    cls.prototype.name = pageObjectName;
    cls.prototype.type = body.type || pageObjectName.toLowerCase();
 
    if (locators) {
        for (locatorKey in locators) {
            locatorDef = locators[locatorKey];
            if (ST.pageobject._validateLocatorFn(locatorDef)) {
                ST.pageobject._createLocatorFn(cls, locatorKey, locatorDef);
            }
        }
    }
 
    // add to ST.pageobject namesapce 
    classScope[type] = cls;
 
    // register is a hook for testing; normally, page objects will auto-register themselves, 
    // but we can specify register:false in the definition to prevent it and require manual registration 
    if (register) {
        // register with PageObject Manager 
        ST.PageObject.register(className);
    }
 
    return cls;
};