
/////////////////
// Linked List //
/////////////////

function LinkedList() {};
LinkedList.prototype = { length: 0, first: null, last: null };
LinkedList.Circular = function() {};
LinkedList.Circular.prototype = new LinkedList();
LinkedList.Circular.prototype.append = function( node )
{
  if ( this.first === null )
  {
    node.prev = node;
    node.next = node;
    this.first = node;
    this.last = node;
  }
  else
  {
    node.prev = this.last;
    node.next = this.first;
    this.first.prev = node;
    this.last.next = node;
    this.last = node;
  }
  this.length++;
};
LinkedList.Circular.prototype.insertAfter = function( node, newNode )
{
  newNode.prev = node;
  newNode.next = node.next;
  node.next.prev = newNode;
  node.next = newNode;
  if ( newNode.prev == this.last ) { this.last = newNode; }
  this.length++;
};
LinkedList.Circular.prototype.remove = function( node )
{
  if ( this.length > 1 )
  {
    node.prev.next = node.next;
    node.next.prev = node.prev;
    if ( node == this.first ) { this.first = node.next; }
    if ( node == this.last ) { this.last = node.prev; }
  }
  else
  {
    this.first = null;
    this.last = null;
  }
  node.prev = null;
  node.next = null;
  this.length--;
};
LinkedList.Node = function( data )
{
  this.prev = null;
  this.next = null;
  this.data = data;
};

//////////////
// Carousel //
//////////////

