/**

@license
Sideshow - An incredible Javascript interactive help Library
Version: 0.4.2
Date: 2015-03-10
Author: Alcides Queiroz [alcidesqueiroz(at)gmail(dot)com]
Available under Apache License 2.0 (https://raw2.github.com/fortesinformatica/sideshow/master/LICENSE)
**/

; (function (global, $, jazz, markdown) {

(function (name, module) {
  var ss = module();

  if (typeof define === 'function' && define.amd) {
    define(module);
  } else {
    global[name] = ss;
  }
})('sideshow', function () {
  //jQuery is needed
  if ($ === undefined) throw new SSException("2", "jQuery is required for Sideshow to work.");

  //Jazz is needed
  if (jazz === undefined) throw new SSException("3", "Jazz is required for Sideshow to work.");

  //Pagedown (the Markdown parser used by Sideshow) is needed
  if (markdown === undefined) throw new SSException("4", "Pagedown (the Markdown parser used by Sideshow) is required for Sideshow to work.");
  var globalObjectName = "Sideshow",

      $window,

      $body,

      $document,

      pollingDuration = 150,

      longAnimationDuration = 600,

      /** 

       The main class for Sideshow

       @class SS 

       @static

       **/

      SS = {

      /**

       The current Sideshow version

       @property VERSION

       @type String

       **/

      get VERSION() {

        return "0.4.2";

      }

      },

      controlVariables = [],

      flags = {

      lockMaskUpdate: false,

      changingStep: false,

      skippingStep: false,

      running: false

      },

      wizards = [],

      currentWizard,

      /**

       Possible statuses for an animation

       @@enum AnimationStatus

       **/

      AnimationStatus = jazz.Enum("VISIBLE", "FADING_IN", "FADING_OUT", "NOT_DISPLAYED", "NOT_RENDERED", "TRANSPARENT");

  /**
   A custom exception class for Sideshow

   @class SSException
   @extends Error
   @param {String} code                                  The error code
   @param {String} message                               The error message
   **/

  function SSException(code, message) {
    this.name = "SSException";
    this.message = "[SIDESHOW_E#" + ("00000000" + code).substr(-8) + "] " + message;
  }

  SSException.prototype = new Error();
  SSException.prototype.constructor = SSException;

  /**
   Shows a warning  in a pre-defined format

   @@function showWarning
   @param {String} code                                  The warning code
   @param {String} message                               The warning message
   **/

  function showWarning(code, message) {
    console.warn("[SIDESHOW_W#" + ("00000000" + code).substr(-8) + "] " + message);
  }

  /**
   Shows a deprecation warning in a pre-defined format

   @@function showDeprecationWarning
   @param {String} message                               The warning message
   **/

  function showDeprecationWarning(message) {
    console.warn("[DEPRECATION_WARNING] " + message);
  }

  /**
   Parses a string in the format "#px" in a number

   @@function parsePxValue
   @param {String} value                                 A value with/without a px unit
   @return Number                                        The number value without unit 
   **/

  function parsePxValue(value) {
    if (value.constructor !== String) return value;
    var br = value === "" ? "0" : value;
    return +br.replace("px", "");
  }

  /**
   Gets a string from the dictionary in the current language

   @@function getString
   @param {Object} stringKeyValuePair                    A string key-value pair in dictionary
   @return String                                        The string value in the current language
   **/

  function getString(stringKeyValuePair) {
    if (!(SS.config.language in stringKeyValuePair)) {
      showWarning("2001", "String not found for the selected language, getting the first available.");
      return stringKeyValuePair[Object.keys(stringKeyValuePair)[0]];
    }

    return stringKeyValuePair[SS.config.language];
  }

  /**
   Registers hotkeys to be used when running Sideshow

   @@function registerInnerHotkeys
   **/

  function registerInnerHotkeys() {
    $document.keyup(innerHotkeysListener);
  }

  /**
   Unregisters hotkeys used when running Sideshow

   @@function Unregisters
   **/

  function unregisterInnerHotkeys() {
    $document.unbind("keyup", innerHotkeysListener);
  }

  function innerHotkeysListener(e) {
    //Esc or F1
    if (e.keyCode == 27 || e.keyCode == 112) SS.close();
  }

  /**
   Registers global hotkeys

   @@function registerGlobalHotkeys
   **/

  function registerGlobalHotkeys() {
    $document.keyup(function (e) {
      //F2
      if (e.keyCode == 113) {
        if (e.shiftKey) SS.start({
          listAll: true
        });
        else SS.start();
      }
    });
  }

  /**
   Removes nodes created by Sideshow (except mask, which remains due to performance reasons when recalling Sideshow)

   @@function removeDOMGarbage
   **/

  function removeDOMGarbage() {
    $("[class*=\"sideshow\"]").not(".sideshow-mask-part, .sideshow-mask-corner-part, .sideshow-subject-mask").remove();
  }

  /**
   Strings Dictionary

   @@object strings
   **/
  var strings = {
    availableWizards: {
      "en": "Available Tutorials",
      "pt-br": "Tutoriais Disponíveis",
      "es": "Tutoriales Disponibles"
    },
    relatedWizards: {
      "en": "Related Wizards",
      "pt-br": "Tutoriais Relacionados",
      "es": "Tutoriales Relacionados"
    },
    noAvailableWizards: {
      "en": "There's no tutorials available.",
      "pt-br": "Não há tutoriais disponíveis para esta tela.",
      "es": "No hay tutoriales disponibles."
    },
    close: {
      "en": "Close",
      "pt-br": "Fechar",
      "es": "Cerrar"
    },
    estimatedTime: {
      "en": "Estimated Time",
      "pt-br": "Tempo Estimado",
      "es": "Tiempo Estimado"
    },
    next: {
      "en": "Next",
      "pt-br": "Continuar",
      "es": "Continuar"
    },
    finishWizard: {
      "en": "Finish Wizard",
      "pt-br": "Concluir Tutorial",
      "es": "Concluir Tutorial"
    }
  };
  /**
   Sideshow Settings

   @@object config
   **/
  SS.config = {};

  /**
   Application route to persists user preferences

   @@field userPreferencesRoute
   @type String
   @@unused
   @@todo Implement persistence logic
   **/
  SS.config.userPreferencesRoute = null;

  /**
   Logged in user

   @@field loggedInUser
   @type String
   @@unused
   **/
  SS.config.loggedInUser = null;

  /**
   Chosen language for sideshow interface

   @@field language
   @type String
   **/
  SS.config.language = "en";

  /**
   Defines if the intro screen (the tutorial list) will be    skipped when there's just one 
   tutorial available. This way, when Sideshow is invoked, the first step is directly shown.

   @@field autoSkipIntro
   @type boolean
   **/
  SS.config.autoSkipIntro = false;

  /**
   Stores the variables used in step evaluators 

   @class ControlVariables
   @static
   **/
  SS.ControlVariables = {};

  /**
   Sets a variable value

   @method set
   @param {String} name                                  The variable name
   @param {String} value                                 The variable value
   @return {String}                                      A formatted key=value pair representing the defined variable 
   **/
  SS.ControlVariables.set = function (name, value) {
    var variable = {};
    if (this.isDefined(name)) {
      variable = this.getNameValuePair(name);
    } else controlVariables.push(variable);

    variable.name = name;
    variable.value = value;
    return name + "=" + value;
  };

  /**
   Sets a variable if not defined yet

   @method setIfUndefined
   @param {String} name                                  The variable name
   @param {String} value                                 The variable value
   @return {String}                                      A formatted key=value pair representing the defined variable 
   **/
  SS.ControlVariables.setIfUndefined = function (name, value) {
    if (!this.isDefined(name)) return this.set(name, value);
  };

  /**
   Checks if some variable is already defined

   @method isDefined
   @param {String} name                                  The variable name
   @return {boolean}                                     A boolean indicating if the variable is already defined
   **/
  SS.ControlVariables.isDefined = function (name) {
    return this.getNameValuePair(name) !== undefined;
  };

  /**
   Gets a variable value

   @method get
   @param {String} name                                  The variable name
   @return {any}                                         The variable value
   **/
  SS.ControlVariables.get = function (name) {
    var pair = this.getNameValuePair(name);
    return pair ? pair.value : undefined;
  };

  /**
   Gets a pair with name and value 

   @method getNameValuePair
   @param {String} name                                  The variable name
   @return {Object}                                      A pair with the variable name and value
   **/
  SS.ControlVariables.getNameValuePair = function (name) {
    for (var i = 0; i < controlVariables.length; i++) {
      var variable = controlVariables[i];
      if (variable.name === name) return variable;
    }
  };

  /**
   Remove some variable from the control variables collection

   @method remove
   @param {String} name                                  The variable name
   @return {Object}                                      A pair with the removed variable name and value
   **/
  SS.ControlVariables.remove = function (name) {
    return controlVariables.splice(controlVariables.indexOf(this.getNameValuePair(name)), 1);
  };

  /**
   Clear the control variables collection 

   @method clear
   **/
  SS.ControlVariables.clear = function () {
    controlVariables = [];
  };

  /**
   A visual item 

   @class VisualItem
   @@abstract
   **/
  var VisualItem = jazz.Class().abstract;

  /**
   The jQuery wrapped DOM element for the visual item

   @@field $el
   @type Object 
   **/
  VisualItem.field("$el");

  /**
   The jQuery wrapped DOM element for the visual item

   @@field $el
   @type AnimationStatus 
   **/
  VisualItem.field("status", AnimationStatus.NOT_RENDERED);

  /**
   Renders the item's DOM object

   @method render
   **/
  VisualItem.method("render", function ($parent) {
    ($parent || $body).append(this.$el);
    this.status = AnimationStatus.NOT_DISPLAYED;
  });

  /**
   Destroys the item's DOM object

   @method destroy
   **/
  VisualItem.method("destroy", function () {
    this.$el.remove();
  });
  /**
   A visual item which can be shown and hidden

   @class HidableItem
   @@abstract
   @extends VisualItem
   **/
  var HidableItem = jazz.Class().extending(VisualItem).abstract;

  /**
   Shows the visual item

   @method show
   @param {boolean} displayButKeepTransparent            The item will hold space but keep invisible
   **/
  HidableItem.method("show", function (displayButKeepTransparent) {
    if (!this.$el) this.render();
    if (!displayButKeepTransparent) this.$el.removeClass("sideshow-invisible");
    this.$el.removeClass("sideshow-hidden");
    this.status = AnimationStatus.VISIBLE;
  });

  /**
   Hides the visual item

   @method hide
   **/
  HidableItem.method("hide", function (keepHoldingSpace) {
    if (!keepHoldingSpace) this.$el.addClass("sideshow-hidden");
    this.$el.addClass("sideshow-invisible");
    this.status = AnimationStatus.NOT_DISPLAYED;
  });
  /**
   A visual item which holds fading in and out capabilities

   @class FadableItem
   @@abstract
   @extends HidableItem
   **/
  var FadableItem = jazz.Class().extending(HidableItem).abstract;

  /**
   Does a fade in transition for the visual item

   @method fadeIn
   **/
  FadableItem.method("fadeIn", function (callback, linearTimingFunction) {
    var item = this;
    item.status = AnimationStatus.FADING_IN;

    if (!item.$el) this.render();
    if (linearTimingFunction) item.$el.css("animation-timing-function", "linear");
    item.$el.removeClass("sideshow-hidden");

    //Needed hack to get CSS transition to work properly
    setTimeout(function () {
      item.$el.removeClass("sideshow-invisible");

      setTimeout(function () {
        item.status = AnimationStatus.VISIBLE;
        if (linearTimingFunction) item.$el.css("animation-timing-function", "ease");
        if (callback) callback();
      }, longAnimationDuration);
    }, 20); //<-- Yeap, I'm really scheduling a timeout for 20 milliseconds... this is a dirty trick =)
  });

  /**
   Does a fade out transition for the visual item

   @method fadeOut
   **/
  FadableItem.method("fadeOut", function (callback, linearTimingFunction) {
    var item = this;
    if (item.status != AnimationStatus.NOT_RENDERED) {
      item.status = AnimationStatus.FADING_OUT;

      if (linearTimingFunction) item.$el.css("animation-timing-function", "linear");
      item.$el.addClass("sideshow-invisible");

      setTimeout(function () {
        item.$el.addClass("sideshow-hidden");
        item.status = AnimationStatus.NOT_DISPLAYED;
        if (linearTimingFunction) item.$el.css("animation-timing-function", "ease");
        if (callback) callback();
      }, longAnimationDuration);
    }
  });

  /**
   Represents a tutorial

   @class Wizard
   @@initializer
   @param {Object} wizardConfig                          The wizard configuration object                        
   **/
  var Wizard = jazz.Class(function (wizardConfig) {
    this.name = wizardConfig.name;
    this.title = wizardConfig.title;
    this.description = wizardConfig.description;
    this.estimatedTime = wizardConfig.estimatedTime;
    this.affects = wizardConfig.affects;
    this.preparation = wizardConfig.preparation;
    this.listeners = wizardConfig.listeners;
    this.showStepPosition = wizardConfig.showStepPosition;
    this.relatedWizards = wizardConfig.relatedWizards;
  });

  /**
   A function to prepare the environment for running a wizard (e.g. redirecting to some screen)

   @@field preparation
   @type Function
   **/
  Wizard.field("preparation");

  /**
   An object with listeners to this wizard (e.g. beforeWizardStarts, afterWizardEnds)

   @@field listeners
   @type Object
   **/
  Wizard.field("listeners");

  /**
   A configuration flag that defines if the step position (e.g. 2/10, 3/15, 12/12) will be shown

   @@field showStepPosition
   @type boolean
   **/
  Wizard.field("showStepPosition");

  /**
   An array with related wizards names. These wizards are listed after the ending of the current wizard.

   @@field relatedWizards
   @type Array
   **/
  Wizard.field("relatedWizards");

  /**
   The wizard unique name (used internally as an identifier)

   @@field name
   @type String
   **/
  Wizard.field("name");

  /**
   The wizard title (will be shown in the list of available wizards)

   @@field title
   @type String
   **/
  Wizard.field("title");

  /**
   The wizard description (will be shown in the list of available wizards)

   @@field description
   @type String
   **/
  Wizard.field("description");

  /**
   The wizard estimated completion time (will be shown in the list of available wizards)

   @@field estimatedTime
   @type String
   **/
  Wizard.field("estimatedTime");

  /**
   A collection of rules to infer whether a wizard should be available in a specific screen

   @@field affects
   @type Array
   **/
  Wizard.field("affects");

  /**
   The sequence of steps for this wizard

   @@field storyline
   @private
   @type Object
   **/
  Wizard.field("_storyline");

  /**
   Points to the current step object in a playing wizard

   @@field currentStep
   @type Object
   **/
  Wizard.field("currentStep");

  /**
   Sets the storyline for the wizard

   @method storyLine
   **/
  Wizard.method("storyLine", function (storyline) {
    this._storyline = storyline;
  });

  /**
   Runs the wizard

   @method play
   **/
  Wizard.method("play", function () {
    var wiz = this;

    Polling.enqueue("check_composite_mask_subject_changes", function () {
      Mask.CompositeMask.singleInstance.pollForSubjectChanges();
    });

    Polling.enqueue("check_arrow_changes", function () {
      Arrows.pollForArrowsChanges(true);
    });

    //Checks if the wizard has a storyline
    if (!this._storyline) throw new SSException("201", "A wizard needs to have a storyline.");
    var steps = this._storyline.steps;

    //Checks if the storyline has at least one step
    if (steps.length === 0) throw new SSException("202", "A storyline must have at least one step.");

    DetailsPanel.singleInstance.render();

    StepDescription.singleInstance.render();

    var listeners = this.listeners;
    if (listeners && listeners.beforeWizardStarts) listeners.beforeWizardStarts();

    flags.changingStep = true;
    this.showStep(steps[0], function () {
      //Releases the polling for checking any changes in the current subject
      //flags.lockMaskUpdate = false;

      //Register the function that checks the completing of a step in the polling queue
      Polling.enqueue("check_completed_step", function () {
        wiz.pollForCheckCompletedStep();
      });
    });

    Mask.CompositeMask.singleInstance.fadeIn();
  });

  /**
   Shows a specific step

   @method showStep
   @param {Object} step                                  The step to be shown
   @param {Function} callback                            A callback function to be called
   **/
  Wizard.method("showStep", function (step, callback) {
    var wizard = this;
    flags.skippingStep = false;

    Arrows.clear();

    if (this.currentStep && this.currentStep.listeners && this.currentStep.listeners.afterStep) this.currentStep.listeners.afterStep();

    function skipStep(wiz) {
      flags.skippingStep = true;
      wizard.next();
    }

    if (step && step.listeners && step.listeners.beforeStep) step.listeners.beforeStep();

    //The shown step is, of course, the current
    this.currentStep = step;

    //If the step has a skipIf evaluator and it evaluates to true, we'll skip to the next step!
    if (step.skipIf && step.skipIf()) skipStep(this);

    if (flags.changingStep && !flags.skippingStep) {
      //Sets the current subject and updates its dimension and position
      if (step.subject) SS.setSubject(step.subject);
      else SS.setEmptySubject();
      //Updates the mask
      Mask.CompositeMask.singleInstance.update(Subject.position, Subject.dimension, Subject.borderRadius);

      var sm = Mask.SubjectMask.singleInstance;
      sm.fadeOut(function () {
        if (step.lockSubject) sm.show(true);
      });
      //The details panel (that wraps the step description and arrow) is shown
      DetailsPanel.singleInstance.show();
      //Repositionate the details panel depending on the remaining space in the screen
      DetailsPanel.singleInstance.positionate();
      //Sets the description properties (text, title and step position)
      var description = StepDescription.singleInstance;
      var text = step.text;
      text = text instanceof Function ? SS.heredoc(text) : text;
      if (step.format == "markdown") {
        description.setHTML(new markdown.Converter().makeHtml(text));
      } else description.setText(text);

      description.setTitle(step.title);
      description.setStepPosition((this.getStepPosition() + 1) + "/" + this._storyline.steps.length);
      //If this step doesn't have its own passing conditions/evaluators, or the flag "showNextButton" is true, then, the button is visible
      if (step.showNextButton || step.autoContinue === false || !(step.completingConditions && step.completingConditions.length > 0)) {
        var nextStep = this._storyline.steps[this.getStepPosition() + 1];
        if (nextStep) {
          description.nextButton.setText(getString(strings.next) + ": " + this._storyline.steps[this.getStepPosition() + 1].title);
        } else {
          description.nextButton.setText(getString(strings.finishWizard));
        }
        description.nextButton.show();

        if (step.autoContinue === false) description.nextButton.disable();
      } else {
        description.nextButton.hide();
      }

      if (step.targets && step.targets.length > 0) {
        Arrows.setTargets(step.targets);
        Arrows.render();
        Arrows.positionate();
        Arrows.fadeIn();
      }

      //Step Description is shown, but is transparent yet (since we need to know its dimension to positionate it properly)
      description.show(true);
      if (!Mask.CompositeMask.singleInstance.scrollIfNecessary(Subject.position, Subject.dimension)) {
        description.positionate();
        //Do a simple fade in for the description box
        description.fadeIn();
      }

      //If a callback is passed, call it    
      if (callback) callback();
      flags.changingStep = false;
    }
  });

  /**
   Shows the next step of the wizard

   @method next 
   @param {Function} callback                            A callback function to be called
   **/
  Wizard.method("next", function (callback, nextStep) {
    if (!flags.changingStep || flags.skippingStep) {
      flags.changingStep = true;
      var currentStep = this.currentStep;
      nextStep = nextStep || this._storyline.steps[this.getStepPosition(this.currentStep) + 1];
      var self = this;

      this.hideStep(function () {
        if (nextStep) self.showStep(nextStep, function () {
          if (callback) callback();
        });
        else {
          if (currentStep && currentStep.listeners && currentStep.listeners.afterStep) currentStep.listeners.afterStep();

          var completedWizard = currentWizard;
          currentWizard = null;
          var listeners = self.listeners;
          if (listeners && listeners.afterWizardEnds) listeners.afterWizardEnds();

          if (!SS.showRelatedWizardsList(completedWizard)) SS.close();
        }
      });
    }
  });

  /**
   Hides the step

   @method hideStep
   @param {Function} callback                            A callback function to be called in the ending of the hiding process
   **/
  Wizard.method("hideStep", function (callback) {
    StepDescription.singleInstance.fadeOut(function () {
      DetailsPanel.singleInstance.hide();
    });
    Arrows.fadeOut();
    Mask.SubjectMask.singleInstance.update(Subject.position, Subject.dimension, Subject.borderRadius);
    Mask.SubjectMask.singleInstance.fadeIn(callback);
  });

  /**
   Returns the position of the step passed as argument or (by default) the current step

   @method getStepPosition
   @param {Object} step                                  The step object to get position
   **/
  Wizard.method("getStepPosition", function (step) {
    return this._storyline.steps.indexOf(step || this.currentStep);
  });

  /**
   Checks if a wizard should be shown in the current context (running each evaluator defined for this wizard)

   @method isEligible
   @return {boolean}                                     A boolean indicating if this wizard should be available in the current context
   **/
  Wizard.method("isEligible", function () {
    var l = global.location;

    function isEqual(a, b, caseSensitive) {
      return (caseSensitive) ? a === b : a.toLowerCase() === b.toLowerCase();
    }

    for (var c = 0; c < this.affects.length; c++) {
      var condition = this.affects[c];
      if (condition instanceof Function) {
        if (condition()) return true;
      } else if (condition instanceof Object) {
        if ("route" in condition) {
          var route = l.pathname + l.search + l.hash;
          if (isEqual(route, condition.route, condition.caseSensitive)) return true;
        }

        if ("hash" in condition) {
          if (isEqual(location.hash, condition.hash, condition.caseSensitive)) return true;
        }

        if ("url" in condition) {
          if (isEqual(location.href, condition.url, condition.caseSensitive)) return true;
        }
      }
    }
    return false;
  });

  /**
   Checks if the current user already watched this wizard

   @method isAlreadyWatched
   @return {boolean}                                     A boolean indicating if the user watched this wizard
   @@todo Implement this method...
   **/
  Wizard.method("isAlreadyWatched", function () {
    //ToDo
    return false;
  });

  /**
   A Polling function to check if the current step is completed

   @method pollForCheckCompletedStep
   **/
  Wizard.method("pollForCheckCompletedStep", function () {
    var conditions = this.currentStep.completingConditions;
    if (conditions && conditions.length > 0 && !flags.skippingStep) {
      var completed = true;
      for (var fn = 0; fn < conditions.length; fn++) {
        var completingCondition = conditions[fn];
        if (!completingCondition()) completed = false;
      }

      if (completed) {
        if (this.currentStep.autoContinue === false) StepDescription.singleInstance.nextButton.enable();
        else currentWizard.next();
      }
    }
  });

  Wizard.method("prepareAndPlay", function () {
    currentWizard = this;

    if (!this.isEligible()) {
      if (this.preparation) this.preparation(function () {
        currentWizard.play();
      });
      else throw new SSException("203", "This wizard is not eligible neither has a preparation function.");
    } else this.play();
  });

  /**
   The panel that holds step description, is positionated over the biggest remaining space among the four parts of a composite mask

   @class DetailsPanel
   @@singleton
   @extends FadableItem
   **/
  var DetailsPanel = jazz.Class().extending(FadableItem).singleton;

  /**
   An object holding dimension information for the Details Panel

   @@field dimension
   @type Object
   **/
  DetailsPanel.field("dimension", {});

  /**
   An object holding positioning information for the Details Panel

   @@field position
   @type Object
   **/
  DetailsPanel.field("position", {});

  /**
   Renders the Details Panel

   @method render
   **/
  DetailsPanel.method("render", function () {
    this.$el = $("<div>").addClass("sideshow-details-panel").addClass("sideshow-hidden");
    this.callSuper("render");
  });

  /**
   Positionates the panel automatically, calculating the biggest available area and putting the panel over there

   @method positionate
   **/
  DetailsPanel.method("positionate", function () {
    var parts = Mask.CompositeMask.singleInstance.parts;

    //Considering the four parts surrounding the current subject, gets the biggest one
    var sortedSides = [
      [parts.top, "height"],
      [parts.right, "width"],
      [parts.bottom, "height"],
      [parts.left, "width"]
    ].sort(function (a, b) {
      return a[0].dimension[a[1]] - b[0].dimension[b[1]];
    });

    var biggestSide = sortedSides.slice(-1)[0];

    for (var i = 2; i > 0; i--) {
      var side = sortedSides[i];
      var dimension = side[0].dimension;
      if (dimension.width > 250 && dimension.height > 250) {
        if ((dimension.width + dimension.height) > ((biggestSide[0].dimension.width + biggestSide[0].dimension.height) * 2)) biggestSide = side;
      }
    }

    if (biggestSide[1] == "width") {
      this.$el.css("left", biggestSide[0].position.x).css("top", 0).css("height", Screen.dimension.height).css("width", biggestSide[0].dimension.width);
    } else {
      this.$el.css("left", 0).css("top", biggestSide[0].position.y).css("height", biggestSide[0].dimension.height).css("width", Screen.dimension.width);
    }

    this.dimension = {
      width: parsePxValue(this.$el.css("width")),
      height: parsePxValue(this.$el.css("height"))
    };

    this.position = {
      x: parsePxValue(this.$el.css("left")),
      y: parsePxValue(this.$el.css("top"))
    };
  });

  /**
   Class representing all the current shown arrows

   @class Arrows
   @static
   **/
  var Arrows = {};

  Arrows.arrows = [];

  /**
   Clear the currently defined arrows

   @method clear
   @static
   **/
  Arrows.clear = function () {
    this.arrows = [];
  };

  /**
   Sets the targets for arrows to point

   @method setTargets
   @static
   **/
  Arrows.setTargets = function (targets, targetsChanged) {
    if (targets.constructor === String) targets = $(targets);

    if (targets instanceof $ && targets.length > 0) {
      targets.each(function () {
        var arrow = Arrow.build();
        arrow.target.$el = $(this);
        if (arrow.target.$el.is(":visible")) {
          Arrows.arrows.push(arrow);
          arrow.onceVisible = true;
        }
      });
    }
    else if (!targetsChanged) throw new SSException("150", "Invalid targets.");
  };

  Arrows.recreateDOMReferences = function () {
    for (var a = 0; a < this.arrows.length; a++) {
      var arrow = this.arrows[a];
      arrow.$el.remove();
    }

    Arrows.clear();
    Arrows.setTargets(currentWizard.currentStep.targets, true);
    Arrows.render();
    Arrows.positionate();
    Arrows.show();
  };

  /**
   Iterates over the arrows collection showing each arrow

   @method show
   @static
   **/
  Arrows.show = function () {
    for (var a = 0; a < this.arrows.length; a++) {
      var arrow = this.arrows[a];
      arrow.show();
    }
  };

  /**
   Iterates over the arrows collection hiding each arrow

   @method hide
   @static
   **/
  Arrows.hide = function () {
    for (var a = 0; a < this.arrows.length; a++) {
      var arrow = this.arrows[a];
      arrow.hide();
    }
  };

  /**
   Iterates over the arrows collection fading in each arrow

   @method fadeIn
   @static
   **/
  Arrows.fadeIn = function () {
    for (var a = 0; a < this.arrows.length; a++) {
      var arrow = this.arrows[a];
      arrow.fadeIn();
    }
  };

  /**
   Iterates over the arrows collection fading out each arrow

   @method fadeOut
   @static
   **/
  Arrows.fadeOut = function () {
    for (var a = 0; a < this.arrows.length; a++) {
      var arrow = this.arrows[a];
      registerFadeOut(arrow);
    }

    function registerFadeOut(arrow) {
      arrow.fadeOut(function () {
        arrow.destroy();
      });
    }
  };

  /**
   Iterates over the arrows collection repositionating each arrow

   @method positionate
   @static
   **/
  Arrows.positionate = function () {
    for (var a = 0; a < this.arrows.length; a++) {
      var arrow = this.arrows[a];
      arrow.positionate();
    }
  };

  /**
   Iterates over the arrows collection rendering each arrow

   @method render
   @static
   **/
  Arrows.render = function () {
    for (var a = 0; a < this.arrows.length; a++) {
      var arrow = this.arrows[a];
      arrow.render();
    }
  };

  /**
   A Polling function to check if arrows coordinates has changed

   @method pollForArrowsChanges
   **/
  Arrows.pollForArrowsChanges = function () {
    var brokenReference = false;
    for (var a = 0; a < this.arrows.length; a++) {
      var arrow = this.arrows[a];
      if (arrow.hasChanged()) arrow.positionate();
      if (arrow.onceVisible && !arrow.target.$el.is(":visible")) brokenReference = true;
    }

    if (brokenReference) this.recreateDOMReferences();
  };

  /**
   A single arrow for pointing individual items in current subject 

   @class Arrow
   **/
  var Arrow = jazz.Class().extending(FadableItem);

  /**
   The jQuery wrapped object which will be pointed by this arrow

   @@field target
   @type Object
   **/
  Arrow.field("target", {});

  /**
   Flag created to set if the arrow was visible once, this is used for recreating references to the targets DOM objects

   @@field onceVisible
   @type Object
   **/
  Arrow.field("onceVisible", false);

  /**
   Renders the Arrow

   @method render
   **/
  Arrow.method("render", function () {
    this.$el = $("<div>").addClass("sideshow-subject-arrow").addClass("sideshow-hidden").addClass("sideshow-invisible");
    this.callSuper("render");
  });

  /**
   Positionates the Arrow according to its target

   @method positionate
   **/
  Arrow.method("positionate", function () {
    var target = this.target;
    target.position = {
      x: target.$el.offset().left - $window.scrollLeft(),
      y: target.$el.offset().top - $window.scrollTop()
    };
    target.dimension = {
      width: target.$el.outerWidth(),
      height: target.$el.outerHeight()
    };

    this.$el.css("top", target.position.y - 30 + "px").css("left", target.position.x + (parsePxValue(target.dimension.width) / 2) - 12 + "px");
  });

  /**
   Shows the Arrow 

   @method show
   **/
  Arrow.method("show", function () {
    this.callSuper("show");
    this.positionate();
  });

  /**
   Does a fade in transition in the Arrow 

   @method fadeIn
   **/
  Arrow.method("fadeIn", function () {
    this.callSuper("fadeIn");
    this.positionate();
  });

  /**
   Checks if the arrow's target position or dimension has changed

   @method hasChanged
   @return boolean                                     
   **/
  Arrow.method("hasChanged", function () {
    return (this.target.dimension.width !== this.target.$el.outerWidth() || this.target.dimension.height !== this.target.$el.outerHeight() || this.target.position.y !== (this.target.$el.offset().top - $window.scrollTop()) || this.target.position.x !== (this.target.$el.offset().left - $window.scrollLeft()));
  });
  /**
   Represents a panel holding the step description

   @class StepDescription
   @extends FadableItem
   @@initializer
   **/
  var StepDescription = jazz.Class(function () {
    this.nextButton = StepDescriptionNextButton.build();
  }).extending(FadableItem).singleton;

  /**
   The step description text content

   @@field text
   @type String
   **/
  StepDescription.field("text", "");

  /**
   The title text for the step description panel

   @@field title
   @type String
   **/
  StepDescription.field("title", "");

  /**
   An object holding dimension information for the Step Description panel

   @@field dimension
   @type Object
   **/
  StepDescription.field("dimension", {});

  /**
   An object holding positioning information for the Step Description panel

   @@field position
   @type Object
   **/
  StepDescription.field("position", {});

  /**
   An object representing the next button for a step description panel 

   @@field nextButton
   @type Object
   **/
  StepDescription.field("nextButton");

  /**
   Sets the text for the step description panel

   @method setText
   @param {String} text                                  The text for the step description panel
   **/
  StepDescription.method("setText", function (text) {
    this.text = text;
    this.$el.find(".sideshow-step-text").text(text);
  });

  /**
   Sets the HTML content for the step description panel

   @method setHTML
   @param {String} text                                  The HTML content for step description panel
   **/
  StepDescription.method("setHTML", function (text) {
    this.text = text;
    this.$el.find(".sideshow-step-text").html(text);
  });

  /**
   Sets the title for the step description panel

   @method setTitle
   @param {String} title                                 The text for the step description panel
   **/
  StepDescription.method("setTitle", function (title) {
    this.title = title;
    this.$el.find("h2:first").text(title);
  });

  /**
   Sets the title for the step description panel

   @method setStepPosition
   @param {String} title                                 The text for the step description panel
   **/
  StepDescription.method("setStepPosition", function (stepPosition) {
    this.stepPosition = stepPosition;
    this.$el.find(".sideshow-step-position").text(stepPosition);
  });

  /**
   Renders the step description panel

   @method render
   **/
  StepDescription.method("render", function () {
    this.$el = $("<div>").addClass("sideshow-step-description").addClass("sideshow-hidden").addClass("sideshow-invisible");

    var stepPosition = $("<span>").addClass("sideshow-step-position");
    this.$el.append(stepPosition);
    if (currentWizard.showStepPosition === false) stepPosition.hide();

    this.$el.append($("<h2>"));
    this.$el.append($("<div>").addClass("sideshow-step-text"));
    this.nextButton.render(this.$el);
    this.nextButton.$el.click(function () {
      currentWizard.next();
    });
    DetailsPanel.singleInstance.$el.append(this.$el);
  });

  /**
   Shows the step description panel

   @method show
   **/
  StepDescription.method("show", function (displayButKeepTransparent) {
    this.callSuper("show", displayButKeepTransparent);
    //this.positionate();
  });

  /**
   Positionates the step description panel

   @method positionate
   **/
  StepDescription.method("positionate", function () {
    var dp = DetailsPanel.singleInstance;

    if (dp.dimension.width >= 900) this.dimension.width = 900;
    else this.dimension.width = dp.dimension.width * 0.9;

    this.$el.css("width", this.dimension.width);

    var paddingLeftRight = (parsePxValue(this.$el.css("padding-left")) + parsePxValue(this.$el.css("padding-right"))) / 2;
    var paddingTopBottom = (parsePxValue(this.$el.css("padding-top")) + parsePxValue(this.$el.css("padding-bottom"))) / 2;

    this.dimension.height = parsePxValue(this.$el.outerHeight());

    //Checks if the description dimension overflow the available space in the details panel
    if (this.dimension.height > dp.dimension.height || this.dimension.width < 400) {
      this.dimension.width = $window.width() * 0.9;
      this.$el.css("width", this.dimension.width);
      this.dimension.height = parsePxValue(this.$el.outerHeight());

      this.position.x = ($window.width() - this.dimension.width) / 2;
      this.position.y = ($window.height() - this.dimension.height) / 2;
    } else {
      this.position.x = (dp.dimension.width - this.dimension.width) / 2;
      this.position.y = (dp.dimension.height - this.dimension.height) / 2;
    }

    this.$el.css("left", this.position.x - paddingLeftRight);
    this.$el.css("top", this.position.y - paddingTopBottom);
  });

  /**
   Step next button 

   @class StepDescriptionNextButton
   @extends HidableItem
   **/
  var StepDescriptionNextButton = jazz.Class().extending(HidableItem);

  /**
   The text for the next button

   @@field _text
   @private
   **/
  StepDescriptionNextButton.field("_text");

  /**
   Disables the next button

   @method disable
   **/
  StepDescriptionNextButton.method("disable", function () {
    this.$el.attr("disabled", "disabled");
  });

  /**
   Enables the next button

   @method enable
   **/
  StepDescriptionNextButton.method("enable", function () {
    this.$el.attr("disabled", null);
  });

  /**
   Sets the text for the next button

   @method setText
   @param {String} text                                  The text for the next button
   **/
  StepDescriptionNextButton.method("setText", function (text) {
    this._text = text;
    this.$el.text(text);
  });

  /**
   Renders the Next Button

   @method render
   @param {Object} $stepDescriptionEl                    The jQuery wrapped DOM element for the Step Description panel
   **/
  StepDescriptionNextButton.method("render", function ($stepDescriptionEl) {
    this.$el = $("<button>").addClass("sideshow-next-step-button");
    this.callSuper("render", $stepDescriptionEl);
  });
  /**
   Represents the current available area in the browser

   @class Screen
   @static
   **/
  var Screen = {};

  /**
   Object holding dimension information for the screen

   @@field
   @static
   @type Object
   **/
  Screen.dimension = {};

  /**
   Checks if the screen dimension information has changed

   @method hasChanged
   @static
   @return boolean
   **/
  Screen.hasChanged = function () {
    return ($window.width() !== this.dimension.width) || ($window.height() !== this.dimension.height);
  };

  /**
   Updates the dimension information for the screen 

   @method updateInfo
   @static
   **/
  Screen.updateInfo = function () {
    this.dimension.width = $window.width();
    this.dimension.height = $window.height();
  };
  /**
   The current subject (the object being shown by the current wizard)

   @class Subject
   @static
   **/
  var Subject = {};

  /**
   The current subject jQuery wrapped DOM element 

   @@field obj
   @static
   @type Object
   **/
  Subject.obj = null;

  /**
   The current subject dimension information

   @@field position
   @static
   @type Object
   **/
  Subject.dimension = {};

  /**
   The current subject positioning information

   @@field position
   @static
   @type Object
   **/
  Subject.position = {};

  /**
   The current subject border radius information

   @@field borderRadius
   @static
   @type Object
   **/
  Subject.borderRadius = {};

  /**
   Checks if the object has changed since the last checking

   @method hasChanged
   @return boolean
   **/
  Subject.hasChanged = function () {
    if (!this.obj) return false;

    return (this.obj.offset().left - $window.scrollLeft() !== this.position.x) || (this.obj.offset().top - $window.scrollTop() !== this.position.y) || (this.obj.outerWidth() !== this.dimension.width) || (this.obj.outerHeight() !== this.dimension.height) || (parsePxValue(this.obj.css("border-top-left-radius")) !== this.borderRadius.leftTop) || (parsePxValue(this.obj.css("border-top-right-radius")) !== this.borderRadius.rightTop) || (parsePxValue(this.obj.css("border-bottom-left-radius")) !== this.borderRadius.leftBottom) || (parsePxValue(this.obj.css("border-bottom-right-radius")) !== this.borderRadius.rightBottom) || Screen.hasChanged();
  };

  /**
   Updates the information about the suject

   @method updateInfo
   @param {Object} config                                Dimension, positioning and border radius information
   **/
  Subject.updateInfo = function (config) {
    if (config === undefined) {
      this.position.x = this.obj.offset().left - $window.scrollLeft();
      this.position.y = this.obj.offset().top - $window.scrollTop();
      this.dimension.width = this.obj.outerWidth();
      this.dimension.height = this.obj.outerHeight();
      this.borderRadius.leftTop = parsePxValue(this.obj.css("border-top-left-radius"));
      this.borderRadius.rightTop = parsePxValue(this.obj.css("border-top-right-radius"));
      this.borderRadius.leftBottom = parsePxValue(this.obj.css("border-bottom-left-radius"));
      this.borderRadius.rightBottom = parsePxValue(this.obj.css("border-bottom-right-radius"));
    } else {
      this.position.x = config.position.x;
      this.position.y = config.position.y;
      this.dimension.width = config.dimension.width;
      this.dimension.height = config.dimension.height;
      this.borderRadius.leftTop = config.borderRadius.leftTop;
      this.borderRadius.rightTop = config.borderRadius.rightTop;
      this.borderRadius.leftBottom = config.borderRadius.leftBottom;
      this.borderRadius.rightBottom = config.borderRadius.rightBottom;
    }

    Screen.updateInfo();
  };

  Subject.isSubjectVisible = function (position, dimension) {
    if ((position.y + dimension.height) > $window.height() || position.y < 0) {
      return false;
    }
    return true;
  };
  /**
   Namespace to hold classes for mask control

   @namespace Mask
   **/
  var Mask = {};
  /**
   Controls the mask that covers the subject during a step transition

   @class SubjectMask
   @@singleton
   **/
  Mask.SubjectMask = jazz.Class().extending(FadableItem).singleton;

  /**
   Renders the subject mask

   @method render
   **/
  Mask.SubjectMask.method("render", function () {
    this.$el = $("<div>").addClass("sideshow-subject-mask");
    this.callSuper("render");
  });

  /**
   Updates the dimension, positioning and border radius of the subject mask

   @method update
   @param {Object} position                              The positioning information 
   @param {Object} dimension                             The dimension information 
   @param {Object} borderRadius                          The border radius information 
   **/
  Mask.SubjectMask.method("update", function (position, dimension, borderRadius) {
    this.$el.css("left", position.x).css("top", position.y).css("width", dimension.width).css("height", dimension.height).css("border-radius", borderRadius.leftTop + "px " + borderRadius.rightTop + "px " + borderRadius.leftBottom + "px " + borderRadius.rightBottom + "px ");
  });
  /**
   Controls the mask surrounds the subject (the step focussed area)

   @class CompositeMask
   @@singleton
   **/
  Mask.CompositeMask = jazz.Class().extending(FadableItem).singleton;

  /**
   Initializes the composite mask

   @method init
   **/
  Mask.CompositeMask.method("init", function () {
    var mask = this;
    ["top", "left", "right", "bottom"].forEach(function (d) {
      mask.parts[d] = Mask.CompositeMask.Part.build();
    });
    ["leftTop", "rightTop", "leftBottom", "rightBottom"].forEach(function (d) {
      mask.parts[d] = Mask.CompositeMask.CornerPart.build();
    });
  });

  /**
   The parts composing the mask

   @@field parts
   @type Object
   **/
  Mask.CompositeMask.field("parts", {});

  /**
   Renders the composite mask 

   @method render
   **/
  Mask.CompositeMask.method("render", function () {
    var mask = this;
    for (var p in this.parts) {
      var part = this.parts[p];
      if (part.render) part.render();
    }
    this.$el = $(".sideshow-mask-part, .sideshow-mask-corner-part");
    // if(!this.$el || this.$el.length === 0) this.$el = $(".sideshow-mask-part, .sideshow-mask-corner-part");
    Mask.SubjectMask.singleInstance.render();
    ["leftTop", "rightTop", "leftBottom", "rightBottom"].forEach(function (d) {
      mask.parts[d].$el.addClass(d);
    });
    this.status = AnimationStatus.NOT_DISPLAYED;
  });

  /**
   Checks if the subject is fully visible, if not, scrolls 'til it became fully visible

   @method scrollIfNecessary
   @param {Object} position                              An object representing the positioning info for the mask
   @param {Object} dimension                             An object representing the dimension info for the mask
   **/
  Mask.CompositeMask.method("scrollIfNecessary", function (position, dimension) {
    function doSmoothScroll(scrollTop, callback) {
      $("body,html").animate({
        scrollTop: scrollTop
      }, 300, callback);
    }

    if (!Subject.isSubjectVisible(position, dimension)) {
      var description = StepDescription.singleInstance;
      var y = dimension.height > ($window.height() - 50) ? position.y : position.y - 25;
      y += $window.scrollTop();

      doSmoothScroll(y, function () {
        setTimeout(function () {
          DetailsPanel.singleInstance.positionate();
          description.positionate();
          description.fadeIn();
        }, 300);
      });

      return true;
    }
    return false;
  });

  /**
   Updates the positioning and dimension of each part composing the whole mask, according to the subject coordinates

   @method update
   @param {Object} position                              An object representing the positioning info for the mask
   @param {Object} dimension                             An object representing the dimension info for the mask
   @param {Object} borderRadius                          An object representing the borderRadius info for the mask
   **/
  Mask.CompositeMask.method("update", function (position, dimension, borderRadius) {
    Mask.SubjectMask.singleInstance.update(position, dimension, borderRadius);
    //Aliases
    var left = position.x,
        top = position.y,
        width = dimension.width,
        height = dimension.height,
        br = borderRadius;

    //Updates the divs surrounding the subject
    this.parts.top.update({
      x: 0,
      y: 0
    }, {
      width: $window.width(),
      height: top
    });
    this.parts.left.update({
      x: 0,
      y: top
    }, {
      width: left,
      height: height
    });
    this.parts.right.update({
      x: left + width,
      y: top
    }, {
      width: $window.width() - (left + width),
      height: height
    });
    this.parts.bottom.update({
      x: 0,
      y: top + height
    }, {
      width: $window.width(),
      height: $window.height() - (top + height)
    });

    //Updates the Rounded corners
    this.parts.leftTop.update({
      x: left,
      y: top
    }, br.leftTop);
    this.parts.rightTop.update({
      x: left + width - br.rightTop,
      y: top
    }, br.rightTop);
    this.parts.leftBottom.update({
      x: left,
      y: top + height - br.leftBottom
    }, br.leftBottom);
    this.parts.rightBottom.update({
      x: left + width - br.rightBottom,
      y: top + height - br.rightBottom
    }, br.rightBottom);
  });

  /**
   A Polling function to check if subject coordinates has changed

   @method pollForSubjectChanges
   **/
  Mask.CompositeMask.method("pollForSubjectChanges", function () {
    if (!flags.lockMaskUpdate) {
      if (currentWizard && currentWizard.currentStep.subject) {
        var subject = $(currentWizard.currentStep.subject);
        if (Subject.obj[0] !== subject[0]) SS.setSubject(subject, true);
      }

      if (Subject.hasChanged()) {
        Subject.updateInfo();
        this.update(Subject.position, Subject.dimension, Subject.borderRadius);
      }
    }
  });

  /**
   A Polling function to check if screen dimension has changed

   @method pollForScreenChanges
   **/
  Mask.CompositeMask.method("pollForScreenChanges", function () {
    if (Screen.hasChanged()) {
      Screen.updateInfo();
      this.update(Subject.position, Subject.dimension, Subject.borderRadius);
    }
  });

  /**
   A part composing the mask

   @class Part
   @@initializer 
   @param {Object} position                              The positioning information 
   @param {Object} dimension                             The dimension information 
   **/
  Mask.CompositeMask.Part = jazz.Class(function (position, dimension) {
    this.position = position;
    this.dimension = dimension;
  }).extending(VisualItem);

  /**
   @@alias Part
   @@to Mask.CompositeMask.Part
   **/
  var Part = Mask.CompositeMask.Part;

  /**
   An object holding positioning information for the mask part

   @@field position
   @type Object
   **/
  Part.field("position", {});

  /**
   An object holding dimension information for the mask part

   @@field position
   @type Object
   **/
  Part.field("dimension", {});

  /**
   Renders the mask part

   @method render
   **/
  Part.method("render", function () {
    this.$el = $("<div>").addClass("sideshow-mask-part").addClass("sideshow-hidden").addClass("sideshow-invisible");
    this.callSuper("render");
  });

  /**
   Updates the dimension and positioning of the subject mask part

   @method update
   @param {Object} position                              The positioning information 
   @param {Object} dimension                             The dimension information 
   **/
  Part.method("update", function (position, dimension) {
    this.position = position;
    this.dimension = dimension;
    this.$el.css("left", position.x).css("top", position.y).css("width", dimension.width).css("height", dimension.height);
  });
  /**
   A corner part composing the mask

   @class CornerPart
   @@initializer 
   @param {Object} position                              The positioning information 
   @param {Object} dimension                             The dimension information 
   **/
  Mask.CompositeMask.CornerPart = jazz.Class().extending(VisualItem);

  /**
   @@alias CornerPart
   @@to Mask.CompositeMask.CornerPart
   **/
  var CornerPart = Mask.CompositeMask.CornerPart;

  /**
   An object holding positioning information for the mask corner part

   @@field position
   @type Object
   **/
  CornerPart.field("position", {});

  /**
   An object holding dimension information for the mask corner part

   @@field position
   @type Object
   **/
  CornerPart.field("dimension", {});

  /**
   An object holding border radius information for the mask corner part

   @@field borderRadius
   @type Object
   **/
  CornerPart.field("borderRadius", 0);

  /**
   Formats the SVG path for the corner part

   @method SVGPathPointsTemplate
   @param {Number} borderRadius                          The corner part border radius
   @static
   **/
  CornerPart.static.SVGPathPointsTemplate = function (borderRadius) {
    return "m 0,0 0," + borderRadius + " C 0," + borderRadius * 0.46 + " " + borderRadius * 0.46 + ",0 " + borderRadius + ",0";
  };

  /**
   Renders the SVG for the corner part

   @method buildSVG
   @param {Number} borderRadius                          The corner part border radius
   @static
   **/
  CornerPart.static.buildSVG = function (borderRadius) {
    function SVG(nodeName) {
      return document.createElementNS("http://www.w3.org/2000/svg", nodeName);
    }

    var bezierPoints = this.SVGPathPointsTemplate(borderRadius);
    var $svg = $(SVG("svg"));
    var $path = $(SVG("path"));

    $path.attr("d", bezierPoints);
    $svg.append($path);

    return $svg[0];
  };

  /**
   Renders the mask corner part

   @method render
   @return {Object}                                      The corner part jQuery wrapped DOM element
   **/
  CornerPart.prototype.render = function () {
    this.$el = $("<div>").addClass("sideshow-mask-corner-part").addClass("sideshow-hidden").addClass("sideshow-invisible");
    this.$el.append(CornerPart.buildSVG(this.borderRadius));
    $body.append(this.$el);
    return this.$el;
  };

  /**
   Updates the positioning and border radius of the mask corner part

   @method update
   @param {Object} position                              The positioning information 
   @param {Object} borderRadius                          The border radius information 
   **/
  CornerPart.prototype.update = function (position, borderRadius) {
    this.$el.css("left", position.x).css("top", position.y).css("width", borderRadius).css("height", borderRadius);

    $(this.$el).find("path").attr("d", CornerPart.SVGPathPointsTemplate(borderRadius));
  };
  /**
   Controls the polling functions needed by Sideshow

   @class Polling
   @static
   **/
  var Polling = {};

  /**
   The polling functions queue

   @@field queue
   @type Object
   @static
   **/
  Polling.queue = [];

  /**
   A flag that controls if the polling is locked

   @@field lock
   @type boolean
   @static
   **/
  Polling.lock = false;

  /**
   Pushes a polling function in the queue

   @method enqueue
   @static
   **/
  Polling.enqueue = function () {
    var firstArg = arguments[0];
    var fn;
    var name = "";

    if (typeof firstArg == "function") fn = firstArg;
    else {
      name = arguments[0];
      fn = arguments[1];
    }

    if (this.getFunctionIndex(fn) < 0 && (name === "" || this.getFunctionIndex(name) < 0)) {
      this.queue.push({
        name: name,
        fn: fn,
        enabled: true
      });
    } else throw new SSException("301", "The function is already in the polling queue.");
  };

  /**
   Removes a polling function from the queue

   @method dequeue
   @static
   **/
  Polling.dequeue = function () {
    this.queue.splice(this.getFunctionIndex(arguments[0]), 1);
  };

  /**
   Enables an specific polling function

   @method enable
   @static
   **/
  Polling.enable = function () {
    this.queue[this.getFunctionIndex(arguments[0])].enabled = true;
  }

  /**
   Disables an specific polling function, but preserving it in the polling queue 

   @method disable
   @static
   **/
  Polling.disable = function () {
    this.queue[this.getFunctionIndex(arguments[0])].enabled = false;
  }

  /**
   Gets the position of a polling function in the queue based on its name or the function itself

   @method getFunctionIndex
   @static
   **/
  Polling.getFunctionIndex = function () {
    var firstArg = arguments[0];

    if (typeof firstArg == "function") return this.queue.map(function (p) {
      return p.fn;
    }).indexOf(firstArg);
    else if (typeof firstArg == "string") return this.queue.map(function (p) {
      return p.name;
    }).indexOf(firstArg);

    throw new SSException("302", "Invalid argument for getFunctionIndex method. Expected a string (the polling function name) or a function (the polling function itself).");
  }

  /**
   Unlocks the polling and starts the checking process

   @method start
   @static
   **/
  Polling.start = function () {
    this.lock = false;
    this.doPolling();
  };

  /**
   Stops the polling process

   @method stop
   @static
   **/
  Polling.stop = function () {
    this.lock = true;
  };

  /**
   Clear the polling queue

   @method clear
   @static
   **/
  Polling.clear = function () {
    var lock = this.lock;

    this.lock = true;
    this.queue = [];
    this.lock = lock;
  };

  /**
   Starts the polling process  

   @method doPolling
   @static
   **/
  Polling.doPolling = function () {
    if (!this.lock) {
      //Using timeout to avoid the queue to not complete in a cycle
      setTimeout(function () {
        for (var fn = 0; fn < Polling.queue.length; fn++) {
          var pollingFunction = Polling.queue[fn];
          pollingFunction.enabled && pollingFunction.fn();
        }
        Polling.doPolling();
      }, pollingDuration);
    }
  };
  /**
   The main menu, where the available wizards are listed

   @class WizardMenu
   @static
   **/
  var WizardMenu = {};

  /**
   Renders the wizard menu

   @method render
   @param {Array} wizards                                The wizards list
   @static
   **/
  WizardMenu.render = function (wizards) {
    var $menu = $("<div>").addClass("sideshow-wizard-menu");
    this.$el = $menu;
    var $title = $("<h1>").addClass("sideshow-wizard-menu-title");
    $menu.append($title);

    if (wizards.length > 0) {
      var $wizardsList = $("<ul>");

      //Extracting this function to avoid the JSHint warning W083

      function setClick($wiz, wizard) {
        $wiz.click(function () {
          WizardMenu.hide(function () {
            wizard.prepareAndPlay();
          });
        });
      }

      for (var w = 0; w < wizards.length; w++) {
        var wiz = wizards[w];
        var $wiz = $("<li>");
        var $wizTitle = $("<h2>").text(wiz.title);

        var description = wiz.description;
        description.length > 100 && (description = description.substr(0, 100) + "...");

        var $wizDescription = $("<span>").addClass("sideshow-wizard-menu-item-description").text(description);
        var $wizEstimatedTime = $("<span>").addClass("sideshow-wizard-menu-item-estimated-time").text(wiz.estimatedTime);
        $wiz.append($wizEstimatedTime, $wizTitle, $wizDescription);
        $wizardsList.append($wiz);

        setClick($wiz, wiz);
      }
      $menu.append($wizardsList);
    } else {
      $("<div>").addClass("sideshow-no-wizards-available").text(getString(strings.noAvailableWizards)).appendTo($menu);
    }

    $body.append($menu);
  };

  /**
   Shows the wizard menu

   @method show
   @param {Array} wizards                                The wizards list
   @static
   **/
  WizardMenu.show = function (wizards, title) {
    if (wizards.length == 1 && SS.config.autoSkipIntro) wizards[0].prepareAndPlay();
    else {
      SS.setEmptySubject();
      Mask.CompositeMask.singleInstance.update(Subject.position, Subject.dimension, Subject.borderRadius);
      Mask.CompositeMask.singleInstance.fadeIn();

      WizardMenu.render(wizards);

      if (title) this.setTitle(title);
      else this.setTitle(getString(strings.availableWizards));
    }
  };

  /**
   Hides the wizard menu

   @method hide
   @param {Function} callback                            The callback to be called after hiding the menu
   @static
   **/
  WizardMenu.hide = function (callback) {
    var menu = this,
        $el = menu.$el;

    $el && $el.addClass("sideshow-menu-closed");
    setTimeout(function () {
      $el && $el.hide();
      if (callback) callback();
    }, longAnimationDuration);
  };

  WizardMenu.setTitle = function (title) {
    this.$el.find(".sideshow-wizard-menu-title").text(title);
  };

  /**
   Initializes Sideshow

   @method init
   @static
   **/
  SS.init = function () {
    $window = $(global);
    $document = $(global.document);
    $body = $("body", global.document);
    registerGlobalHotkeys();
    Polling.start();
    Mask.CompositeMask.singleInstance.init();
    flags.lockMaskUpdate = true;
    Mask.CompositeMask.singleInstance.render();
  };

  /**
   Receives a function with just a multiline comment as body and converts to a here-document string

   @method heredoc
   @param {Function}                                     A function without body but a multiline comment
   @return {String}                                      A multiline string
   @static
   **/
  SS.heredoc = function (fn) {
    return fn.toString().match(/[^]*\/\*([^]*)\*\/\}$/)[1];
  }

  /**
   Stops and Closes Sideshow

   @method closes
   @static
   **/
  SS.close = function () {
    if (!currentWizard) WizardMenu.hide();

    DetailsPanel.singleInstance.fadeOut();

    this.CloseButton.singleInstance.fadeOut();
    Arrows.fadeOut();

    setTimeout(function () {
      if (Mask.CompositeMask.singleInstance.status === AnimationStatus.VISIBLE || Mask.CompositeMask.singleInstance.status === AnimationStatus.FADING_IN) Mask.CompositeMask.singleInstance.fadeOut();

      Mask.SubjectMask.singleInstance.fadeOut();

    }, longAnimationDuration);

    removeDOMGarbage();
    Polling.clear();
    SS.ControlVariables.clear();
    unregisterInnerHotkeys();
    currentWizard = null;
    flags.running = false;
  };

  /**
   @deprecated
   @method runWizard
   @static
   **/
  SS.runWizard = function (name) {
    showDeprecationWarning("This method is deprecated and will be removed until the next major version of Sideshow.");

    var wiz = wizards.filter(function (w) {
      return w.name === name
    })[0];
    currentWizard = wiz;
    if (wiz) {
      if (wiz.isEligible()) wiz.play();
      else if (wiz.preparation) wiz.preparation(function () {
        setTimeout(function () {
          wiz.play();
        }, 1000);
      });
      else throw new SSException("204", "This wizard hasn't preparation.");
    } else throw new SSException("205", "There's no wizard with name " + name + ".");
  };

  SS.gotoStep = function () {
    var firstArg = arguments[0],
        steps = currentWizard._storyline.steps,
        destination;

    flags.skippingStep = true;

    //First argument is the step position (1-based)
    if (typeof firstArg == "number") {
      if (firstArg <= steps.length) destination = steps[firstArg - 1];
      else throw new SSException("401", "There's no step in the storyline with position " + firstArg + ".");
    } //First argument is the step name
    else if (typeof firstArg == "string") {
      destination = steps.filter(function (i) {
        return i.name === firstArg;
      })[0];

      if (!destination) throw new SSException("401", "There's no step in the storyline with name " + firstArg + ".");
    }
    setTimeout(function () {
      currentWizard.next(null, destination);
    }, 100);
  };

  /**
   A trick to use the composite mask to simulate the behavior of a solid mask, setting an empty subject

   @method setEmptySubject
   @static
   **/
  SS.setEmptySubject = function () {
    flags.lockMaskUpdate = true;
    Subject.obj = null;
    Subject.updateInfo({
      dimension: {
        width: 0,
        height: 0
      },
      position: {
        x: 0,
        y: 0
      },
      borderRadius: {
        leftTop: 0,
        rightTop: 0,
        leftBottom: 0,
        rightBottom: 0
      }
    });
  };

  /**
   Sets the current subject

   @method setSubject
   @param {Object} subj
   @static
   **/
  SS.setSubject = function (subj, subjectChanged) {
    if (subj.constructor === String) subj = $(subj);

    if (subj instanceof $ && subj.length > 0) {
      if (subj.length === 1) {
        Subject.obj = subj;
        Subject.updateInfo();
        flags.lockMaskUpdate = false;
      } else throw new SSException("101", "A subject must have only one element. Multiple elements by step will be supported in future versions of Sideshow.");
    }
    else if (subjectChanged) SS.setEmptySubject();
    else throw new SSException("100", "Invalid subject.");
  };

  /**
   Registers a wizard

   @method registerWizard
   @param {Object} wizardConfig                          
   @return {Object}                                      The wizard instance
   @static
   **/
  SS.registerWizard = function (wizardConfig) {
    var wiz = Wizard.build(wizardConfig);
    wizards.push(wiz);
    return wiz;
  };

  /**
   Registers a wizard

   @method registerWizard
   @param {boolean} onlyNew                              Checks only recently added wizards
   @return {Array}                                       The eligible wizards list
   @static
   **/
  SS.getElegibleWizards = function (onlyNew) {
    var eligibleWizards = [];
    var somethingNew = false;
    for (var w = 0; w < wizards.length; w++) {
      var wiz = wizards[w];
      if (wiz.isEligible()) {
        if (!wiz.isAlreadyWatched()) somethingNew = true;
        eligibleWizards.push(wiz);
      }
    }

    return !onlyNew || somethingNew ? eligibleWizards : [];
  };

  /**
   Checks if there are eligible wizards, if exists, shows the wizard menu   

   @method showWizardsList
   @param {boolean} onlyNew                              Checks only recently added wizards
   @return {boolean}                                     Returns a boolean indicating whether there is some wizard available
   @static
   **/
  SS.showWizardsList = function () {
    var firstArg = arguments[0];
    var title = arguments[1];
    var onlyNew = typeof firstArg == "boolean" ? false : firstArg;
    var wizards = firstArg instanceof Array ? firstArg : this.getElegibleWizards(onlyNew);

    WizardMenu.show(wizards, title);

    return wizards.length > 0;
  };

  /**
   Shows a list with the related wizards  

   @method showRelatedWizardsList
   @param {Object} completedWizard                       The recently completed wizard
   @return {boolean}                                     Returns a boolean indicating whether there is some related wizard available
   @static
   **/
  SS.showRelatedWizardsList = function (completedWizard) {
    var relatedWizardsNames = completedWizard.relatedWizards;
    if (!relatedWizardsNames) return false;

    //Gets only related tutorials which are eligible or have a preparation function
    var relatedWizards = wizards.filter(function (w) {
      return relatedWizardsNames.indexOf(w.name) > -1 && (w.isEligible() || w.preparation);
    });
    if (relatedWizards.length == 0) return false;

    Polling.clear();
    SS.ControlVariables.clear();
    SS.showWizardsList(relatedWizards, getString(strings.relatedWizards));

    return true;
  };

  /**
   The close button for the wizard

   @class CloseButton
   @@singleton
   @extends FadableItem
   **/
  SS.CloseButton = jazz.Class().extending(FadableItem).singleton;

  /**
   Renders the close button

   @method render
   **/
  SS.CloseButton.method("render", function () {
    this.$el = $("<button>").addClass("sideshow-close-button").text(getString(strings.close));
    this.$el.click(function () {
      SS.close();
    });
    this.callSuper("render");
  });

  /**
   Starts Sideshow

   @method start
   @param {Object} config                                The config object for Sideshow
   **/
  SS.start = function (config) {
    config = config || {};

    if (!flags.running) {
      var onlyNew = "onlyNew" in config && !! config.onlyNew;
      var listAll = "listAll" in config && !! config.listAll;
      var wizardName = config.wizardName;

      if (listAll) SS.showWizardsList(wizards.filter(function (w) {
        return w.isEligible() || w.preparation;
      }));
      else if (wizardName) {
        var wizard = wizards.filter(function (w) {
          return w.name === wizardName;
        })[0];

        if (!wizard) throw new SSException("205", "There's no wizard with name '" + wizardName + "'.");

        wizard.prepareAndPlay();
      }
      else SS.showWizardsList(onlyNew);

      this.CloseButton.singleInstance.render();
      this.CloseButton.singleInstance.fadeIn();

      registerInnerHotkeys();
      flags.running = true;

      Polling.enqueue("check_composite_mask_screen_changes", function () {
        Mask.CompositeMask.singleInstance.pollForScreenChanges();
      });
    }
  };

  //Tries to register the Global Access Point
  if (global[globalObjectName] === undefined) {
    global[globalObjectName] = SS;
  } else throw new SSException("1", "The global access point \"Sideshow\" is already being used.");
});

})(this, jQuery, Jazz, Markdown);