// meta data about what challenge leaderboards exist
var gMetaData = new Object( );

// the id of the currently selected world
var gWorldId = null;

// the map of world ids to names
var gWorlds = new Object( );

// map of race ids to names
var gRaces = new Object( );

// map of class ids to names
var gClasses = new Object( );

// the id of the currently selected challenge
var gChallengeId = null;

// the currently selected challenge rating
var gRating = null;

// the current page of results (0-indexed)
var gPage = 0;

// the id of the js timeout for the loading animation
var gTimeout = null;

// the max size of a page
var gPageSize = 50;

/**
 * On document ready initialize the filer display.
 */
jQuery( document ).ready( function( ) {
  jQuery( '#loading' ).show( );
  jQuery( '#world-select' ).bind( 'change', updateWorld );
  jQuery( '#challenge-select' ).bind( 'change', updateChallenge );
  jQuery( '#rating-select' ).bind( 'change', updateRating );
  jQuery( '#challenge-filter-button' ).bind( 'click', function ( ) {
    gPage = 0;
    jQuery.cookie( 'cl-page', gPage );
    refreshLeaderboard( );
  } );
  jQuery( '.paginate_control a' ).bind( 'click', paginationClicked );
  initializeLeaderboards( );

  jQuery( '#charlevel-desc' ).cluetip( {
    local: true,
    showTitle: false,
    width: 200
  } );
} );

/**
 * Callback for when the world select changes.  This will:
 *   - activate the challenge select
 *   - remove the empty option in the world select
 *   - update the gWorldId global variable
 */
function updateWorld ( ) {
  jQuery( '#challenge-select' ).attr( 'disabled', '' );
  jQuery( '#world-null' ).remove( );
  gWorldId = parseInt( jQuery( '#world-select' ).val( ) );
  jQuery.cookie( 'cl-world-id', gWorldId );
  buildChallengeOptions( );
  buildRatingOptions( );
  setFilterButtonState( );
}

/**
 * Callback for when the challenge select changes.  This will:
 *   - activate the rating select
 *   - remove the empty option in the challenge select
 *   - update the gChallengeId global variable
 *   - populate the rating select for this challenge
 */
function updateChallenge ( ) {
  jQuery( '#rating-select' ).attr( 'disabled', '' );
  jQuery( '#challenge-null' ).remove( );
  gChallengeId = parseInt( jQuery( '#challenge-select' ).val( ) );
  jQuery.cookie( 'cl-challenge-id', gChallengeId );
  jQuery.cookie( 'cl-rating', null );
  buildRatingOptions( );
  setFilterButtonState( );
}

/**
 * Callback for when the rating select changes.  This will:
 *   - update the gRating global variable
 */
function updateRating ( ) {
  gRating = parseInt( jQuery( '#rating-select' ).val( ) );
  jQuery.cookie( 'cl-rating', gRating );
  setFilterButtonState( );
}

/**
 * Enables the filter button if all of the values are set.
 */
function setFilterButtonState ( ) {
  if( gWorldId != null && gChallengeId != null && gRating != null ) {
    jQuery( '#challenge-filter-button' ).attr( 'disabled', '' ).attr( 'src', gThemeVariantUri + '/filter_off.png' );
  }
}

/**
 * Populates the rating select with the valid challenge values for
 * the currently selected challenge.
 */
function buildChallengeOptions ( ) {

  // get the data for the selected challenge
  var challenges = gMetaData[gWorldId];
  if( challenges != null ) {

    // remove all of the existing options
    jQuery( '#challenge-select option' ).remove( );

    var found = false;

    // iterate over the levels and add the options
    jQuery.each( challenges, function ( pIndex, pItem ) {
      if( pItem.did == gChallengeId ) {
        found = true;
      }
      var challengeName = pItem.name == null ? '<i>unknown challenge</i>' : pItem.name
      jQuery( '#challenge-select' ).append( '<option value="' + pItem.did + '">' + challengeName + '</option>' );
    } );

    if( !found ) {
      jQuery( '#challenge-select' ).prepend( '<option value="" id="challenge-null" selected="selected"></option>' );
      jQuery( '#challenge-select' ).val( "" );
      gChallengeId = null;
    }
    else {
      jQuery( '#challenge-select' ).val( gChallengeId );
      updateChallenge( );
    }
  }
}

/**
 * Populates the rating select with the valid rating values for
 * the currently selected challenge.
 */