function Carousel( idOfContainerElement, options )
{
  // Do some preliminary checking to ensure the constructor was called correctly
  if ( typeof( idOfContainerElement ) != 'string' )
  {
    if ( typeof( console ) != 'undefined' )
      if ( typeof( console.error ) != 'undefined' )
        console.error( 'First parameter of Carousel() must be the id of an element containing items for the carousel' );
    return;
  }
  if ( !idOfContainerElement.startsWith( '#' ) ) idOfContainerElement = '#' + idOfContainerElement;
  // Assign original markup
  var originalHeight = $( idOfContainerElement ).css( 'height' );
  this.itemContainer = $( idOfContainerElement );
  this.items         = $( idOfContainerElement ).children();
  this.setItemList();
  // Assign options
  this.options = typeof( options ) == 'undefined' ? {} : options;
  if ( typeof( this.options.keepVerticalDimension ) == 'undefined' ) this.options.keepVerticalDimension = true;
  if ( typeof( this.options.width ) == 'undefined' ) this.options.width = $( idOfContainerElement ).css( 'width' );
  // Clone container markup in order to disturb page layout as little as possible
  this.carouselWrapper = this.itemContainer.clone();
  this.carouselWrapper.removeAttr( 'id' );
  this.carouselWrapper.empty();
  // Add carousel-specific CSS to carousel wrapper
  this.carouselWrapper.addClass( 'div-carousel-wrapper' );
  this.carouselWrapper.css( 'width', this.options.width );
  this.carouselWrapper.css( 'height', originalHeight );
  // Adapt CSS of item container to new role in carousel
  this.itemContainer.removeClass();
  this.itemContainer.addClass( 'div-carousel-container' );
  this.itemContainer.css( 'width',  this.getContainerWidth()  );
  this.itemContainer.css( 'height', this.getContainerHeight() );
  // Create buttons
  this.prevButton = $( '<div></div>' );
  this.prevButton.addClass( 'div-carousel-button prev disabled' );
  this.nextButton = $( '<div></div>' );
  this.nextButton.addClass( 'div-carousel-button next disabled' );
  // Assign event handler functions
  var self = this;
  var clickHandlerNext = this.next;
  this.nextButton.click( function( event ) { clickHandlerNext( self ); });
  this.nextButton.mouseover( function() { $( this ).addClass   ( 'hover'   ); });
  this.nextButton.mouseout ( function() { $( this ).removeClass( 'hover'   ); });
  this.nextButton.mousedown( function() { $( this ).addClass   ( 'clicked' ); });
  this.nextButton.mouseup  ( function() { $( this ).removeClass( 'clicked' ); });
  var clickHandlerPrev = this.prev;
  this.prevButton.click( function( event ) { clickHandlerPrev( self ); });
  this.prevButton.mouseover( function() { $( this ).addClass   ( 'hover'   ); });
  this.prevButton.mouseout ( function() { $( this ).removeClass( 'hover'   ); });
  this.prevButton.mousedown( function() { $( this ).addClass   ( 'clicked' ); });
  this.prevButton.mouseup  ( function() { $( this ).removeClass( 'clicked' ); });
  // Create container wrapper
  this.containerWrapper = $( '<div></div>' );
  this.containerWrapper.addClass( 'div-carousel-container-wrapper' );
  // Add markup to DOM
  this.itemContainer.parent().append( this.carouselWrapper );
  this.carouselWrapper.append( this.prevButton );
  this.carouselWrapper.append( this.containerWrapper );
  this.carouselWrapper.append( this.nextButton );
  this.containerWrapper.append( this.itemContainer );
  // Set viewport width
  this.viewportWidth  = parseInt( this.options.width );
  this.viewportWidth -= parseInt( this.prevButton.css( 'width' ) );
  this.viewportWidth -= parseInt( this.nextButton.css( 'width' ) );
  this.containerWrapper.css( 'width', this.viewportWidth + 'px' );
  // Check if we want to keep original vertical dimension
  var verticalOffset = 0;
  if ( this.options.keepVerticalDimension )
  {
    // Vertically align in the middle of carousel wrapper
    originalHeight = parseInt( originalHeight );
    if ( typeof( originalHeight ) == 'number' )
    {
      // Height of original container is defined, keeping original dimensions possible
      verticalOffset = ( originalHeight - this.getContainerHeight() ) / 2;
    }
  }
  if ( !( verticalOffset > 0 ) )
  {
    // Keeping original dimensions not possible or not wanted
    this.carouselWrapper.css( 'height', this.getContainerHeight() );
  }
  // Vertically align the buttons (and possibly the container wrapper)
  var verticalOffsetPrevButton = ( this.getContainerHeight() - parseInt( this.prevButton.css( 'height' ) ) ) / 2 + verticalOffset;
  this.prevButton.css( 'margin-top', verticalOffsetPrevButton + 'px' );
  var verticalOffsetNextButton = ( this.getContainerHeight() - parseInt( this.nextButton.css( 'height' ) ) ) / 2 + verticalOffset;
  this.nextButton.css( 'margin-top', verticalOffsetNextButton + 'px' );
  if ( verticalOffset > 0 ) this.containerWrapper.css( 'margin-top', '' + verticalOffset + 'px' );
  // Initialise stuff
  this.xCurrent = 0;
  this.enableNext();
};
Carousel.prototype.setItemList = function()
{
  this.itemList = new LinkedList.Circular();
  for ( var it = 0 ; it < this.items.length ; it++ )
  {
    var node = new LinkedList.Node( this.items[it] );
    node.x = it * this.getItemWidth();
    node.xRight = node.x + this.getItemWidth();
    this.itemList.append( node );
  }
};
Carousel.prototype.getContainerWidth  = function() { return this.itemList.length * this.getItemWidth(); };
Carousel.prototype.getContainerHeight = function() { return this.getItemHeight(); };
Carousel.prototype.getItemWidth = function()
{
  var actualWidth  = parseInt( this.getItemCSSPropertyValue( 'width' ) );
      actualWidth += parseInt( this.getItemCSSPropertyValue( 'margin-left' ) );
      actualWidth += parseInt( this.getItemCSSPropertyValue( 'margin-right' ) );
      actualWidth += parseInt( this.getItemCSSPropertyValue( 'border-left-width' ) );
      actualWidth += parseInt( this.getItemCSSPropertyValue( 'border-right-width' ) );
  return actualWidth;
};
Carousel.prototype.getItemHeight = function()
{
  var actualHeight  = parseInt( this.getItemCSSPropertyValue( 'height' ) );
      actualHeight += parseInt( this.getItemCSSPropertyValue( 'margin-top' ) );
      actualHeight += parseInt( this.getItemCSSPropertyValue( 'margin-bottom' ) );
      actualHeight += parseInt( this.getItemCSSPropertyValue( 'border-top-width' ) );
      actualHeight += parseInt( this.getItemCSSPropertyValue( 'border-bottom-width' ) );
  return actualHeight;
};
Carousel.prototype.getItemCSSPropertyValue = function( property )
{
  if ( !( this.items.length > 0 ) ) return false;
  var item = this.items[0];
  return $( item ).css( property );
};
Carousel.prototype.enablePrev = function()
{
  this.prevButton.removeClass( 'disabled' );
  this.prevButton.addClass( 'enabled' );
}
Carousel.prototype.disablePrev = function()
{
  this.prevButton.removeClass( 'enabled hover clicked' );
  this.prevButton.addClass( 'disabled' );
}
Carousel.prototype.enableNext = function()
{
  this.nextButton.removeClass( 'disabled' );
  this.nextButton.addClass( 'enabled' );
}
Carousel.prototype.disableNext = function()
{
  this.nextButton.removeClass( 'enabled hover clicked' );
  this.nextButton.addClass( 'disabled' );
}
Carousel.prototype.prev = function( self )
{
  var maxMoveTo = 0;
  if ( self.xCurrent > maxMoveTo )
  {
    self.enableNext();
    var node = self.itemList.last;
    var xMin = self.xCurrent - self.viewportWidth;
    if ( xMin < maxMoveTo ) xMin = maxMoveTo;
    while ( node.x > xMin ) node = node.prev;
    var moveTo = node.x;
    self.itemContainer.animate( { marginLeft: '-' + moveTo + 'px' } );
    self.xCurrent = moveTo;
    if ( !( self.xCurrent > maxMoveTo ) ) self.disablePrev();
  }
  else self.disablePrev();
};
Carousel.prototype.next = function( self )
{
  var maxMoveTo = self.itemList.last.xRight - self.viewportWidth;
  if ( self.xCurrent < maxMoveTo )
  {
    self.enablePrev();
    var node = self.itemList.first;
    while ( node.xRight < self.viewportWidth + self.xCurrent ) node = node.next;
    var moveTo = node.xRight;
    if ( moveTo > maxMoveTo ) moveTo = maxMoveTo;
    self.itemContainer.animate( { marginLeft: '-' + moveTo + 'px' } );
    self.xCurrent = moveTo;
    if ( !( self.xCurrent < maxMoveTo ) ) self.disableNext();
  }
  else self.disableNext();
};

