/**
 * jcontroller class
 *
 * Author : Pau Sanchez (contact@pausanchez.com)
 * Created: 23/01/2010  (dd-mm-yyyy)
 * Updated: 23/01/2010
 *
 * When extending this class, there are a couple of things you should know:
 *
 *  - on the hash part of the href, all characters that are not
 *    in the range [a-zA-Z0-9_-] are replaced by '_'
 *
 *  - each function has a 'this' object which is initialized as an array with the following keys:
 *     - hash
 *       The controller being called (after replacing characters)
 *
 *     - event
 *       The event name that triggered the controller
 *         + 'init':  triggered because the script has initiated
 *         + 'click': triggered by the user, by clicking an anchor element
 *
 *     - jq
 *       This is the jQuery object associated with the element that
 *       triggered the controller. On 'init' event, it will be $(document)
 *
 *     - controller
 *       This holds the methods you are defining when extending the controller
 *
 *  - Any hash variable which starts by '_' will be considered private and
 *    cannot be called. For example <a href="#_private"> the "_private" function
 *    will never be called, even if you defined it.
 *
 *  - There are two special methods that you might want to define:
 *     - _init
 *       This is a private method that will be called before calling
 *       anything else, so you can initialize here whatever you want
 *
 *       IMPORTANT: If you return a function, this function will be used to control
 *       who triggers what. You could define custom functions, instead of
 *       the default behaviour ;)
 *
 *     - _clear
 *       When exists, this function gets called before calling any other
 *       controller, so you can cleanup the view here.
 *     
 *     - _default
 *       This is a private method but get's called when no other method
 *       can be called. You definitelly want to define this, so you
 *       can call whatever you want
 *
 *  NOTE: Please note that you can define as many controllers as you want!
 *  Even on the same page, and triggered by different kind of events ;D
 *
 *
 * Examples:
 * 
 *  + Defining the HTML that triggers a controller:
 *
 *     <a href="#custom_controller" ...>Click me! :)</a>
 *     <a href="#custom-controller" ...>Click me! :)</a>
 *
 *
 *  + Defining a custom controller:
 *
 *     var g_controller = new jcontroller ({
 *       '_init' : function () {
 *         // this method will be called when the page loads
 *       },
 *       '_default' : function () {
 *         // this will be called when no other method can be called
 *         // You can call the custom controller: 
 *         //  example 1: this.controller.custom_controller()
 *         //  example 2: this.controller.custom_controller.apply(this)
 *       },
 *       '_clear': function () { // define it if you like
 *         // This method will be called before calling other methods,
 *         // you can use it to cleanup the view
 *       },
 *       'custom_controller' : function () { 
 *          alert ('hey! calling controller_name');
 *       }
 *     });
 *
 *  + Defining a custom trigger function (override the a href):
 *     var controller = new jcontroller ({
 *       '_init'   : function () {
 *         // do whatever you want
 *
 *         // define a custom controller here!
 *         return function () {
 *           var jc_object = this; // this is the jcontroller object
 *           $('div.clickme').live ('click', function() {
 *             var hash = $(this).attr ('trigger');
 *             jc_object.trigger (hash, 'custom-click', $(this));
 *           });
 *         };
 *       },
 *       '_default': function () { ... },
 *       '_clear'  : function () { ... },
 *       'whatever': function () { ... },
 *     });
 * 
 *     Now, if you define:
 *       <div class="clickme" trigger="whatever">...</div>
 *
 *     And the user clicks on that div, the 'whatever' element will trigger with a 'custom-click' event
 *     
 *     On the other hand, clicking on:
 *       <a href="#whatever">...</a>
 *     Will do NOTHING, we have OVERRIDED this behaviour!
 */
var jcontroller = function (extended) {
  this._methods = extended || {};
  this._vars    = {};

  this.trigger = function (hash /* eventName, jQueryObject */) {
    var ename = arguments[1] || 'trigger';
    var jq    = arguments[2] || $('a[href="' + hash + '"]') || $(document);

    return this._call_function (hash, ename, jq);
  },

  this.get = function (key) {
    if (!(key in this._vars))
      return false;
    return this._vars[key];
  },

  this.set = function (key, value) {
    this._vars[key] = value;
  },

  this.init = function () {
    var jcontroller_object = this;

    var result = this._call_function ('_init', 'init', $(document));
    if (typeof (result) == 'function') {
      // initialize their own trigger controller
      result.apply(this);
    }
    else {
      $('a').live ('click', function () {
        var href = $(this).attr ('href');
        if (href[0] != '#')
          return true;
  
        return jcontroller_object._call_function (href, 'click', $(this));
      });
    }

    // try to find the element that has this href associated
    var jq = $('a[href="' + window.location.hash + '"]');
    return this._call_function (window.location.hash, 'init', jq || $(document));
  }

  /** 
   * This function magically calls any of our internal methods,
   * initializes this object
   */
  this._call_function = function (hash, eventName, jqueryObject) {
    // get the hash of the page with no '#' and with all invalid chars replaced
    if (hash[0] == '#')
      hash = hash.substring (1);
    hash = hash.replace (/[^a-zA-Z0-9_-]/g, '_');

    // do not call empty hash functions or private functions which start by '_'
    if (
      ((hash.length > 0) && (hash[0] == '_')) &&
      ((hash != '_init') && (eventName != 'init'))
    )
    {
      return false;
    }

    var args = {
      'hash'   : hash,
      'event'  : eventName,
      'jq'     : jqueryObject,
      'self'   : this._methods,
      'parent' : this
    };

    // a default method so the user can display anything he wants
    if (!(hash in this._methods) && (hash != '_init'))
      hash = '_default';

    // then run the proper method
    if (hash in this._methods) {
      // if the  method exists, try to cleanup the view
      if (('_clear' in this._methods) && (hash != '_init')) // _clear is called always but for _init
        this._methods['_clear'].apply (args);
      
      return this._methods[hash].apply(args);
    }

    return true;
  }
}

