var USER_LEADERBOARD_ROW; // the current user record used by the leaderboard
var LEADERBOARD; // the LEADERBOARD displayed for this user
var MAX_LEADERBOARD_ROWS = 100; // Maximum number of rows to show on the leaderboard
var CURRENT_LEVEL; // the current level
var MIN_LEVEL = 1; // the minimum permissible level inclusive
var MAX_LEVEL = 25; // the maximum permissible level inclusive
var SAVE_PROGRESS_INTERVAL = 25; // Number of problems to solve before reminder.
var COMPETITION_OFFER_INTERVAL = 10;
var CHANGING_PROBLEM_LEVEL = 0; // semaphore to track changing level
var WORKSPACE_LOAD_ATTEMPTS = 0; // The number of times attempted to check
var MAX_WORKSPACE_LOAD_ATTEMPTS = 600; // The maximum number of attempts to try
var SUBSCRIPTION_REQUIRED = false; // Whether or not we ask for money
var CURRENT_PROBLEM;
var NEXT_PROBLEM;
var SELECTED_PROBLEM_TYPE;
var MAX_LEADERBOARD_INTERVAL = 1;
var RELOAD_LEADERBOARD_INTERVAL = MAX_LEADERBOARD_INTERVAL; // Seconds to wait to reload the leaderboard
var LEADERBOARD_LOADING; // Whether the leaderboard is done loading or not
var OPPONENT_ID; // The user ID of the potential opponent
var OPPONENT_INFO; // Info about the opponent
var COMPETITION_ID; // The ID of the competition this user is involved in
var CURRENT_USER_IS_CHALLENGER = false; // Whether or not the current user is the challenger
var COMPETITION_STATUS_UPDATING = false;
var EARNED_REWARD_ID;

// Adds callback for DOM ready
$(document).ready(practiceDomReady);

// Function to call when the DOM is ready for manipulation.
function practiceDomReady() {
  if (location.pathname == "/play") {
    setupPracticeControls();
  }
}

// Function to set up app for practice mode.
function setupPractice() {
  CURRENT_GAME_MODE = PRACTICE_MODE;
  switchStylesheet('practice');
  changeStatus(STATUS_ONLINE, true);
  setupWorkspace();
  setupProgressBar();
  getNewProblems();
  setupClassroom();
  LEADERBOARD_LOADING = false;
  reloadLeaderboard();  
  updatePracticeProgress();  
}

// Function called when the flash app is done loading.
function setupWorkspace() {
  if (WORKSPACE != null) {
    WORKSPACE.setPadding(100, 200, 300, 200);
    $("#loadingContainer").addClass('hidden');
    $("helpMessageContainer").hide();  
  }
  else {
    WORKSPACE_LOAD_ATTEMPTS++;
    if (WORKSPACE_LOAD_ATTEMPTS > MAX_WORKSPACE_LOAD_ATTEMPTS) {
      MODAL_SHOWN_REASON = "Flash Load Timeout";
      $("#flashNotInstalled").jqmShow();
    }
    else {
      setTimeout("setupWorkspace()", 100);
    }
  }
}

function newHelpMessage() {
  //alert("New help message set.");
  setTimeout(retrieveHelpMessage, 0);
  //alert("Carrot counter and image hidden, message shown.");
}

function retrieveHelpMessage() {
  var newHelpMessage = WORKSPACE.getHelpMessage();
  if ((newHelpMessage == "") || (newHelpMessage == null)) {
    return;
  }
  $("#helpMessage").html(newHelpMessage);
  $("#helpMessageContainer").show();
}

function clearHelpMessage() {
  //alert("Help message cleared.")
  $("#helpMessageContainer").hide();
}

// Function to set up the progressbar
function setupProgressBar() {
  $("#progressbar").progressbar({ value: 0 });
}