////////////////////////////////////////
// Thickbox for character biographies //
////////////////////////////////////////

function thickboxifyCharacterIcons()
{
  $( ".character-icon" ).each( function()
  {
    $( this ).addClass( "thickbox_carousel" );
    var characterId = getId( $( this ).attr( "id" ) );
    var href = $( this ).attr( "href" );
    if ( href != "#" ) $( this ).attr( "href", "/bio/" + characterId + "?keepThis=true&TB_iframe=true&height=400&width=700" );
    $( this ).mouseover( function()
    {
      getCharacterInfo( characterId );
    });
  });
  tb_init('a.thickbox_carousel');
}

//////////////////////////////////
// Write and read Cookie values //
//////////////////////////////////

function setCookie( name, value )
{
  var a = new Date();
  a = new Date( a.getTime() + 1000 * 60 * 60 * 24 * 365 );
  document.cookie = name + "=" + value + "; expires=" + a.toGMTString() + ";";
}
function getCookie( name )
{
  var str = document.cookie;
  var cookiename, cookievalue, i, res = "";
  while ( str != "" )
  {
    while ( str.substr( 0, 1 ) == ' ' )
    {
      str = str.substr( 1, str.length );
    }
    cookiename = str.substring( 0, str.indexOf( "=" ) );
    if ( str.indexOf( ";" ) != -1 ) cookievalue = str.substring( str.indexOf( "=" ) + 1, str.indexOf( ";" ) );
    else cookievalue = str.substr( str.indexOf( "=" ) + 1, str.length );
    if ( name == cookiename ) res = cookievalue;
    i = str.indexOf( ";" ) + 1;
    if ( i == 0 ) i = str.length;
    str = str.substring( i, str.length );
  }
  return res;
}
function useTheAwesome()
{
  if ( getCookie( "enhanced" ) == "yes" ) return true;
  return false;
}

//////////////////////////////////////////
// Retrieve basic character information //
//////////////////////////////////////////

function getCharacterInfo( characterId )
{
  $.ajax(
  {
    url: "/ajax/characterinfo/id/" + characterId,
    timeout: 20000,
    success: function( html )
    {
      if ( !introduction ) introduction = $( "#div_info" ).html()
      $( "#div_info" ).html( "" );
      $( "#div_info" ).append( html );
      tb_init('a.thickbox_ci');
    }
  });
}

