/**
 * JKwebPageChange - Class
 * 
 * @author Pascal Josephy
 * @copyright JKweb (http://jkweb.ch)
 * @licence Free for use and modifying, but please let the credits untouched!
 * @version 1.0 (2012/01/08)
 * 
 * The JKwebPageChange class provides a simple, modern, fast and lightweight
 * method to dynamicly load contents in background and blend them in with a
 * custum effect. The script only works in newer browsers and it's usable
 * without any other special adjustments. Just include and enjoy.
 * 
 * @notice This file cointains the Method toDOM for the String prototype,
 *         credits to Yannick Croissant.
 * 
 */

String.implement({

  toDOM : function() {
    var wrapper = this.test('^<the|^<tf|^<tb|^<colg|^<ca')
        && [ '<table>', '</table>', 1 ]
        || this.test('^<col')
        && [ '<table><colgroup>', '</colgroup><tbody></tbody></table>',
            2 ] || this.test('^<tr')
        && [ '<table><tbody>', '</tbody></table>', 2 ]
        || this.test('^<th|^<td')
        && [ '<table><tbody><tr>', '</tr></tbody></table>', 3 ]
        || this.test('^<li') && [ '<ul>', '</ul>', 1 ]
        || this.test('^<dt|^<dd') && [ '<dl>', '</dl>', 1 ]
        || this.test('^<le') && [ '<fieldset>', '</fieldset>', 1 ]
        || this.test('^<opt')
        && [ '<select multiple="multiple">', '</select>', 1 ]
        || [ '', '', 0 ];
    var el = new Element('div', {
      html : wrapper[0] + this + wrapper[1]
    }).getChildren();
    while (wrapper[2]--)
      el = el[0].getChildren();
    return el;
  }

});

var JKwebPageChange = new Class();

var JKwebPageChangeFx = {};

/**
 * This demo shows how to implement a custom fx for the pagechange.
 */
JKwebPageChangeFx['Demo'] = {
  /**
   * Sould return the fx object.
   */
  getFx : function(dn) {
    var fx = new Fx.Morph(dn.animatedElement);
    return fx;
  },
  /**
   * sould cause the animated content to slide out...
   */
  fxOut : function(dn) {
    dn.fx.start({
      'opacity' : 0
    });
  },
  /**
   * sould cuase the animated countent to slide in...
   */
  fxIn : function(dn) {
    dn.fx.start({
      'opacity' : 1
    });
  },
  /**
   * returns true if the content has finished sliding out...
   */
  hasFinishedOut : function(dn) {
    return $(dn.animatedElement).getStyle("opacity") == 0;
  },
  /**
   * gets called as soon as the new content is included. the new content is
   * available as dn.loadedContent, the old content is dn.contentElement. the
   * fx has to call after it has done its functions dn.hideOldContent() to
   * destroy the old content.
   */
  contentChangeFx : function(dn) {
    dn.hideOldContent();
  }
};
/**
 * A slide transition
 */
JKwebPageChangeFx['Slide'] = {
  getFx : function(dn) {
    var fx = new Fx.Slide(dn.animatedElement);
    return fx;
  },
  fxOut : function(dn) {
    dn.fx.slideOut();
  },
  fxIn : function(dn) {
    dn.fx.slideIn();
  },
  hasFinishedOut : function(dn) {
    return !dn.fx.open;
  },
  contentChangeFx : function(dn) {
    dn.hideOldContent();
  }
};
/**
 * A transition with fade transition
 */
JKwebPageChangeFx['Opacity'] = {
  getFx : function(dn) {
    var fx = new Fx.Morph(dn.animatedElement);
    return fx;
  },
  fxOut : function(dn) {
    dn.fx.start({
      'opacity' : 0
    });
  },
  fxIn : function(dn) {
    dn.fx.start({
      'opacity' : 1
    });
  },
  hasFinishedOut : function(dn) {
    return $(dn.animatedElement).getStyle("opacity") == 0;
  },
  contentChangeFx : function(dn) {
    /*
     * el.setStyle("overflow", "hidden");
     * 
     * var tween = new Fx.Tween(el, { duration : 'short', transition :
     * 'linear', link : 'cancel', property : 'height' });
     * tween.addEvent('complete', function() { this.setStyle("height", "");
     * }.bind(el)); tween.start(from, to);
     * 
     */
    dn.hideOldContent();
  }
};

/**
 * The JKwebPageChange class
 */