// Function to update the indicator for number of carrots earned and the
// number of problems solved as well as the random motivation message. This
// function also hides the instructions if the user has already earned some
// carrots.
function updatePracticeProgress() {
  if (typeof(USER.next_reward) == "undefined" || 
      USER.next_reward == false ||
      (USER.classroom && !USER.classroom.rewards_enabled)) {
    $('#progressContainer').hide();
  }
  else if (USER.points >= USER.next_reward.points_required) {
    $('#progressContainer').hide();
  }
  else {
    carrotsNeeded = USER.next_reward.points_required - USER.points;
    $('#carrots_needed').html(pluralize(carrotsNeeded, "more carrot"));
    percent = 100.0 * USER.points / USER.next_reward.points_required;
    $('#progressbar').progressbar('option', 'value', percent);  
    $('#next_reward').html(USER.next_reward.title);
    $('#progressContainer').show();
  }
  
  $("#userCarrots").html(USER.points);
  $(".userCarrotsColumn").show();
}

// Function to fetch a new leaderboard when the filters have been changed.
function setupClassroom() {
  if (USER.classroom) {
    $("#yourClassLeaderboardType").show();
    $("#yourClassLeaderboardType").attr("selected", true);
    $("#saveProgressParentEmail").hide();
    if (USER.username == null) {
      MODAL_SHOWN_REASON = "Initial Prompt for Classroom Users";
      $("#saveProgress").jqmShow();
    }
  }  
  else {
    $("#yourClassLeaderboardType").remove();
  }
}

// Function to repeatedly get the leaderboard
function reloadLeaderboard() {
  if (!LEADERBOARD_LOADING) {
    LEADERBOARD_LOADING = true;
    getLeaderboard();    
  }
  if (CURRENT_GAME_MODE == PRACTICE_MODE) {
    setTimeout("reloadLeaderboard()", RELOAD_LEADERBOARD_INTERVAL * 7500);
  }
}

function changeLeaderboardType() {
  getLeaderboard();
  trackEvent("Leaderboard", "Changed Type", $("#leaderboardType").val(), 1);
}

function changeLeaderboardDateType() {
  getLeaderboard();
  trackEvent("Leaderboard", "Changed Date", $("#leaderboardDateType").val(), 1);
}

// Function to get the leaderboard for the current user.
function getLeaderboard() {
  $("#filterContainer").show();
  params = {
    'leaderboard_type': $("#leaderboardType").val(),
    'date_type': $("#leaderboardDateType").val(),
    'user_id': USER.user_id
  };
  if (USER.classroom) {
    params['classroom_key'] = USER.classroom.key;
  }
  $.ajax({
    url: "/leaderboards",
    dataType: "json",
    data: params,
    success: leaderboardLoaded,
    error: leaderboardError
  });
}

// Callback for when the leaderboard has been fetched.
function leaderboardLoaded(json) {
  processLeaderboard(json[0]);
  processCompetition(json[1]);
}


// Function to process the leaderboard JSON retrieved on refresh.
function processLeaderboard(leaderboard) {
  RELOAD_LEADERBOARD_INTERVAL = MAX_LEADERBOARD_INTERVAL;
  newLeaderboard = [];
  
  for (i = 0; i < leaderboard.length; i++) {
    userArray = leaderboard[i];
    userDict = {
      userId: userArray[0],
      leaderboardName: userArray[1],
      status: userArray[2],
      ai: userArray[3],
      points: userArray[4]
    };
    if (userDict.userId == USER.user_id) {
      USER_LEADERBOARD_ROW = userDict;
      if (USER.username == null) {
        userDict.leaderboardName = "Me";
      }
      userDict.points += PENDING_POINTS;      
      USER.status = userDict.status;
      
      if ($(".jqmOverlay").length == 0) {
        changeStatus(STATUS_ONLINE, true);
      }
      else {    
        changeStatus(STATUS_BUSY, true);
      }
      
/*      if (USER.points != USER_LEADERBOARD_ROW.points) {
        label = "Carrot Counter (" + USER.points + ") Out of Sync with Leaderboard (" + USER_LEADERBOARD_ROW.points + ")";
        trackEvent("Debug", "Carrot Counter", label, 1); 
        USER.points = USER_LEADERBOARD_ROW.points;
        updatePracticeProgress();  
      }*/
    }
    newLeaderboard.push(userDict);      
  }
  
  LEADERBOARD = newLeaderboard;
  updateLeaderboard();
  
  $("#pickOpponentContainer").removeClass('hidden');
}