///////////////////////////////////////////////////////////////////////
// Write the introduction text blurb back into the left upper screen //
///////////////////////////////////////////////////////////////////////

function setIntroduction()
{
  if ( introduction ) $( "#div_info" ).html( introduction );
  introduction = false;
}

/////////////////////////////////////////////
// Make the Starfleet logo do shiny tricks //
/////////////////////////////////////////////

function blinkLogo()
{
  $( "#div_logo" ).hide();
  $( "#div_logo" ).fadeIn( 1000 );
  $( "#div_logo_overlay" ).show();
  $( "#div_logo_overlay" ).fadeOut( 1000 );
}

///////////////////////////////
// Send the application form //
///////////////////////////////

function sendJoinForm()
{
  // check if we're in awesome mode...
  if ( getCookie( "enhanced" ) == "no" ) return true;
  // check input...
  var application = $( "#input_application" ).val();
  var email = $( "#input_email" ).val();
  var subject = $( "#input_subject" ).val();
  if ( application != "" && email != "" && subject != "" )
  {
    // send application...
    $.ajax(
    {
      url: "/ajax/join",
      type: "POST",
      data: "application=" + application + "&subject=" + subject + "&email=" + email,
      timeout: 20000,
      success: function( html ) { $( "#div_content" ).html( html ) }
    });
    // change the display...
    $( "#div_content" ).html( "sending application..." );
  }
  else alert( "All form fields have to be filled in." );
  // ...and tell the plain old html form to bloody sod off
  return false;
}

////////////////////////////////////////
// Restore state (e.g. from bookmark) //
////////////////////////////////////////

function initialiseStateFromURL()
{
  loadSection( window.location.hash );
}

///////////////////////
// Load page section //
///////////////////////

function loadSection( section )
{
  section = section.replace( "#", "");
  if ( section == currentlySelectedSection || section == "" ) return;
  currentlySelectedSection = section;
  window.location.hash = section;
  $( document.body ).css( "cursor", "wait" );
  $.ajax(
  {
    url: "/ajax/" + section, timeout: 20000,
    success: function( html )
    {
      $( "#div_content" ).html( html );
      setIntroduction();
      blinkLogo();
      $( document.body ).css( "cursor", "default" );
      if ( currentlySelectedSection == 'stories' )
      {
        new Carousel( '#div_manifest', { width: '620px' } );
        thickboxifyCharacterIcons();
      }
    }
  });
  switch ( section )
  {
    case "ship":          retrieveTopSectionContent( "/ajax/msd"               ); break;
    case "stories":       retrieveTopSectionContent( "/ajax/story"             ); break;
    case "credits":       retrieveTopSectionContent( "/ajax/disclaimer"        ); break;
    case "join":          retrieveTopSectionContent( "/ajax/joiningprocedure"  ); break;
    case "commendations": retrieveTopSectionContent( "/ajax/shipcommendations" ); break;
    case "history":       retrieveTopSectionContent( "/ajax/commandlineage"    ); break;
  }
}

function retrieveTopSectionContent( url )
{
  $.ajax(
  {
    url: url,
    timeout: 20000,
    success: function( html )
    {
      $( "#div_preview" ).html( html );
      //$( "#div_preview" ).css( "overflow", "hidden" );
      if ( useTheAwesome() ) $( "#tehScroll" ).jScrollPane( { showArrows: true, reinitialiseOnImageLoad: true } );
    }
  });
}

////////////////////////////////////////////////////
// Retrieve an identifier from an attribute value //
////////////////////////////////////////////////////

function getId( str )
{
  var pos = str.lastIndexOf( "_" ) + 1;
  if ( pos < 0 ) return false;
  return str.substr( pos );
}

//////////////////////
// String functions //
//////////////////////

String.prototype.trim       = function()         { return this.replace( /^\s+|\s+$/g, '' ); }
String.prototype.limit      = function( length ) { return this.substr( 0, length ); }
String.prototype.startsWith = function( str )    { return this.match( "^" + str ) == str; }
String.prototype.endsWith   = function( str )    { return this.match( str + "$" ) == str; }