function buildRatingOptions ( ) {

  // get the data for the selected challenge
  var challenge = gMetaData[gWorldId][gChallengeId];

  // remove all of the existing options
  jQuery( '#rating-select option' ).remove( );

  if( challenge != null ) {

    // if the current rating value is not in the array, just default to the last value
    if( gRating == null || jQuery.inArray( parseInt( gRating ), challenge.levels ) == -1 ) {
      gRating = challenge.levels.last( );
    }

    // iterate over the levels and add the options
    jQuery.each( challenge.levels, function ( pIndex, pItem ) {
      jQuery( '#rating-select' ).append( '<option value="' + pItem + '"' + ( gRating == pItem ? ' selected' : '' ) + '>' + pItem + '</option>' );
    } );

  }

  else {
    jQuery( '#rating-select' ).attr( 'disabled', 'true' );
    jQuery( '#rating-select' ).prepend( '<option value="" id="rating-null" selected="selected"></option>' );
      jQuery( '#rating-select' ).val( "" );
    gRating = null;
  }

}

/**
 * This is the success callback for loading the leaderboard meta
 * data file.  This will populate the filter drop downs.
 */
function loadFilters ( pData ) {
  jQuery.each( pData.challenges, function ( pWorldId, pChallenges ) {

    if( pChallenges == null || pChallenges.length == 0 ) {
      jQuery( '#world-select option[value="' + pWorldId + '"]' ).remove( );
    }
    else {
      gMetaData[pWorldId] = new Object( );
      jQuery.each( pChallenges, function( pIndex, pItem ) {
        gMetaData[pWorldId][pItem.did] = pItem;
      } );
    }
  } );

  jQuery.each( pData.worlds, function ( pIndex, pItem ) {
    gWorlds[pIndex] = pItem;
  } );

  jQuery.each( pData.classes, function ( pIndex, pItem ) {
    gClasses[pIndex] = pItem.replace( /\[.*\]/, '' );
  } );

  jQuery.each( pData.races, function ( pIndex, pItem ) {
    gRaces[pIndex] = pItem;
  } );

  jQuery( '#challenge-filter-table' ).show( );
  jQuery( '#loading' ).hide( );

  // if there's a world cookie, set the world id and call the select callback
  var worldId = jQuery.cookie( 'cl-world-id' );
  if( !isNaN( parseInt( worldId ) ) ) {
    jQuery( '#world-select' ).val( worldId );
    updateWorld( );
  }

  // if there's a challenge cookie, set the challenge id and call the select callback
  var challengeId = jQuery.cookie( 'cl-challenge-id' );
  if( !isNaN( parseInt( challengeId ) ) ) {
    jQuery( '#challenge-select' ).val( challengeId );
    updateChallenge( );
  }

  // if there's a rating cookie, set the rating and call the select callback
  var rating = jQuery.cookie( 'cl-rating' );
  if( !isNaN( parseInt( rating ) ) ) {
    jQuery( '#rating-select' ).val( rating );
    updateRating( );
  }

  // if there's a page cookie, set the page
  var page = jQuery.cookie( 'cl-page' );
  if( !isNaN( parseInt( page ) ) ) {
    gPage = page;
  }

  if( gWorldId != null && gChallengeId != null && gRating != null ) {
    refreshLeaderboard( );
  }
}

/**
 * Initializes the leaderboards by loading the meta data JSON
 * file.
 */
function initializeLeaderboards ( ) {
  jQuery.ajax( {
    type: 'GET',
    url:  '/challenge_leaderboards/challenges.json',
    dataType: 'json',
    success: loadFilters
  } );
}

/**
 * Called when the filter button is clicked to fetch a new page of data and
 * display it.
 */
function refreshLeaderboard ( ) {

  gTimeout = setTimeout( "jQuery( '#loading' ).show( )", 500 );

  var url = '/challenge_leaderboards/' + gWorldId + '/' + gChallengeId + '/' + gRating + '/' + ( gPage % 10 ) + '/' + gPage + '.json';

  jQuery.ajax( {
    type: 'GET',
    url:  url,
    dataType: 'json',
    success: displayPage,
    error: displayEmpty
  } );
}

/**
 * Success callback for fetching a page of data.  This will clear the current
 * rows in the result table and populate it with new rows.
 */
function displayPage ( pData ) {

  // disable the filter button until the drop-downs change
  jQuery( '#challenge-filter-button' ).attr( 'disabled', 'true' ).attr( 'src', gThemeVariantUri + '/filter_disabled.png' );

  jQuery( '#select-challenge-message' ).hide( );

  jQuery( '#challenge-leaderboard-table tbody tr' ).remove( );

  jQuery.each( pData.rows, function ( pIndex, pItem ) {
    jQuery( '#challenge-leaderboard-table tbody' ).append( getRowHtml( pItem ) );
  } );

  updatePagination( pData );

  jQuery( '.cache-time' ).show( ).find( 'span' ).text( pData.time_c );

  jQuery( 'a.party' ).cluetip( {
    splitTitle: '|',
    showTitle: false,
    cluetipClass: 'challenges',
    width: 200
  } );

  clearTimeout( gTimeout );
  jQuery( '#loading' ).hide( );

}