// In case leaderboard error fails back-off and reload slower and slower.
function leaderboardError() {
  RELOAD_LEADERBOARD_INTERVAL *= 2;
}

// Comparator used to sort users on the LEADERBOARD.
function leaderboardComparator(a, b) {
  return b.points - a.points;
}

// Updates the LEADERBOARD displayed on the page.
function updateLeaderboard() {
  LEADERBOARD.sort(leaderboardComparator);
  
  $("#leaderboard").html("");
  for (i = 0; i < Math.min(MAX_LEADERBOARD_ROWS, LEADERBOARD.length); i++) {
    rank = i + 1;
    if (LEADERBOARD[i].userId == USER.user_id) {
      addUserToLeaderboard(LEADERBOARD[i], "userLeaderboardRow");
    }
    else if (LEADERBOARD[i].ai) {
      addUserToLeaderboard(LEADERBOARD[i], "ai opponentLeaderboardRow");
    }
    else {
      addUserToLeaderboard(LEADERBOARD[i], "opponentLeaderboardRow");
    }
  }
  
  if ($(".userLeaderboardRow").length == 0) {
    for (i = 10; i < LEADERBOARD.length; i++) {
      rank = i + 1;
      if (LEADERBOARD[i].userId == USER.user_id) {
        $("#leaderboard").append("<tr><td>...</td></tr>");
        addUserToLeaderboard(LEADERBOARD[i], "userLeaderboardRow");
        break; 
      }
    }
  } 
  
  $(".opponentLeaderboardRow").click(opponentRowClick);
  
  LEADERBOARD_LOADING = false;
}

// Function to add a LEADERBOARD user to a LEADERBOARD row.
function addUserToLeaderboard(user, row_class) {
  $("#leaderboard").append("<tr id='" + user.userId + "' class='"+ row_class + "'><td>" +
                           "<div class='rank'>" + rank + ".</div>" +
                           "<div class='userStatus status_" + user.status + "'></div>" +
                           "<div class='name'>" + user.leaderboardName + "</div>" +
                           "<div class='points'>" + user.points + "</div>" +
                           "</td></tr>");
}

// Function to process the competitions JSON retrieved on refresh.
function processCompetition(competition) {
  if (!COMPETITION_STATUS_UPDATING && 
      competition != null && 
      competition != -1) {
    challenger = competition[1];
    challengee = competition[2];
    competition_status = competition[3];
    problem_type = competition[4];
    level = competition[5];
    
    if (challenger.user_id == USER.user_id && 
        $("#waitingForOpponent").is(":visible") &&
        COMPETITION_ID == competition[0]) {
      if (competition_status == STATUS_ACCEPTED) {
        $("#waitingForOpponent").jqmHide();
        OPPONENT_INFO = challengee;      
        CURRENT_USER_IS_CHALLENGER = true;
        cleanupPractice();
      }
      else if (competition_status == STATUS_REJECTED) {
        MODAL_SHOWN_REASON = "Challengee Rejecting";
        $("#waitingForOpponent").jqmHide();
        $("#rejectedChallenge").jqmShow();
      }
    }
    else if (challengee.user_id == USER.user_id) {
      if (competition_status == STATUS_OFFERED) {
        $(".opponentName").html(challenger.leaderboard_name);
        spacedProblemType = addSpaceBeforeCap(problem_type);
        $('.opponentLevel').html(spacedProblemType + " Level " + level);
        setOpponentAvatar(challenger.user_id);
        
        COMPETITION_ID = competition[0];    
        OPPONENT_INFO = challenger;      
        MODAL_SHOWN_REASON = "Being Challenged";
        $("#acceptOrRejectChallenge").jqmShow();
      }
      else if (competition_status == STATUS_CANCELLED &&
               $("#acceptOrRejectChallenge").is(":visible")) {  
        MODAL_SHOWN_REASON = "Challenger Cancelling";
        $("#acceptOrRejectChallenge").jqmHide();
        $("#cancelledChallenge").jqmShow();
      }
    }
  }  
}

function setOpponentAvatar(user_id) {
  opponentAvatarUrl = '/avatar/view?size=1&user=' + user_id;
  $(".opponentAvatar").attr('src', opponentAvatarUrl);
}

