/* Pages navigation.
 * 
 * Copyright (c) 2010 Raphaël Bois
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software  and associated  documentation  files (the  "Software"), to
 * deal in the Software without  restriction, including  without limitation the
 * rights to use, copy, modify, merge,  publish, distribute, sublicense, and/or
 * sell copies of the Software,  and to permit persons  to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice  and this permission notice  shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED  "AS IS", WITHOUT WARRANTY  OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING  BUT NOT  LIMITED TO THE  WARRANTIES OF  MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR  COPYRIGHT  HOLDERS BE  LIABLE FOR  ANY CLAIM,  DAMAGES  OR OTHER
 * LIABILITY,  WHETHER IN AN  ACTION OF  CONTRACT, TORT  OR OTHERWISE,  ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 * 
 * page-nav.js
 */

try {
  if (!MooTools || MooTools.version != '1.2.4')
    throw "!!!";
} catch (e) { throw "This module requires MooTools 1.2.4"; }

var PageNav = new Class({
  Implements: [Options, Events],
  Binds: [
    'pageAdd', 'pageMerge', 'pageGet', 'pageQueueRequest', 'pageShow', 'pageLoad', 'onPageLoaded',
    'scanList', 'scanEntry', 'scanSubEntry',
    'activeMenu', 'activePage', 'navUpdate'
  ],
  options: {
    'loading': null, /* Object to show when loading pages. */
    'permalink': null, /* anchor presenting the permalink of the active page. */
    'logoholder': null, /* Element that display the logo as a background. */
    'dissolveProjects': false
  },
  initialize: function (menu_root, container, options) {
    this.setOptions(options);
    this.mnuRoot = $(menu_root);
    this.container = $(container);
    this.logoHolder = $(this.options.logoHolder);
    this.dissolveProjects = this.options.dissolveProjects;
    this.ulOver = null;
    this.ulOutTimeout = null;
    this.activeStack = [];
    this.pageActive = 0;
    this.pageFading = 0;
    this.showNext = 0;
    this.requestQueue = [];
    this.requestPage = 0;
    this.showOnLoad = 0;
    this.pages = {};
    this.pageReq = new Request.JSON({
      'method': 'post',
      'url': 'page.php',
      'link': 'ignore',
      'nocache': 'true' /* XXX ??? Should be boolean ??? */
    });
    var f = function () {
      this.requestPage = this.requestQueue.shift();
      this.pageLoad(this.requestPage);
    }.bind(this);
    this.pageReq.addEvents({
      'success': function (data, txt) {
        this.onPageLoaded(data, txt);
        this.requestPage = this.requestQueue.shift();
        this.pageLoad(this.requestPage);
      }.bind(this),
      'cancel': f,
      'failure': f,
    });
    if (this.mnuRoot) {
      this.scanList(this.mnuRoot);
    }
    this.nextOver = new Element('a', {'href':'#', 'class': 'nav-over nav-next'}).inject(this.container, 'top');
    this.next = [
      /* new Element('a', {'href':'#', 'class': 'page-nav-next'}).inject(this.container, 'top'), */
      this.nextOver
    ];
    /* this.prev = (new Element('a', {'href':'#', 'class': 'page-nav-prev'})).inject(this.container, 'top'); */
    this.prevOver = new Element('a', {'href':'#', 'class': 'nav-over nav-prev'}).inject(this.container, 'top');
    this.prev = [
      /* new Element('a', {'href':'#', 'class': 'page-nav-prev'}).inject(this.container, 'top'), */
      this.prevOver
    ];
    this.prev.each(function (e) {
      e.addEvent('click', function (e) {
        var p = this.pageGet(this.pageActive);
        if (p && p.prevPage > 0) {
          this.activePage(p.prevPage, true);
        }
        e.stop();
      }.bind(this));
    }.bind(this));
    this.next.each(function (e) {
      e.addEvent('click', function (e) {
        var p = this.pageGet(this.pageActive);
        if (p && p.nextPage > 0) {
          this.activePage(p.nextPage, true);
        }
        e.stop();
      }.bind(this));
    }.bind(this));
    this.dock = new Element('div', {styles:{'color':'#fff', 'position':'fixed', 'left':10, 'top':10}});
    this.dock.inject(this.container);
    
    [this.nextOver, this.prevOver].each(function (e) {
      var m = e.set('morph', {'duration':200, 'fps':30, 'link': 'cancel',
        onComplete: function () {
          if (this.getStyle('opacity') == 0) {
            this.setStyle('visibility', 'hidden');
          } else {
            this.setStyle('visibility', 'visible');
          }
        }.bind(e),
        onStart: function () {
          this.setStyle('visibility', 'visible');
        }.bind(e)
      });
      m.set('opacity', 0.0);
    });

    this.container.addEvent('mousemove', function (event) {
      var p = this.container.getPosition();
      var x = this.container.getSize().x;
      if (event.page.x - p.x < 0.33 * x) {
        this.prevOver.morph({'opacity': 1.0});
      } else if (this.prevOver.getStyle('visibility') == 'visible') {
        this.prevOver.morph({'opacity': 0.0});
      }
      /* this.nextOver.setStyle('visibility', event.page.x - p.x > 0.67 * x ? 'visible' : 'hidden'); */
      if (event.page.x - p.x > 0.67 * x) {
        this.nextOver.morph({'opacity': 1.0});
      } else if (this.nextOver.getStyle('visibility') == 'visible') {
        this.nextOver.morph({'opacity': 0.0});
      }
      var y = Math.max(this.prevOver.getSize().y, this.nextOver.getSize().y);
      /* this.dock.set('html', 'X='+event.page.x + ' - Y='+event.page.y); */
      var pos = Math.max(event.page.y - p.y - y / 2);
      this.prevOver.setStyle('top', pos);
      this.nextOver.setStyle('top', pos);
    }.bind(this));
    this.container.addEvent('mouseleave', function () {
      if (this.prevOver.getStyle('visibility') == 'visible') {
        this.prevOver.morph({'opacity':0.0});
      }
      if (this.nextOver.getStyle('visibility') == 'visible') {
        this.nextOver.morph({'opacity':0.0});
      }
    }.bind(this));
    if (this.pageActive) { /* Get page infos. */
      this.pageLoad.delay(50, this, [this.pageActive, true]);
    }
  },
  pageAdd: function (page, infos) {
    if (!infos) infos = {};
    var pinfos = $extend({'page':page,'prevPage':0,'nextPage':0}, infos);
    this.pages['p'+page] = pinfos
    return pinfos;
  },
  pageMerge: function (page, infos) {
    var pinfos = this.pageGet(page);
    if (!pinfos) {
      pinfos = this.pageAdd(page, infos);
    } else if (infos) {
      $extend(pinfos, infos);
    }
    return pinfos;
  },
  pageGet: function (page) {
    return this.pages['p'+page];
  },

  scanList: function (ul, is_subul) {
    ul = $(ul);
    if (!ul) { return; }
    if (ul.hasClass('expandable')) {
      ul.set('reveal', {
        'fps':25,
        'link':'chain',
        'duration':300,
        'transition': 'cubic:out'
      });
      ul.set('morph', {
        'fps':25,
        'link':'chain',
        'duration':300,
        'transition':'linear'
      });
      if (ul.hasClass('projects')) {
        ul.addEvent('mousemove', function () {
          if (!this.t.dissolveProjects) return;
          this.t.ulOver = this.ul;
          if (this.t.ulOutTimeout && this.t.ulOutTimeout[0] == this.ul) {
            $clear(this.t.ulOutTimeout[1]);
          }
        }.bind({ul:ul, t:this}));
        ul.addEvent('mouseleave', function () {
          if (!this.t.dissolveProjects) return;
          if (this.t.ulOver == this.ul) {
            this.t.ulOver = null;
          }
          this.t.ulOutTimeout = [this.ul, this.ul.dissolve.delay(this.t.dissolveProjects, this.ul)];
        }.bind({ul:ul, t:this}));
        var r = ul.get('reveal');
        r.addEvent('show', function () {
          if (this.t.dissolveProjects && (this.ul != this.t.ulOver)) {
            if (this.t.ulOutTimeout && this.t.ulOutTimeout[0] == this.ul) {
              $clear(this.t.ulOutTimeout[1]);
            }
            this.t.ulOutTimeout = [this.ul, this.ul.dissolve.delay(this.t.dissolveProjects, this.ul)];
          }
        }.bind({ul:ul, t:this}));
        r.addEvent('hide', function () {
          if (this.t.ulOutTimeout && this.t.ulOutTimeout[0] == this.ul) {
            this.t.ulOutTimeout = null;
          }
        }.bind({ul:ul, t:this}));
        if (this.dissolveProjects && ul.getStyle('visibility') == 'visible') {
          ul.fireEvent('mouseleave');
        }
      }
    }
    var lis = ul.getChildren('li');
    if (lis && lis.length) {
      if (is_subul) {
        lis.each(this.scanSubEntry);
      } else {
        lis.each(this.scanEntry);
      }
    }
  },
  scanSubEntry: function (li, idx, array) {
    this.scanEntry(li, idx, array, true);
  },
  scanEntry: function (li, idx, array, is_subul) {
    li = $(li); /* Required in old IE */
    if (li.hasClass('active')) {
      this.activeStack.push(li);
    }
    var subul = li.getFirst('ul');
    var a = li.getFirst('div');
    if (a) a = a.getFirst('a');
    if (!a) { return; }
    if (a.hasClass('directlink')) {
      a.set('target', '_blank');
      return;
    }
    if (!is_subul && subul && this.dissolveProjects) {
      a.addEvent('mousemove', function () {
        if (this.t.activeStack.length > 0 && this.li == this.t.activeStack[0]) {
          /* ul.setStyle('visibility', 'visible'); */
          subul.reveal();
          if (this.t.ulOutTimeout && this.t.ulOutTimeout[0] == subul) {
            $clear(this.t.ulOutTimeout[1]);
          }
          this.t.ulOutTimeout = [subul, subul.dissolve.delay(this.t.dissolveProjects, subul)];
        }
      }.bind({li:li, t:this}));
    }
    var page = a.get('href').replace('?page=','').toInt();
    if (a.hasClass('active')) {
      this.pageActive = page;
    }
    if (!li.hasClass('project') || !li.getFirst('ul')) {
      this.pageAdd(page, {'li':li});
    }
    a.addEvent('click', function (e) {
      this.n.activePage(this.page);
      if (e) e.stop();
      return false; /* Prevents link from being activated. */
    }.bind({n:this, page:page}));
    this.scanList(subul, true);
  },

  activeMenu: function (page, noshow) {
    if (page == this.activePage) return;
    var pinfos = this.pageGet(page) || {};
    var li = pinfos.li;
    if (li) {
      var a = li.getFirst('div').getFirst('a');
      this.activeStack.getLast().getFirst('div').getFirst('a').removeClass('active');
      a.addClass('active');
      var newStack = [li];
      var ul = li.getParent();
      while (ul && ul != this.mnuRoot) {
        li = ul.getParent();
        newStack.unshift(li);
        ul = li.getParent();
      }
      var i = 0;
      for (; i < this.activeStack.length || i < newStack.length; i++) {
        if (i < newStack.length) {
          if (i < this.activeStack.length && this.activeStack[i] == newStack[i]) {
            if (this.dissolveProjects) {
              var u = this.activeStack[i].getFirst('ul');
              if (u.hasClass('expandable') && u.hasClass('projects') && u.getStyle('display', 'none')) {
                if ((i > 0 || !noshow)) {
                  u.reveal();
                }
              }
            }
            continue;
          }
          /* newStack[i].addClass('active'); */
          ul = newStack[i].getFirst('ul');
          if (ul && ul.hasClass('expandable') && (i > 0 || !noshow)) {
            ul.setStyle('visibility','visible');
            ul.reveal();
          }
        }
        if (i < this.activeStack.length) {
          ul = this.activeStack[i].getFirst('ul');
          if (ul && ul.hasClass('expandable')) { /* Override default CSS behaviour. */
            if (!this.dissolveProjects || !ul.hasClass('projects') || this.ulOver == ul || (this.ulOutTimeout && this.ulOutTimeout[0] == ul)) {
              if ((i != 0 || !noshow) && (ul.getStyle('display') != 'none' || ul.getStyle('visibility') != 'hidden')) {
                if (this.ulOutTimeout && this.ulOutTimeout[0] == ul) {
                  $clear(this.ulOutTimeout[1]);
                }
                ul.setStyles({
                  'display': 'block',
                  'visibility': 'visible'
                });
                ul.dissolve();
              }
            }
          }
          this.activeStack[i].removeClass('active'); /* May remain from the original init. */
        }
      }
      this.activeStack = newStack;
    }
  },
  activePage: function (page, noshow) {
    if (page == this.activePage) return;
    this.activeMenu(page, noshow);
    var pinfos = this.pageGet(page);
    var p = $('page-'+page);
    if (!p) {
      this.showOnLoad = page;
      this.pageQueueRequest(page, true);
    } else {
      this.showOnLoad = 0; /* Prevent any previously loaded page to show after that */
      this.pageQueueRequest(0, true); /* clear pending requests. */
      this.pageShow(page);
    }
  },
  pageShow: function (page) {
    if (page == this.pageActive) return;
    if (this.pageFading > 0) {
      this.showNext = page;
      return;
    }
    this.pageFading = this.pageActive;
    this.pageActive = page;
    var op = $('page-'+this.pageFading);
    var p = $('page-'+page);
    var pinfos = this.pageGet(page);
    if (op) {
      op.set('id', 'oldpage-'+this.pageFading);
      op.setStyle('z-index', 1);
    }
    p.setStyles({'display': 'block', 'visibility': 'visible', 'z-index': 2, 'opacity':0.0});
    if (!pinfos.morphIsSet) {
      var m = p.set('morph', {'fps':25,'duration':500, 'transition':'linear'}).get('morph');
      m.addEvent('complete', function () {
        if (this.pageFading > 0) {
          var p = $('oldpage-'+this.pageFading);
          if (p && this.pageFading != this.showNext) {
            /* p.setStyles({'display': 'none', 'visibility':'hidden', 'z-index': 0}); */
            p.dispose().destroy();
            pinfos = this.pageGet(this.pageFading);
            pinfos.morphIsSet = false;
          }
        }
        this.pageFading = -1;
        if (this.showNext > 0) {
          var n = this.showNext;
          this.showNext = -1;
          this.pageShow(n);
        }
      }.bind(this));
      pinfos.morphIsSet = true;
    }
    p.morph({'opacity':[0.0,1.0]});
    if (pinfos.logo && this.logoHolder) {
      this.logoHolder.setStyle('background-image', "url('"+pinfos.logo+"')");
    }
    this.navUpdate();
  },
  pageQueueRequest: function (page, clearQueue) {
    if (clearQueue) this.requestQueue.empty();
    if (!page) return;
    this.requestQueue.push(page);
    if (!this.requestPage) {
      this.requestPage = this.requestQueue.shift();
      this.pageLoad(this.requestPage);
    }
  },
  navUpdate: function () {
    var pi = this.pageGet(this.pageActive);
    this.prev.each(function (e) {
      e.setStyle('display', this.prevPage > 0 ? 'block' : 'none');
    }.bind(pi));
    this.next.each(function (e) {
      e.setStyle('display', pi.nextPage > 0 ? 'block' : 'none');
    }.bind(pi));
  },
  onPageLoaded: function (infos, raw) {
    if (!infos || !infos.page) return; /* Must be a valid page. */
    if ('contents' in infos) {
      if (this.container) {
        var div = new Element('div', {
          'id':'page-'+infos.page,
          'class': 'page',
          'html':infos.contents,
          'styles': {'visibility': 'hidden', 'z-index': 0}
        }).inject(this.container);
      }
      delete infos.contents;
    }
    this.pageMerge(infos.page, infos);
    if (this.showOnLoad == infos.page) {
      this.showOnLoad = 0;
      this.pageShow(infos.page);
    } else if (this.pageActive == infos.page) { /* Occurs for the first page. */
      this.navUpdate();
    }
  },
  pageLoad: function (page, infosOnly) {
    if (!page) return;
    var d = {
      'action': (infosOnly ? 'page-nav-info' : 'page-full'),
      'page': page
    };
    this.pageReq.send({data:d});
  }
});

var pageNav = null;
window.addEvent('domready', function () {
  pageNav = new PageNav('menu-cat-0', 'pages', {'logoHolder': 'menu', 'dissolveProjects': PAGE_NAV_DISSOLVE_PROJECTS});
});


