/**
 * Third Angle Contrapuntus
 * Startup javascript
 */

function initialize() {
  detect = new BrowserDetect();
  var browser = detect.browser;
  var version = detect.version;
  var OS = detect.OS;

  // insert any desired browser-specific exclusions here
  if (browser == 'Explorer' && version < 6) {
    return;
  }

  flash = new FlashEmbedder();
  if (!flash.hasFlash()) return;

  $('body').setStyle({overflow:'hidden', height:'100%', width:'100%'});
  nav = new Navigation();
}

/**
 * Functions to be called by flash to toggle the display mode.
 */
function toggleDisplay() {
  nav.toggle();
  $('swf').focus();
}

function openDisplay() {
  nav.open();
  $('swf').focus();
}

function closeDisplay() {
  nav.close();
  $('swf').focus();
}

function reloadDisplay() {
  nav.reload();
  $('swf').focus();
}

/**
 * Function Flash can call to create a new window containing arbitrary xml
 */
function popWindowWithXML(content) {
  var popup = window.open('','music');
  popup.document.open();
  popup.document.write(content.escapeHTML());
  popup.document.close();
}

/**
 * FlashEmbedder
 * Abstraction for using SWFObject for embedding and Flash detection.
 */
var FlashEmbedder = Class.create({
  initialize: function() {

    this.playerVersion = swfobject.getFlashPlayerVersion();
    this.majorVersion = this.playerVersion.major;

    if (this.hasFlash()) {
      var flashvars = false;
      var params = { menu: "false", scale: "noscale" };
      var attributes = { id: "swf", name: "swf" };
      swfobject.embedSWF('3rd_angle.swf', "noflash", "100%", "100%", "9.0.0", false, flashvars, params, attributes);
    }
  },

  hasFlash: function() {
    return this.majorVersion >= 9;
  }
});



/**
 * State
 * State object for keeping track of the current state of the interface
 */
var State = Class.create({
  initialize: function(params) {
    this.page = 1;
    this.count = 25;
    this.parent = null;
  }
});

/**
 * Navigation
 * Abstraction for creating and controlling the html-based navigation zone.
 */