// Function when a user clicks on a potential playmate on the leaderboard.
function opponentRowClick(eventObject) {
  row = $(eventObject.target).parents('tr');
  MODAL_SHOWN_REASON = "Click on Leaderboard User";
  promptToChallenge(row);  
}

function promptToChallenge(row) {
  OPPONENT_ID = row.attr('id');
  setOpponentAvatar(OPPONENT_ID);

  opponentStatus = row.children('td').children('.userStatus');
  $(".opponentName").html(row.children('td').children('.name').html());
  
  if (opponentStatus.hasClass("status_" + STATUS_ONLINE)) {
    $("#confirmChallenge").jqmShow();
  }
  else if (opponentStatus.hasClass("status_" + STATUS_BUSY)) {
    $("#opponentBusy").jqmShow();    
  }
  else if (opponentStatus.hasClass("status_" + STATUS_OFFLINE)) {
    $("#opponentOffline").jqmShow();    
  }
}

// Function called if the user cancels their current challenge.
function challengeCancelled() {
  changeCompetitionStatus(STATUS_CANCELLED);
}

// Function called if the user rejects their current challenge.
function challengeRejected() {
  changeCompetitionStatus(STATUS_REJECTED);
}

// Function called if the user accepts their current challenge.
function challengeAccepted() {
  if ($("#acceptOrRejectChallenge").is(":visible")) {
    changeCompetitionStatus(STATUS_ACCEPTED);
    MODAL_SHOWN_REASON = "Accepted Challenge";
    $('#acceptOrRejectChallenge').jqmHide();  
    $("#synchronizingWithOpponent").jqmShow();
    CURRENT_USER_IS_CHALLENGER = false;
    // Average case delay is half the reload time of the leaderboard.
    synchronizationDelay = (RELOAD_LEADERBOARD_INTERVAL * 1000) / 2;
    setTimeout("cleanupPractice()", synchronizationDelay);
  }
}

function cleanupPractice() {
  $("#progressContainer").hide();
  $("#synchronizingWithOpponent").jqmHide();  
  WORKSPACE.hideProblem();
  setupCompetition();
}

// Function change the status of the competition.
function changeCompetitionStatus(new_status) {
  COMPETITION_STATUS_UPDATING = true;
  params = {
    competition_id: COMPETITION_ID,
    new_status: new_status
  };

  $.postJSON('/competitions/change_status', params, competitionStatusChanged); 
}

function competitionStatusChanged() {
  COMPETITION_STATUS_UPDATING = false;
}

// Function called after the user confirms they want to send a challenge.
function challengeConfirmed() {
  MODAL_SHOWN_REASON = "Confirmed Challenge";
  $("#confirmChallenge").jqmHide();
  $("#waitingForOpponent").jqmShow();
  
  params = {
    problem_type: SELECTED_PROBLEM_TYPE,
    level: CURRENT_LEVEL,
    opponent_id: OPPONENT_ID
  };
  
  $.postJSON('/competitions/challenge_opponent', params, challengeResponse); 
}

function challengeResponse(competition_id) {
  COMPETITION_ID = competition_id;
}

// Function to set up the onclick and style handlers for the level buttons.
function setupPracticeControls() {
	$('#easierLink').click(levelDown);
	$('#harderLink').click(levelUp);
	
  $('.gameButton').click(gameButtonClick);

  $('#challengeConfirmed').click(challengeConfirmed);
  $('#challengeCancelled').click(challengeCancelled);
  $('#challengeAccepted').click(challengeAccepted);
  $('#challengeRejected').click(challengeRejected);
  
  $('#leaderboardType').change(changeLeaderboardType);
  $('#leaderboardDateType').change(changeLeaderboardDateType);
}

// Function called when the user wants to manually increase the current level.
function levelUp() {
  if (CURRENT_LEVEL < MAX_LEVEL) {
    changeLevel(CURRENT_LEVEL + 1);
    trackEvent("Controls", "Harder Problem", SELECTED_PROBLEM_TYPE, CURRENT_LEVEL);
  }
    
  return false;
}