/**
 * Failure callback for loading a page of data.  This will display and error
 * message.
 */
function displayEmpty ( pData ) {
  alert( 'no data found for the specified options' );

  // Fix for the 'loading' div blocking the form when there's invalid/missing data
  clearTimeout( gTimeout );
  jQuery( '#loading' ).hide( );
}

/**
 * Called when one of the pagination links is clicked.
 */
function paginationClicked ( ) {
  gPage = jQuery( this ).attr( 'rel' );
  jQuery.cookie( 'cl-page', gPage );
  refreshLeaderboard( );
  return false;
}

/**
 * Uses the pagination data in the page of results to refresh the pagination
 * links.
 */
function updatePagination ( pData ) {

  if( pData.page_t > 1 ) {
    jQuery( '.paginate_control' ).show( );
    jQuery( '.paginate_control .paginate_count .start' ).text( pData.page * gPageSize + 1 );
    jQuery( '.paginate_control .paginate_count .end' ).text( pData.page * gPageSize + pData.count );
    jQuery( '.paginate_control .paginate_count .total' ).text( pData.count_t );
    jQuery( '.paginate_control .paginate_back a.page-prev' ).attr( 'rel', Math.max( 0, pData.page - 1 ) );
    jQuery( '.paginate_control .paginate_next a.page-next' ).attr( 'rel', Math.min( pData.page_t - 1, pData.page + 1 ) );
    jQuery( '.paginate_control .paginate_back a.page-first' ).attr( 'rel', 0 );
    jQuery( '.paginate_control .paginate_next a.page-last' ).attr( 'rel', pData.page_t - 1 );

    var first = Math.max( pData.page - 3, 0 );
    var last = first + 6;

    if( last > pData.page_t - 1 ) {
      last -= last - ( pData.page_t - 1 );
      first = last - 6;
    }

    if( first == 0 ) {
      last += 1;
    }
    if( last == pData.page_t - 1 ) {
      first -= 1;
    }

    if( first < 0 ) {
      first = 0;
    }
    if( last > pData.page_t - 1 ) {
      last = pData.page_t - 1
    }

    var infoText = '';

    if( first > 0 && pData.page_t > 8 ) {
      infoText += '<span>...</span>';
    }
    for( var i = first; i <= last; i ++ ) {
      if( i != pData.page ) {
        infoText += '<a href="" rel="' + i + '">';
      }
      else {
        infoText += '<span>';
      }
      infoText += ( i + 1 );
      if( i != pData.page ) {
        infoText += '</a>';
      }
      else {
        infoText += '</span>';
      }
    }
    if( last < pData.page_t - 1 && pData.page_t > 8 ) {
      infoText += '<span>...</span>';
    }

    jQuery( '.paginate_control .paginate_info' ).html( infoText );

    if( pData.page == 0 ) {
      jQuery( '.paginate_control .paginate_back a' ).hide( );
      jQuery( '.paginate_control .paginate_back span' ).show( );
    }
    else {
      jQuery( '.paginate_control .paginate_back a' ).show( );
      jQuery( '.paginate_control .paginate_back span' ).hide( );
    }

    if( pData.page == pData.page_t - 1 ) {
      jQuery( '.paginate_control .paginate_next a' ).hide( );
      jQuery( '.paginate_control .paginate_next span' ).show( );
    }
    else {
      jQuery( '.paginate_control .paginate_next a' ).show( );
      jQuery( '.paginate_control .paginate_next span' ).hide( );
    }

    // rebind events for the new links that were just generated
    jQuery( '.paginate_control a' ).bind( 'click', paginationClicked );
  }
  else {
    jQuery( '.paginate_control' ).hide( );
  }
}

/**
 * Returns the HTML for displaying a row of data.
 */