var Navigation = Class.create({
  initialize: function() {
    this.state = new State({page: 1, count: 25, parent: null});
    this.history = [];

    this.settings = {
      open_flash_height: '30%',
      open_html_height: '70%',
      closed_flash_height: '100%',
      closed_html_height: '0%',
      transition_duration: 0.3,
      page_window: 5,
      page_chunk: 10,
      simple_paging_limit: 15
    };

    // create and attach our navigation and browser zones
    container = Builder.node('div',{id:'htmlcontent'},[
      Builder.node('div',{id:'loadingIndicator'}, ["Loading..."]),
      Builder.node('div',{id:'navigation'},[]),
      Builder.node('div',{id:'browserContainer'}, [
        Builder.node('div',{id:'browser'}, []),
      ]),
    ]);
    container = $(container);
    $('body').appendChild(container);

    // register our 'loading' indicator
    Ajax.Responders.register({
      onCreate: function(){
        if (Ajax.activeRequestCount) {
          $('loadingIndicator').show();
        }
      },
      onComplete: function(){
        if (!Ajax.activeRequestCount) {
          $('loadingIndicator').hide();
        }
      }
    });

    // initialize performance loader and do initial load of data
    this.loader = new PerformanceLoader();
    this.load();

    this.close();
  },


  /**
   * Open or close the performance browser area, depending on its current state.
   */
  toggle: function() {
    if (this.html_open) this.close();
    else this.open();
  },

  /**
   * Open the performance browser area.
   */
  open: function() {
    // hack for safari... force flash to update by resizing the browser after change
    if (detect.browser == 'Safari') {
      $('flashcontent').setStyle({height:this.settings.open_flash_height})
      $('htmlcontent').setStyle({height:this.settings.open_html_height})
      window.setTimeout(function() {
        window.resizeBy(1, 0);
        window.setTimeout(function() { window.resizeBy(-1, 0); }, 5)
      }, 5);
    } else {
      $('flashcontent').morph({height:this.settings.open_flash_height}, {duration:this.settings.transition_duration})
      $('htmlcontent').morph({height:this.settings.open_html_height}, {duration:this.settings.transition_duration})
    }
    this.html_open = true;

  },

  /**
   * Close the performance browser area.
   */
  close: function() {
    // hack for safari... force flash to update by resizing the browser after change
    if (detect.browser == 'Safari') {
      $('flashcontent').setStyle({height:this.settings.closed_flash_height})
      $('htmlcontent').setStyle({height:this.settings.closed_html_height})
      window.setTimeout(function() {
        window.resizeBy(1, 0);
        window.setTimeout(function() { window.resizeBy(-1, 0); }, 5)
      }, 5);
    } else {
      $('flashcontent').morph({height:this.settings.closed_flash_height}, {duration:this.settings.transition_duration})
      $('htmlcontent').morph({height:this.settings.closed_html_height}, {duration:this.settings.transition_duration})
    }
    this.html_open = false;
  },

  /**
   * Fetch the data for the selected page.
   */
  load: function() {
    this.loader.fetch({page: this.state.page, count: this.state.count,
                      parent: this.state.parent,
                      onParseComplete: this.handleUpdate.bindAsEventListener(this)});
  },

  /**
   * Load data consisting of the children for a particular performance
   */
  loadChildrenOf: function(parent_id) {
    var parent = new Object();
    parent.id = parent_id;
    this.loader.fetch({page: 1, count: 'all', parent: parent,
                      onParseComplete: this.handleChildrenUpdate.bindAsEventListener(this)});
  },

  /**
   * Reset the pagination and reload the content on the page
   */
  reload: function() {
    this.state.page = 1;
    this.load();
  },

  /**
   * Load the next page of performances.
   */
  next: function() {
    this.state.page++;
    this.load();
  },

  /**
   * Load the previous page of performances.
   */
  prev: function() {
    this.state.page--;
    this.load();
  },

  /**
   * Event handler for a click on a page
   */
  handlePageClick: function(event) {
    var element = event.element();
    var page = element.id.replace(/page/, '');
    page = Number(page);
    this.state.page = page;
    this.load();
  },

  /**
   * Callback used to update the presentation layer after new data has loaded.
   */
  handleUpdate: function(data) {
    this.totalPages = data.totalPages;
    this.totalResults = data.totalResults;
    this.render(data);
  },

  /**
   * Render the data to the page
   */
  render: function(data) {
    this.renderPagination(data);
    this.renderPerformanceList(data);
  },

  /**
   * Update the pagination area
   */
  renderPagination: function(data) {
    var totalPages = data.totalPages;
    var totalResults = data.totalResults;


    // create pagination list
    var pagination = Builder.node('ul',{id:'pagination'});

    if (this.state.parent && this.state.parent.id) {
      pagination.appendChild( Builder.node('li', [
        Builder.node('a', {id:'parentBtn'}, ["Up"])
      ]));
    }

    if (this.state.page > 1) {
      prevBtn = Builder.node('a', {id:"prevBtn"}, "Prev.");
      pagination.appendChild( Builder.node('li', [prevBtn]));
    }

    if (this.totalPages <= this.settings.simple_paging_limit) {

      // simple case for small number of pages
      for (pg = 1; pg <= this.totalPages; pg++) {
        var className = 'pageButtons';
        if (pg == this.state.page) {
          className = className + ' here';
        }
        pagination.appendChild( Builder.node('li', [
          Builder.node('a', {id:'page' + pg, className: className}, [pg])
        ]));
      }

    } else {

      // more complex case for handling lots of pages
      for (pg = 1; pg <= this.totalPages; pg++) {

        if ((pg % this.settings.page_chunk) == 0
            || ((pg < (this.state.page + this.settings.page_window)) && (pg > (this.state.page - this.settings.page_window)))
        ) {

          var className = 'pageButtons';
          if (pg == this.state.page) {
            className = className + ' here';
          }

          pagination.appendChild( Builder.node('li', [
            Builder.node('a', {id:'page' + pg, className: className}, [pg])
          ]));
        }
      }

    }

    if (this.state.page < this.totalPages) {
      nextBtn = Builder.node('a', {id:"nextBtn"}, "Next");
      pagination.appendChild( Builder.node('li', [nextBtn]));
    }

    pagination = $(pagination);

    // replace existing nav with this new one.
    this.clearNav();
    $('navigation').appendChild(pagination);

    // bind some observers
    if (this.state.page > 1) {
      $('prevBtn').observe('click', this.prev.bindAsEventListener(this));
    }
    if (this.state.page < totalPages) {
      $('nextBtn').observe('click', this.next.bindAsEventListener(this));
    }
    if (this.state.parent && this.state.parent.id) {
      $('parentBtn').observe('click', this.handleParentClick.bindAsEventListener(this));
    }

    var pageLinks = $$('#pagination .pageButtons');
    for (var i = 0, len = pageLinks.length; i < len; i++) {
      pageLinks[i].observe('click', this.handlePageClick.bindAsEventListener(this));
    }
  },

  /**
   * Update the performance list
   */
  renderPerformanceList: function(data) {
    var performances = data.performances;
    if (!performances) return;

    var performanceNodes = [];
    for (var i = 0; i < performances.length; i++) {
      var performance = performances[i];

      if (!performance.performer) performance.performer = 'Anonymous';

      var content_nodes = [];

      if (performance.children) {
        content_nodes.push(
          Builder.node('div', {className:'variations', id:"variations" + performance.id}, [
            performance.children,
            Builder.node('span', {className:'plus'}, "+")
          ])
        );
      }

      content_nodes.push(
        Builder.node('div', {className:'performance', id:"performance" + performance.id}, [
          Builder.node('div', {className:'arrow'}),
          Builder.node('div', {className:'title'}, performance.title),
          Builder.node('div', {className:'performer'}, " by " + performance.performer)
        ])
      );

      performanceNodes.push(Builder.node('li', content_nodes));
    }

    this.clearBrowser();
    $('browser').insert(
      Builder.node('ul',{id:'performances'},performanceNodes)
    );

    var performanceLinks = $$('#performances .performance');
    for (var i = 0, len = performanceLinks.length; i < len; i++) {
      performanceLinks[i].observe('click', this.handlePerformanceClick.bindAsEventListener(this));
    }

    var variationLinks = $$('#performances .variations');
    for (var i = 0, len = variationLinks.length; i < len; i++) {
      variationLinks[i].observe('click', this.handleVariationClick.bindAsEventListener(this));
    }
  },

  /**
   * Callback used to insert child nodes under their parents
   */
  handleChildrenUpdate: function(data) {
    var parentId = data.parentId;
    var performances = data.performances;
    if (!performances || !parentId) return;

    // construct the unordered list of children
    var performanceNodes = [];
    for (var i = 0; i < performances.length; i++) {
      var performance = performances[i];

      if (!performance.performer) performance.performer = 'Anonymous';

      var content_nodes = [];

      if (performance.children) {
        content_nodes.push(
          Builder.node('div', {className:'variations', id:"variations" + performance.id}, [
            performance.children,
            Builder.node('span', {className:'plus'}, "+")
          ])
        );
      }

      content_nodes.push(
        Builder.node('div', {className:'performance', id:"performance" + performance.id}, [
          Builder.node('div', {className:'arrow'}),
          Builder.node('div', {className:'title'}, performance.title),
          Builder.node('div', {className:'performer'}, " by " + performance.performer)
        ])
      );

      performanceNodes.push(Builder.node('li', content_nodes));
    }

    var containerId = 'childrenOf' + parentId;

    // if the node already existed, remove it first
    var oldChildren = $(containerId);
    if (oldChildren) oldChildren.remove();

    // insert the new list
    var ul = Builder.node('ul',{id: containerId, className:'childPerformances'},performanceNodes);
    $('performance' + parentId).up().insert(ul);

    // wipe out any pre-existing unordered lists of children... unless they
    // are an ancestor of the newly loaded child!
    var childPerformances = $$('.childPerformances');

    for (var i = 0, len = childPerformances.length; i < len; i++) {
      if (! (childPerformances[i].id == containerId)        // don't remove item we just added
          && !(ul.descendantOf(childPerformances[i]))       // don't remove ancestors of item we just added
      ) {
        childPerformances[i].remove();
      }
    }

    var performanceLinks = $$('#' + containerId + ' .performance');
    for (var i = 0, len = performanceLinks.length; i < len; i++) {
      performanceLinks[i].observe('click', this.handlePerformanceClick.bindAsEventListener(this));
    }

    var variationLinks = $$('#' + containerId + ' .variations');
    for (var i = 0, len = variationLinks.length; i < len; i++) {
      variationLinks[i].observe('click', this.handleVariationClick.bindAsEventListener(this));
    }
  },


  /**
   * Empty the 'browser' zone
   */
  clearBrowser: function() {
    $('browser').replace(Builder.node('div',{id:'browser'}, []));
  },

  /**
   * Empty the 'navigation' zone
   */
  clearNav: function() {
    $('navigation').replace(Builder.node('div',{id:'navigation'}, []));
  },

  /**
   * Event handler for a click on a performance
   */
  handlePerformanceClick: function(event) {
    var element = event.element().up();
    var id = element.id.replace(/performance/, '');

    this.activatePerformance(id);
  },

  activatePerformance: function(performance_id) {
    var activeListItems = $$('#performances li.active');
    for (var i = 0, len = activeListItems.length; i < len; i++) {
      activeListItems[i].removeClassName('active');
    }
    var performance = $('performance' + performance_id);
    if (performance) {
      performance.up().addClassName('active');
    }

    this.openVariations(performance_id, true);

    SWFAddress.setValue('/performance/' + performance_id);
    SWFAddress.setTitle('Third Angle New Music Ensemble: Contrapunctus Variations');
  },

  /**
   * Event handler for a click on a 'variations' link
   */
  handleVariationClick: function(event) {
    var element = event.element();
    if (element.className == 'plus') {
      element = element.up();
    }

    var id = element.id.replace(/variations/, '');
    this.openVariations(id);
  },

  openVariations: function(id, skip_activating_performance) {
    if (!skip_activating_performance) this.activatePerformance(id);

    var target = $('performance' + id);
    var openedVariations = $$('.openedVariations');
    for (var i = 0, len = openedVariations.length; i < len; i++) {
      if (!(target.descendantOf(openedVariations[i]))) {
        openedVariations[i].removeClassName('openedVariations');
      }
    }
    target.up().addClassName('openedVariations');

    var kids = $('childrenOf' + id);
    if (kids) {
      /* do nothing */
    } else {
      this.loadChildrenOf(id);
    }
  },

  /**
   * Event handler for going back up a level in the tree.
   */
  handleParentClick: function(event) {
    this.state = this.history.pop();
    this.load();
  }
});