// Function called when the user wants to manually decrease the current level.
function levelDown() {
  if (CURRENT_LEVEL > MIN_LEVEL) {
    changeLevel(CURRENT_LEVEL - 1);
    trackEvent("Controls", "Easier Problem", SELECTED_PROBLEM_TYPE, CURRENT_LEVEL);
  } 
  
  return false;
}

// Function to change the level to a new level
function changeLevel(newLevel) {
  if (WORKSPACE) WORKSPACE.hideProblem();
  $("#loadingContainer").removeClass('hidden');
  
  updateLevel(newLevel);
    
  if (CHANGING_PROBLEM_LEVEL == 0) {
    CHANGING_PROBLEM_LEVEL++;
    params = {
      problem_type: SELECTED_PROBLEM_TYPE,
      level: newLevel
    };

    $.postJSON('/performance/change_problem_level', params, levelChanged); 
  }
}

// Callback after the problem level has been successfully changed.
function levelChanged(json) {
  CHANGING_PROBLEM_LEVEL--;
  if (CURRENT_LEVEL == json[0].level) {
    setCurrentProblem(json[0]);
    NEXT_PROBLEM = json[1];
  }
  else {
    changeLevel(CURRENT_LEVEL);
  }
}

// Function to handle clicks on buttons to change the problem type.
function gameButtonClick(eventObject) {
  if ($(eventObject.target).hasClass("subscriptionRequired") && 
      !USER.subscribed &&
      !freeClassroomUse()) {
    MODAL_SHOWN_REASON = "Due to Game Button Click";        
    showUpsell();
  }
  else {
    if (WORKSPACE) WORKSPACE.hideProblem();
    $("#loadingContainer").removeClass('hidden');
    SELECTED_PROBLEM_TYPE = eventObject.target.id;  
    saveProblemType();
    getNewProblems();
    trackEvent("Controls", "Change Problem Type", SELECTED_PROBLEM_TYPE, 1);    
  }
}

function getNewProblems() {
  $.getJSON("/problems/get_new_problems", {}, receivedNewProblems);  
}

// Function to save problem type in cookie
function saveProblemType() {
  options = {
    expires: 365 * 10
  };
  $.cookie('problem_type', SELECTED_PROBLEM_TYPE, options);
}

// Callback to receive new problem after changing problem type
function receivedNewProblems(json) {
  SELECTED_PROBLEM_TYPE = json[0].problem_type;
  updateLevel(json[0].level);
  selectProblemButton();
  setCurrentProblem(json[0]);
  NEXT_PROBLEM = json[1];
}

// Animation that happens to the current level indicator when it goes up by one.
jQuery.fn.explode = function() {
  this.
    animate({ fontSize: "28px" }, 500).
    animate({ fontSize: "20px" }, 500);
}

// Function to update the current level indicator on the page and to call the
// appropriate animation if the level increases.
function updateLevel(level) {
  CURRENT_LEVEL = level;

  spacedProblemType = addSpaceBeforeCap(SELECTED_PROBLEM_TYPE);
  $('.level').html(spacedProblemType + " Level " + level);
  $('.level').show();

  if (level > MIN_LEVEL) {
    $('#easierLink').removeClass('hidden');
  }
  else {
    $('#easierLink').addClass('hidden');
  }
  
  if (level < MAX_LEVEL) {
    $('#harderLink').removeClass('hidden');
  }
  else {
    $('#harderLink').addClass('hidden');
  }
  
  $('#difficultyButtonsContainer').show();
}

// Function to pluralize the given word if it is 0 or more than 1.
function pluralize(count, word) {
  if (count == 1) return count + " " + word;
  else return count + " " + word + "s";
}

// Function called to switch the current gameName (e.g. 'Addition') to another
// gameName.
function selectProblemButton() {
  $('.gameButton').addClass('buttonSelected');
  $('.gameButton').removeClass('buttonSelected');
  
  newButton = $("div#" + SELECTED_PROBLEM_TYPE);
  newButton.addClass('buttonSelected');
  
  $("#controlsContainer").removeClass('hidden');
}