var JKwebPageChange = new Class({
  Implements : [ Options ],

  options : {
    duration : 500,
    transition : Fx.Transitions["Cubic"]["easeOut"],
    fx : JKwebPageChangeFx.Slide,
    spinnerElement : false,
    scale : false,
    tempContentId : 'temp-content'
  },
  /**
   * This method will initialize the navigation.
   * 
   * @param elements
   *            The element which should be used as the links for the
   *            pagechange event.
   * @param content
   *            The id of the content element which should be replaced.
   * @param animated
   *            The id of the element which should be animated (can be the
   *            same as the content element).
   * 
   */
  initialize : function(elements, content, animated, options) {
    this.setOptions(options);

    // If Browser is "too old", return...
    if (!history.pushState)
      return;

    // setting the content elements and the content animation element...
    this.contentElement = $(content);
    this.animatedElement = $(animated);

    // setting up the animation...
    this.fx = this.options.fx.getFx(this);

    this.fx.addEvent('complete', function() {
      if (this.options.fx.hasFinishedOut(this)) {
        this.finishedFxOut();
      } else {
        this.finishedFxIn();
      }
    }.bind(this));
    this.fx.options.duration = this.options.duration;
    this.fx.options.transition = this.options.transition;

    this.performingAction = false;

    // Cause a popstate event to be ignored...
    this.started = false;

    // Make sure that the spinner exists...
    if (this.options.spinnerElement) {
      if (!$(this.options.spinnerElement))
        this.options.spinnerElement = false;
    }

    // For the safari browser
    window.addEvent('unload', function() {
      this.started = false;
    }.bind(this));

    // Adds an event listener for the popstate event, fired when the user
    // presses the back button.
    window.addEventListener('popstate', function(event) {
      var href = location.href;
      // if the system has startet, cause a "loadContent" request..
      if (this.started)
        // If it is "our" event (and not just another)
        if (event && event.state && event.state.pagechange)
          this.loadContent(href, false);

    }.bind(this));

    history.replaceState({
      pagechange : true
    }, document.title, location.href);

    // Looping through all the links...
    $$(elements).each(function(el) {
      // ...and add an event listener
      el.addEvent('click', function(e) {
        // Make sure that from now going on popevents will cause a
        // "loadContent" request...
        this.started = true;

        e.stop();
        // determing the "normal" link destination
        var href = e.target.get('href');

        // load the content
        this.loadContent(href, true);

      }.bind(this));
    }.bind(this));

  },
  /**
   * 
   */
  loadContent : function(ressource, pushState) {
    if (ressource == null)
      return;

    this.pushState = pushState;

    if (this.performingAction)
      return;
    this.performingAction = true;

    // Performing animation for hiding content
    this.options.fx.fxOut(this);

    // If option is set, show spinner
    if (this.options.spinnerElement) {
      spinner = $(this.options.spinnerElement);
      spinner.setStyles({
        opacity : 0,
        display : 'block'
      });
      spinner.set('tween', {
        duration : this.options.duration,
        link : 'cancel'
      });
      spinner.fade('in');
    }

    this.destination = ressource;

    // Setting up dynamic request
    // IMPORTANT NOTE: you have to change the mootools Request.HTML to
    // return also the head part!
    var req = new Request({
      url : ressource,
      method : 'get',
      onRequest : function() {
        // TODO
      }.bind(this),
      onSuccess : function(responseHTML) {
        var html = responseHTML.toDOM();
        html
            .each(function(el) {
              /*
               * Search in the answer elements (el) the title and
               * the content and put them into the this object.
               */
              if (el.get("tag") == "title") {
                this.destinationTitle = el.get("html");
              } else if ($(el).getElement("title")) {
                this.destinationTitle = $(el).getElement(
                    "title").get("html");
              } else if ($(el).get("id") == this.contentElement
                  .get("id")) {
                this.loadedContent = $(el);
              } else if ($(el).getElement(
                  "#" + this.contentElement.get("id"))) {
                this.loadedContent = $(el).getElement(
                    "#" + this.contentElement.get("id"));
              }
              /*
               * search for scripts
               */
              if (el.get("tag") == "script") {
                alert(el.get("html"));
              } else if ($(el).getElement("script")) {
                alert($(el).getElement("script").get("html"));
              }
            }.bind(this));

        // remove domready
        window.removeEvents("domready");

        // If no content was found, just redirect to the page to prevent
        // from wrong display...
        if (!this.loadedContent) {
          location.href = this.destination;
        }

        // If no title was foud, set it to the current...
        if (!this.destinationTitle) {
          this.destinationTitle = document.title;
        }

        // Making the browser to update the url...
        if (this.pushState) {
          history.pushState({
            pagechange : true
          }, this.destinationTitle, this.destination);
        }

        // Finally set the documents title to the new one...
        if (this.destinationTitle) {
          document.title = this.destinationTitle;
        }

        // Include the new content...
        this.includeNewContent();
      }.bind(this),
      onFailure : function(a) {
        if (this.destination != null) {
          location.href = this.destination;
        }
      }.bind(this)
    });
    req.send();
  },
  /**
   * This method is performed AFTER the event has finished AND the request is
   * done. It changes the content and animates it to "slide" in.
   */
  includeNewContent : function() {
    // Make sure content is loaded and effect has finished
    if (!this.loadedContent || !this.fxOutFinished) {
      return;
    }
    this.fxOutFinished = false;

    // Change the content
    var fromHeight = this.contentElement.getSize().y;

    this.loadedContent.set("id", this.options.tempContentId);
    // this.contentElement.set("html", this.loadedContent.get("html"));
    this.contentElement.grab(this.loadedContent, 'after');

    // fire domready event
    window.fireEvent("pagechanged", this.destination);
    window.fireEvent("domredy");

    this.options.fx.contentChangeFx(this);
  },
  /**
   * This method is performed AFTER the event has finished AND the request is
   * done. It changes the content and animates it to "slide" in.
   */
  hideOldContent : function() {
    // Make sure content is loaded
    if (!this.loadedContent) {
      return;
    }

    // Hide old content
    var contentId = this.contentElement.get("id");
    this.contentElement.dispose();
    this.contentElement = this.loadedContent;
    this.contentElement.set("id", contentId);

    // Remove
    this.loadedContent = false;
    this.destination = false;
    this.destinationTitle = false;

    // Animate in
    this.options.fx.fxIn(this);

    // If option is set, hide spinner
    if (this.options.spinnerElement) {
      spinner = $(this.options.spinnerElement);
      spinner.setStyles({
        opacity : 1,
        display : 'block'
      });
      spinner.fade('out');
    }
  },
  /**
   * This method is called after the slide in animation has successfully
   * completed. The fx is responsable to call this method!
   */
  finishedFxIn : function() {
    // Allowing next action...
    this.performingAction = false;

  },
  /**
   * This method is called by the fx after the slide out animation has
   * completed. It causes the content to be replaced by the new one.
   */
  finishedFxOut : function() {
    this.fxOutFinished = true;
    this.includeNewContent();
  }
});