/**
 * PerformanceLoader
 * Abstraction for using Ajax to load the performances list and update the
 * performance zone.
 */
var PerformanceLoader = Class.create({
  initialize: function(params) {
    this.serviceRoot = '/srv';
    this.data = {};
    this.xml = null;
  },

  /**
   * Fetch the data from our service
   */
  fetch: function(params) {
    if (!params) params = {};
    if (!params.page) params.page = 1;
    if (!params.count) params.count = 50;
    if (params.onParseComplete) {
      this.onParseComplete = params.onParseComplete;
    } else {
      this.onParseComplete = null;
    }


    var serviceUri = this.serviceRoot;
    if (params.parent && params.parent.id) {
      serviceUri = serviceUri + '/performances/' + params.parent.id + '/children.json';
    } else {
      serviceUri = serviceUri + '/performances.json';
    }


    new Ajax.Request(serviceUri,
    {
      method:'get',
      parameters: {itemsPerPage: params.count, page: params.page},
      onSuccess: this.parseJSON.bindAsEventListener(this),
      onFailure: this.handleFetchError.bindAsEventListener(this)
    });
  },

  /**
   * Parse an XMLDocument into a simplified array of performance data
   */
  parseJSON: function(transport) {
    this.data = transport.responseText.evalJSON(true);
    this.onParseComplete(this.data);
  },

  /**
   * Error handler for when ajax load fails.
   */
  handleFetchError: function() {
    alert('Unable to load requested data.  Please try again in a few moments.');
  }
});


