/*!
 * jQBoxa 0.1, Copyright (c) 2009 Pelle Wessman, <http://kodfabrik.se/>, MIT Style License.
 */
/*global jQuery */
/*jslint white: true, onevar: true, browser: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true, maxerr: 50, indent: 2 */
/**
 * @fileOverview An extendable modular jQuery popup library
 * @name jQBoxa
 */

/**
 * Create a jQBoxa popup object
 * @param   {object} [options]    The options for the popup
 * @config  {object} [element]    The popup element - if left out a div-element will be created that data can be added to through callbacks
 * @config  {object} [container]  The element to which the popup element will be appended - if not specified it will be the body-element
 * $returns {object}              A new popup object
 */
var jQBoxa = function (options) {
  var isActive, triggerCallback, subscribe, trigger, show, hide, redraw, $popup, popupobj, callbacks = {};

  if (typeof options !== 'object') {
    options = {};
  }

  /**
   * Check if the popup is currently activated - "popped up"
   * @memberOf jQBoxaPopup
   * @returns {boolean} True if active
   */
  isActive = function () {
    var parent;
    if (typeof $popup === 'undefined') {
      return false;
    }
    else {
      parent = $popup.get(0).parentNode;
      return !(!parent || parent.nodeType === 11);
    }
  };

  /**
   * Trigger a callback registered on the popup object
   * @memberOf jQBoxaPopup
   * @param   {string}  thecallback  The name of the callback
   * @returns {boolean}              False if any of the callbacks returned false
   */
  triggerCallback = function (thecallback) {
    if (typeof callbacks[thecallback] === 'function') {
      return callbacks[thecallback].call(popupobj) !== false;
    }
    return true;
  };

  /**
   * Add a callback to the popup
   * @memberOf jQBoxaPopup
   * @todo     Make it possible to as well send a string as first argument and a function as a second
   * @param    {object}  addCallbacks  An object containing all of the callbacks with the callback names as the keys
   * @config   {object}  [created]     Fired when the popup element has been created and initialized for the first time
   * @config   {object}  [preShow]     Fired before the element is shown - even if it's already active. If false is returned it will cancel the showing of the popup.
   * @config   {object}  [postShow]    Fired after the element is shown - not fired if preShow canceled the showing
   * @config   {object}  [preHide]     Fired before the element is hidden. If false is returned it will cancel hiding of the element.
   * @config   {object}  [postHide]    Fired after the element is hidden.
   * @returns  {object}                The popup element itself - to enable chaining
   */
  subscribe = function (addCallbacks) {
    var key;
    for (key in addCallbacks) {
      if (addCallbacks.hasOwnProperty(key)) {
        if (typeof callbacks[key] === 'function') {
          callbacks[key] = (function (oldfunc, newfunc) {
            return function () {
              return oldfunc.apply(this, arguments) !== false && newfunc.apply(this, arguments) !== false;
            };
          }(callbacks[key], addCallbacks[key]));
        }
        else {
          callbacks[key] = addCallbacks[key];
        }
      }
    }
    return this;
  };

  /**
   * Shows the popup if hidden and hides the object if shown
   * @memberOf jQBoxaPopup
   * @returns  {object} The popup element itself - to enable chaining
   */
  trigger = function () {
    if (!isActive()) {
      show();
    }
    else {
      hide();
    }
    return this;
  };

  /**
   * Shows the popup
   * @memberOf jQBoxaPopup
   * @todo Add options for this specific function as well?
   * @todo The postShow should be fired even if the show is canceled? how should otherwise something initialized in preShow know that it wasn't shown? Would make it more inline with the hide() as well - the only bad thing would be that the postShowers would have to call isActive()
   * @returns  {object} The popup element itself - to enable chaining
   */
  show = function () {
    var el;
    if (typeof $popup === 'undefined') {
      if (typeof options.element === 'object') {
        $popup = options.element;
        if (!($popup instanceof jQuery)) {
          $popup = jQuery($popup);
        }
      }
      else {
        $popup = jQuery('<div></div>');
      }
      this.element = $popup;
      $popup.addClass('jQBoxa' + (options.popupClass ? ' ' + options.popupClass : ''));
      el = $popup.get(0);
      el.parentNode.removeChild(el);
      if (!triggerCallback('created')) {
        return this;
      }
    }
    if (!triggerCallback('preShow') || isActive()) {
      return this;
    }
    jQuery(options.container ? options.container : 'body').prepend($popup);
    redraw();
    triggerCallback('postShow');
    //Enable chaining
    return this;
  };

  /**
   * Hides the popup
   * @memberOf jQBoxaPopup
   * @returns  {object} The popup element itself - to enable chaining
   */
  hide = function () {
    var el;
    if (isActive()) {
      if (triggerCallback('preHide')) {
        el = $popup.get(0);
        el.parentNode.removeChild(el);
      }
      triggerCallback('postHide');
    }
    return this;
  };

  /**
   * Triggers a redraw of the popup. The reason for this one is that some extensions might need to react on differences in size or similar to reposition the element or similar.
   * @memberOf jQBoxaPopup
   * @returns  {object} The popup element itself - to enable chaining
   */
  redraw = function () {
    if (isActive()) {
      triggerCallback('redraw');
    }
    return this;
  };

  /**
   * @name jQBoxaPopup
   * @namespace The jQBoxa popup object - containing the functions needed to interact with it
   * @property {object}  options  The options set for the popup
   * @property {object}  element  A jQuery object referencing the popup element
   */
  popupobj = {
    //Properties
    options : options,
    element : $popup,

    //Methods
    isActive        : isActive,
    trigger         : trigger,
    show            : show,
    hide            : hide,
    redraw          : redraw,
    triggerCallback : triggerCallback,
    subscribe       : subscribe
  };

  return popupobj;
};