function getRowHtml ( pItem ) {

  var worldName = gWorlds[gWorldId];
  var html = '<tr>\n';

  // display the rank
  html += '<td class="text_center left">' + pItem.rank + '</td>\n';

  // display the character class icons
  html += '<td class="text_right charclass">' + getClassIcons( pItem.char.cls, pItem.char.lvl, true ) + '</td>';

  // display the character name link
  html += '<td class="text_left charname">\n';
  html += '<a class="char-link" href="/character/' + worldName + '/' + pItem.char.name.toLowerCase( ) + '">' + pItem.char.name + '</a><br />\n';
  html += '</td>\n';

  // display the character level at time of run
  html += '<td class="text_center">' + pItem.lvl + '</td>';

  // display the party details
  html += '<td class="text_center">\n';
  html += '<a class="party" title="' + getPartyTooltip( pItem ) + '">\n';
  
  // Check for a negative hireling count, and apply compensation to party size
  // (This seems to occur when a player in this challenge has a higher score in a different leaderboard)
  if(pItem.hmen < 0) {
	var partycount = pItem.party - pItem.hmen;
	html += partycount + ' (0)';
  } else {
	html += pItem.party + ' (' + pItem.hmen + ')\n';
  }
  
  html += '</a>\n';
  html += '</td>\n';

  // display the stars/score
  html += '<td class="text_center"><img src="' + gThemeMediaUri + 'common/challenges/stars_' + pItem.stars + '.png" /></td>\n';
  html += '<td class="text_center">' + pItem.score + '</td>\n';

  html += '</tr>\n';
  return html;
}

/**
 * Returns the markup for displaying class icons
 */
function getClassIcons ( pClasses, pLevels, pUseLink ) {

  var html = '';

  jQuery.each( pClasses, function ( pIndex, pClassId ) {
    if( pClassId > 0 ) {
      if( pUseLink ) {
        html += '<a href="' + gCompendiumUrl + 'wiki/Special:DdoResource?id=' + pClassId + '&amp;name=Level+' + pLevels[pIndex] + '+' + gClasses[pClassId] + '">\n';
      }
      html += '<div class="class_icon"><img title="Level ' + pLevels[pIndex] + ' ' + gClasses[pClassId] + '" src="' + gThemeMediaUri + 'common/class_icons/small/' + gClasses[pClassId].toLowerCase( ) + '.png"><img class="lvl_overlay" src="' + gThemeMediaUri + 'common/level_overlays/small/' + pLevels[pIndex] + '.png"></div>';
      if( pUseLink ) {
        html += '</a>\n';
      }
    }
  } );

  return html;
}

/**
 * Returns the tooltip body for the specified row.
 */
function getPartyTooltip ( pItem ) {
  var html = '|';

  html += '<table class="tooltip" cellspacing="0" cellpadding="0">';
  html += '<tr class="tt_tr">';
  html += '<td><img src="' + gThemeMediaUri + 'common/tooltips/tc_tl.gif" /></td>';
  html += '<td class="tbt"></td>';
  html += '<td><img src="' + gThemeMediaUri + 'common/tooltips/tc_tr.gif" /></td>';
  html += '</tr>';
  html += '<tr>';
  html += '<td class="tbl"></td>';
  html += '<td class="tooltipbody">';

  html += '<table class="party-details">';
  
  // Check for a negative hireling count, and apply compensation to party size
  // (This seems to occur when a player in this challenge has a higher score in a different leaderboard)
  if(pItem.hmen < 0) {
	var partycount = pItem.party - pItem.hmen;
	html += '<tr><th>Party Size:</th><td>' + partycount + '</td></tr>';
    html += '<tr><th>Hirelings:</th><td>0</td></tr>';
  } else {
	html += '<tr><th>Party Size:</th><td>' + pItem.party + '</td></tr>';
    html += '<tr><th>Hirelings:</th><td>' + pItem.hmen + '</td></tr>';
  }
  
  html += '<tr><th>Max Character Level:</th><td>' + pItem.lvl_p + '</td></tr>';
  html += '<tr><th colspan="2">Players:</th></tr>';
  html += '<tr class="party-members"><td>' + pItem.char.name + '</td><td>' + getClassIcons( pItem.char.cls, pItem.char.lvl, false ) + '</td></tr>';
  for( i = 1; i < pItem.party - pItem.hmen; i ++ ) {
    var player = eval( 'pItem.p' + i );
    html += '<tr class="party-members"><td>' + ( player.name == null || player.name == "" ? '<i>unknown character</i>' : player.name ) + '</td><td>' + getClassIcons( player.cls, player.lvl, false ) + '</td></tr>';
  }
  html += '</table>';

  html += '</td>';
  html += '<td class="tbr"></td>';
  html += '</tr>';
  html += '<tr class="tt_br">';
  html += '<td><img src="' + gThemeMediaUri + 'common/tooltips/tc_bl.gif" /></td>';
  html += '<td class="tbb"></td>';
  html += '<td><img src="' + gThemeMediaUri + 'common/tooltips/tc_br.gif" /></td>';
  html += '</tr>';
  html += '</table>';

  return html.replace( /"/g, "'" );
}