/**
 * BrowserDetect
 * Slightly modified from the browser detect script on the excellent
 * quirksmode website: http://www.quirksmode.org/js/detect.html
 */
var BrowserDetect = Class.create({
  initialize: function(params) {
		this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
		this.version = this.searchVersion(navigator.userAgent)
			|| this.searchVersion(navigator.appVersion)
			|| "an unknown version";
		this.OS = this.searchString(this.dataOS) || "an unknown OS";
  },
	searchString: function (data) {
		for (var i=0;i<data.length;i++)	{
			var dataString = data[i].string;
			var dataProp = data[i].prop;
			this.versionSearchString = data[i].versionSearch || data[i].identity;
			if (dataString) {
				if (dataString.indexOf(data[i].subString) != -1)
					return data[i].identity;
			}
			else if (dataProp)
				return data[i].identity;
		}
	},
	searchVersion: function (dataString) {
		var index = dataString.indexOf(this.versionSearchString);
		if (index == -1) return;
		return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
	},
	dataBrowser: [
		{ 	string: navigator.userAgent,
			subString: "OmniWeb",
			versionSearch: "OmniWeb/",
			identity: "OmniWeb"
		},
		{
			string: navigator.vendor,
			subString: "Apple",
			identity: "Safari"
		},
		{
			prop: window.opera,
			identity: "Opera"
		},
		{
			string: navigator.vendor,
			subString: "iCab",
			identity: "iCab"
		},
		{
			string: navigator.vendor,
			subString: "KDE",
			identity: "Konqueror"
		},
		{
			string: navigator.userAgent,
			subString: "Firefox",
			identity: "Firefox"
		},
		{
			string: navigator.vendor,
			subString: "Camino",
			identity: "Camino"
		},
		{		// for newer Netscapes (6+)
			string: navigator.userAgent,
			subString: "Netscape",
			identity: "Netscape"
		},
		{
			string: navigator.userAgent,
			subString: "MSIE",
			identity: "Explorer",
			versionSearch: "MSIE"
		},
		{
			string: navigator.userAgent,
			subString: "Gecko",
			identity: "Mozilla",
			versionSearch: "rv"
		},
		{ 		// for older Netscapes (4-)
			string: navigator.userAgent,
			subString: "Mozilla",
			identity: "Netscape",
			versionSearch: "Mozilla"
		}
	],
	dataOS : [
		{
			string: navigator.platform,
			subString: "Win",
			identity: "Windows"
		},
		{
			string: navigator.platform,
			subString: "Mac",
			identity: "Mac"
		},
		{
			string: navigator.platform,
			subString: "Linux",
			identity: "Linux"
		}
	]
});

Event.observe(window, 'load', initialize);