// Function to set a new problem for the current user.
function setCurrentProblem(json) {
  resetCarrotCounter();
  INPUT_QUEUE = [];
  CURRENT_PROBLEM = json;
  setProblem();
}

// Checks to make sure WORKSPACE has been set before actually running the problem
function setProblem() {
  if (WORKSPACE == null) {
    setTimeout("setProblem()", 100);
  }
  else {
    runProblem(WORKSPACE, CURRENT_PROBLEM);
    $("#loadingContainer").addClass('hidden');
  }
}

function practiceProblemInput(eventObject) {
  if (typeof(eventObject.getInput()) == "object") {
    description = "Clicked on " + eventObject.getInput().getText();
  }
  else {
    description = "Typed in " + eventObject.getInput();
  }
  
  input = {
   correct: (eventObject.getIsCorrect() == 1),
   description: description,
   label: eventObject.getLabel(),
   points: eventObject.getPoints(),
   created: (new Date()).getTime() / 1000
  };
  
  INPUT_QUEUE.push(input);
  PENDING_POINTS += input.points;
  USER.points += input.points;
  if (USER_LEADERBOARD_ROW) {
    USER_LEADERBOARD_ROW.points += input.points;
    updateLeaderboard();
  }
  updatePracticeProgress();
}

// Event listener for when all of the problem grids for the current problem
// the user is solving has been finished. 
function practiceProblemFinishedAfterTimeout() {
  if (NEXT_PROBLEM == null) {
    setTimeout(practiceProblemFinishedAfterTimeout, 1000);
    $("#loadingContainer").removeClass('hidden');    
  }
  else {

    $("#loadingContainer").addClass('hidden');     
    if (NEXT_PROBLEM.level > CURRENT_PROBLEM.level) {
      $(".level").explode();
      updateLevel(NEXT_PROBLEM.level);
    }
    trackEvent("Problem", "Finished Practice Problem", SELECTED_PROBLEM_TYPE, PENDING_POINTS);

    params = {
      problem_type: SELECTED_PROBLEM_TYPE,
      problem: $.toJSON(CURRENT_PROBLEM),
      input_queue: $.toJSON(INPUT_QUEUE),
      user_vars: WORKSPACE.getEncodedUserVars()
    };

    USER.points += PENDING_POINTS;
    setCurrentProblem(NEXT_PROBLEM);
    NEXT_PROBLEM = null;

    $.postJSON("/problems/problem_finished", params, nextProblemReceived);
    USER.problems_solved++;

    showRelevantFinishedDialog();
    updatePracticeProgress();
  }
}

function showRelevantFinishedDialog() {
  if ((USER.next_reward) &&
      (USER.points >= USER.next_reward.points_required) &&
      (!USER.classroom || USER.classroom.rewards_enabled)) {
    showReward();
  }  
  else if ((USER.username == null) &&
           (USER.problems_solved % SAVE_PROGRESS_INTERVAL) == 0) {
    MODAL_SHOWN_REASON = "Save Progress Reminder";
    $("#saveProgress").jqmShow();
  }
  else if ((USER.problems_solved % COMPETITION_OFFER_INTERVAL) == 0) {
    promptToChallengeRandomOpponent();
  }
}

function promptToChallengeRandomOpponent() {
  eligibleOpponents = $('.status_' + STATUS_ONLINE);
  if (eligibleOpponents != null) {
    MODAL_SHOWN_REASON = "Recurring Prompt to Challenge Random Opponent";
    promptToChallenge(eligibleOpponents.parent().parent('.opponentLeaderboardRow:random'));
  }
}

// Function to fetch the reward URL for the reward and show it
function showReward() {
  EARNED_REWARD_ID = USER.next_reward.reward_id
  $("#rewardTitle").html(USER.next_reward.title);
  MODAL_SHOWN_REASON = "Reward Earned";
  $("#reward").jqmShow();
  USER.next_reward = false;
}

// Callback for the response to the problem being finished.
function nextProblemReceived(json) {
  NEXT_PROBLEM = json;
  resetCarrotCounter();
}

function resetCarrotCounter() {
  USER.points -= PENDING_POINTS;
  PENDING_POINTS = 0;  
  updatePracticeProgress();  
}
