var g_buildingStartVal, g_floorStartVal;
var g_roomsArr = ["B,2,A","B,3,Breakroom","B,5,A","B,5,Breakroom","B,8,Breakroom","B,8,Fishbowl","B,12,Breakroom","B,12,E","B,12,F","C,2,Auditorium","C,2,Cafe B","C,3,A","C,3,B","C,3,Breakroom","C,3,D","C,4,Breakroom","C,4,N","C,5,B","C,5,Breakroom","C,5,C","C,5,D","C,5,E","C,5,F","C,5,G","C,5,H","C,5,I","C,5,J","C,5,K","C,5,L","C,5,M","C,5,N","C,5,O","C,5,P","C,5,Q","C,6,Breakroom","C,7,A","C,7,B","C,7,Breakroom","C,8,Breakroom","C,9,A","C,9,B","C,9,Breakroom","C,10,Breakroom","C,11,A","C,11,B","C,11,Bluesky","C,11,Breakroom","C,12,Breakroom","C,14,Breakroom","C,15,Breakroom","C,16,Breakroom","C,17,Breakroom","C,18,Breakroom","C,19,Breakroom","C,20,Breakroom","C,21,Breakroom","C,22,Breakroom","D,2,A","D,2,B","D,2,C","D,3,Breakroom","D,4,A","D,4,Breakroom","D,5,A","D,5,Breakroom","D,5,C","D,5,D","D,5,E","D,5,F","D,5,I","D,5,J","D,6,A","D,6,B","D,6,Breakroom","D,6,C","D,7,A","D,7,B","D,7,Breakroom","D,8,Breakroom","D,9,Breakroom","D,10,A","D,10,B","D,10,Breakroom","D,10,C","D,11,A","D,11,B","D,11,Breakroom","D,11,C","D,12,A","D,12,B","D,12,Breakroom","D,12,C","D,14,B","D,14,Breakroom","D,14,C","D,15,Breakroom","D,16,B","D,16,Breakroom","D,17,Breakroom","D,18,B","D,18,Breakroom","D,19,A","D,19,B","D,19,Breakroom"];
var g_windowFocused, g_postsAddedSinceFocus = 0;

$(document).ready(function() {
  // webkit-specific code
  if($.browser.webkit) {
    $('button').css({ 'font-weight': 'bold' });
  }
  
  var rooms = parseRoomsArr(g_roomsArr);
  populateLocationCombos(rooms);
  
  setupSelectHandlers();
  
  // setup click handler for Add Food button
  $('#add-food').click(openNewFoodForm);
  
  // setup resize handler
  $(window).resize(resizePage);
  resizePage();
  
  // setup keyboard handler
  $('.new-food input, .new-food select').keypress(function(e) {
    if(e.which == 13) {
      validateAndSubmit();
    }
  });
  
  setupWindowFocusHandlers();
  
  loadInitialData();
});

// setup focus & blur handlers for select elements
function setupSelectHandlers() {
  $('select').focus(function() {
    $(this).parent().removeClass('focus');
    $(this).parent().addClass('focus');
  });
  $('select').blur(function() {
    $(this).parent().removeClass('focus');
  });
}

function setupWindowFocusHandlers() {
  var focusWindow = function() {
    g_windowFocused = true;
    
    if(g_postsAddedSinceFocus > 0) {
      g_postsAddedSinceFocus = 0;
      
      // we can't directly do:
      //  - document.title = <title>;
      // because of what appears to be a bug in chrome:
      // http://stackoverflow.com/questions/2952384/changing-the-window-title-when-focussing-the-window-doesnt-work-in-chrome
      window.setTimeout(function () { $(document).attr('title', 'HDFNS'); }, 200);
      
      addTimerToRemoveNewIndicators();
    }
    
    $(window).unbind('mousemove', focusWindow);
  };
  
  $(window).focus(focusWindow);
  $(window).blur(function() {
    g_windowFocused = false;
    $(window).bind('mousemove', focusWindow);
  });
}

function loadInitialData() {
  $.ajax({
    url: 'main/getNoms'
    ,success: function(data) {
      addFoodFromServer(data, true);
      setupPollingTimer();
    }
    ,dataType: 'json'
  });
}

// takes as input a [roomsArr] which is an array of strings, each
// with the following format: "<building> <floor> <room>"
//  - building: capital letter (A-Z)
//  - floor: positive number (2+)
//  - room: any non-spaced string of letters
// and outputs an object map of buildings, each of which holds
// an object map of floors, each of which holds an array of rooms
//  ex: { "B": { "2": [ "C", "Breakroom" ], ... }, ... }
function parseRoomsArr(roomsArr) {
  var rooms = {};
  for(var i = 0, len = roomsArr.length; i < len; ++i) {
    // this will split by a comma, so [roomLoc] will hold an array of 3 strings
    var roomLoc = roomsArr[i].split(',');
    if(!rooms[roomLoc[0]]) {
      rooms[roomLoc[0]] = {};
    }
    
    if(!rooms[roomLoc[0]][roomLoc[1]]) {
      rooms[roomLoc[0]][roomLoc[1]] = [];
    }
    
    rooms[roomLoc[0]][roomLoc[1]].push(roomLoc[2]);
  }
  
  // sort each set of rooms
  for(var b in rooms) {
    var building = rooms[b];
    for(var f in building) {
      building[f] = building[f].sort();
    }
  }
  
  return rooms;
}

// takes as input a [roomsObj] object whose format is described
// by the output of parseRoomsArr()
function populateLocationCombos(roomsObj) {
  var b = $('.input-building'),
      f = $('.input-floor'),
      r = $('.input-room');
  
  // populate the initial set of buildings
  for(var i in roomsObj) {
    b.append('<option value="' + i + '">' + i + '</option>');
  }
  
  // setup handlers for if the user changes the building and floor
  //
  // we can't just rely on the change event because the user
  // can use the arrow keys to change the value of a combo,
  // which apparently doesn't fire a change event (at least in FF),
  // so to cover this usecase, we save the start value on focus and
  // if the value has changed on blur then we invoke the change handler
  //
  // building
  b.focus(function() {
    g_buildingStartVal = b.val();
  });
  b.blur(function() {
    if(b.val() != g_buildingStartVal) {
      onBuildingChanged(roomsObj, b, f, r, b.val());
    }
  });
  b.change(function(e) {
    onBuildingChanged(roomsObj, b, f, r, e.currentTarget.options[e.currentTarget.selectedIndex].value);
  });
  
  // floor
  f.focus(function() {
    g_floorStartVal = f.val();
  });
  f.blur(function() {
    if(f.val() != g_floorStartVal) {
      onFloorChanged(roomsObj, b, f, r, f.val());
    }
  });
  f.change(function(e) {
    onFloorChanged(roomsObj, b, f, r, e.currentTarget.options[e.currentTarget.selectedIndex].value);
  });
}

function onBuildingChanged(roomsObj, b, f, r, building) {
  // reset so if we trigger blur again without focusing it won't think it has changed
  g_buildingStartVal = building;
  
  // clear the floor and room combos
  clearCombo(f);
  clearCombo(r);
  
  // if the user selected the empty option, then return
  if(!building || building.length == 0 || !roomsObj[building]) {
    return;
  }
  
  // add floors in this building
  for(var j in roomsObj[building]) {
    f.append('<option value="' + j + '">' + j + '</option>');
  }
}

function onFloorChanged(roomsObj, b, f, r, floor) {
  // reset so if we trigger blur again without focusing it won't think it has changed
  g_floorStartVal = floor;
  
  // clear the room combo
  clearCombo(r);
  
  // if the user selected the empty option, then return
  var building = b.val();
  if(!floor || floor.length == 0 || !roomsObj[building] || !roomsObj[building][floor]) {
    return;
  }
  
  // add the rooms on this floor
  var rooms = roomsObj[building][floor];
  for(var k = 0; k < rooms.length; ++k) {
    r.append('<option value="' + rooms[k] + '">' + rooms[k] + '</option>');
  }
}

function clearCombo(c) {
  c.empty();
  c.append('<option value=""></option>');
}

function resizePage() {
  var height = $('#food-list-inner').outerHeight(true);
  var minHeight = $(window).height() - 75;
  if(height < minHeight) {
    height = minHeight;
  }
  $('#food-list-outer').height(height);
};

// this function adds the food specified by [data] to the list;
// if [firstLoad] is false, then it is being called by the polling
// timer, so we will do a couple things differently:
//  - ensure food is prepended before all other food
//  - add a temporary enclosing div around all food so it can be animated in
//  - ensure that new timers for updating times are only added for the new food
//  - color the backgrounds a light blue for a few seconds
function addFoodFromServer(data, firstLoad) {
  var timeWhenFoodWasAdded = new Date().getTime();
  
  if(data.length > 0) {
    var html = [];
    
    var isFirstPost = ($('#no-food').length > 0);
    if(!firstLoad) {
      if(isFirstPost) {
        $('#no-food').remove();
      }
      html.push('<div id="animation-ct">');
    }
    
    for(var i = 0; i < data.length; ++i) {
      if(i > 0) {
        html.push('<div class="food-div"></div>');
      }
      
      html.push(
        '<div class="food">',
          '<div class="line">',
            '<label>what:</label>',
            '<div class="desc">', data[i].desc, '</div>',
          '</div>',
          '<div class="line">',
            '<label>where:</label>',
            '<div class="loc">',
              data[i].loc
      );
    if(!firstLoad) {
      html.push('<div class="new-indicator"></div>');
      if(g_windowFocused) {
        // we can add a timer to remove the new indicators immediately
        addTimerToRemoveNewIndicators();
      }
      else {
        // we will wait to remove the new indicators until the window is focused
      }
    }
      html.push(
            '</div>',
          '</div>',
          '<div class="line">',
            '<label>when:</label>',
            '<div class="time">',
              '<span>', getTimeForPost(data[i].posted, timeWhenFoodWasAdded), '</span>',
              '<input type="hidden" value="', data[i].posted, '" />',
            '</div>',
          '</div>',
          '<input type="hidden" value="', data[i].id, '" />',
        '</div>'
      );
    }
    
    if(!firstLoad) {
      // check to make sure there are other food posts before we add a divider
      if(!isFirstPost) {
        html.push('<div class="food-div"></div>');
        html.push('</div>');
      }
    }
    
    $('.new-food-div').after(html.join(''));
    
    if(firstLoad) {
      addTimersToPosts($('#food-list-inner'), timeWhenFoodWasAdded);
    }
    else {
      // animate the animation-ct, then remove it
      $('#animation-ct').slideDown(500, function() {
        addTimersToPosts($('#animation-ct'), timeWhenFoodWasAdded);
        $('#animation-ct').replaceWith($('#animation-ct').children());
        resizePage();
        
        // if the window does not have focus, then alert the user by changing
        // the title
        if(!g_windowFocused) {
          g_postsAddedSinceFocus += data.length;
          document.title = 'HDFNS (' + g_postsAddedSinceFocus + ')';
        }
      });
    }
  }
  else if(firstLoad) {
    $('#food-list-inner').append('<div id="no-food">No food has been posted yet today...</div>');
  }
  
  resizePage();
}

function addTimerToRemoveNewIndicators() {
  $(document).oneTime('5s', function() {
    // we only select new indicators inside food that is top-level, meaning
    // food that is in the process of being animated in (i.e. food that is currently
    // rendered inside the animation-ct div) will not be included in this list
    $('#food-list-inner > .food .new-indicator').animate({
      'background-color': '#ffffff'
    }, {
      duration: 500
      ,complete: function() {
        $('.food-list-inner > .food .new-indicator').remove();
      }
    });
  });
}

// this function adds timers for data that already exists to food posts;
// the time displayed for each post is generally going to be in terms of
// # of minutes, so the goal is to update per minute, and we accomplish this
// by adding the update timer for each post 1 second after it should change
// minutes
function addTimersToPosts(ct, timeWhenFoodWasAdded) {
  ct.children('.food').each(function(i, el) {
    // ignore the new food form
    if($(this).hasClass('new-food')) return;
    
    var timeEl = $(this).find('.time span');
    var inputEl = $(this).find('.time input');
    var utimeForPost = Number(inputEl.val());
    
    // holds the time (in ms) before the next full minute in the time difference
    // passed in from the server (+ 1 second to allow it time to change)
    var msUntilNextMinute = (60 - (Math.round(utimeForPost / 1000) % 60) + 1) * 1000;
    
    $(this).oneTime(msUntilNextMinute, function() {
      // at this point, we've hit a new minute, so we will:
      //  1) update the UI to the new time
      //  2) set an infinite timer with 1min intervals to continually update the UI
      timeEl.html(getTimeForPost(utimeForPost, timeWhenFoodWasAdded));
      $(this).everyTime('60s', function() {
        timeEl.html(getTimeForPost(utimeForPost, timeWhenFoodWasAdded));
      });
    });
  });
}

// this function calculates the total relative time for when a food post
// was added; there are 2 cases:
//  - called for food that was returned from the server (on page load, or from polling)
//    - [serverDiff] represents the time difference from when the food
//      was first posted and added to the DB and when it was fetched
//      from the server
//    - [initialTime] represents the time when the food was added on the client-side,
//      so the client time difference can be calculated and added to the [serverDiff]
//      for the total relative time
//  - called for food that was added by the user (and added dynamically without
//    reloading the page)
//    - [serverDiff] is 0
//    - [initialTime] is the time when it was added by the user
function getTimeForPost(serverDiff, initialTime) {
  serverDiff = Number(serverDiff);
  var msdiff = serverDiff + ((new Date()).getTime() - initialTime),
      sdiff = Math.floor(msdiff / 1000);
  if(sdiff < 15) {
    return 'a few seconds ago';
  }
  else if(sdiff < 60) {
    return 'less than a minute ago';
  }
  
  var mdiff = Math.floor(sdiff / 60);
  if(mdiff < 60) {
    return mdiff + ' minute' + (mdiff > 1 ? 's' : '') + ' ago';
  }
  else if(mdiff < 65) {
    return 'about an hour ago';
  }
  
  var hdiff = Math.floor(mdiff / 60);
  if(hdiff < 4) {
    return hdiff + ' hour' + (hdiff > 1 ? 's' : '') + ' ago';
  }
  
  return 'several hours ago';
}

// this function sets up an infinite timer which polls the server
// for new food every minute
function setupPollingTimer() {
  $(document).everyTime('60s', function() {    
    // get the most recent entry
    var mostRecentPost = $('.new-food').next().next(),
        mostRecentId;
    if(mostRecentPost.length > 0) {
      mostRecentId = mostRecentPost.children('input').val();
    }
    
    // poll the server
    $.ajax({
      url: 'main/getNew'
      ,type: 'POST'
      ,data: { mostRecent: mostRecentId }
      ,success: function(data) {
        addFoodFromServer(data, false);
      }
      ,dataType: 'json'
    });
  });
}

function validateAndSubmit() {
  blurCombos();
  var isValid = validateInput($('.input-room'));
  isValid &= validateInput($('.input-floor'));
  isValid &= validateInput($('.input-building'));
  isValid &= validateInput($('.input-desc'));
  
  if(isValid) {
    requestToAddFood();
  }
}

function openNewFoodForm() {
  if(!$('.new-food').is(':visible')) {
    $('.new-food-div').show();
    $('.new-food').slideDown(200, function() {
      resizePage();
    });
  }
  
  // focus first input
  $('.new-food input:first').focus();
}

function closeNewFoodForm() {
  clearInputs();
  $('.new-food-div').hide();
  $('.new-food').slideUp(200, function() {
    resizePage();
    
    $('.input-desc').removeClass('required');
    $('.input-building-ct').removeClass('required');
    $('.input-floor-ct').removeClass('required');
    $('.input-room-ct').removeClass('required');
    $('.error').hide();
  });
}

function clearInputs() {
  $('.input-desc').val('');
  $('.input-building').val('');
  $('.input-floor').val('');
  $('.input-room').val('');
  blurCombos();
}

function blurCombos() {
  $('.input-building').blur();
  $('.input-floor').blur();
  $('.input-room').blur();
}

function requestToAddFood() {
  $('.loading-graphic').show();
  $('.error').hide();
  
  var desc = $('.new-food').find('.desc').children().first().val(),
      loc = $('.input-building').val() + ',' + $('.input-floor').val() + ',' + $('.input-room').val();
  $.ajax({
    url: 'main/add'
    ,type: 'POST'
    ,data: { desc: desc, loc: loc }
    ,success: function(data) {
      if(data.success) {
        $('.loading-graphic').hide();
        addFoodLocally(data.id);
      }
      else {
        showError(data.reason);
      }
    }
    ,error: showError
    ,dataType: 'json'
  });
}

function showError(reason) {
  $('.loading-graphic').hide();
  
  if(reason && reason.status) {
    $('.error').html('Error: Server returned ' + reason.status + ' status code');
  }
  else {
    $('.error').html(reason || 'Error occurred while attempting to add food');
  }
  
  $('.error').show();
}

// this function adds (renders) food that was added via the form
function addFoodLocally(id) {
  var foodToAddEl = $('.new-food');
  var foodToAddDivEl = $('.new-food-div');
  
  var newFoodEl = foodToAddEl.clone().hide();
  newFoodEl.find('.desc').parent().remove();
  newFoodEl.find('.loc').parent().remove();
  var newFoodDivEl = foodToAddDivEl.clone().hide();
  
  // remove the "no-food" div if it's present
  var isFirstPost = ($('#no-food').length > 0);
  if(isFirstPost) {
    $('#no-food').remove();
  }
  
  // replace input for location
  var locEl = foodToAddEl.find('.loc'),
      locStr = $('.input-building').val() + ' ' + $('.input-floor').val() + ' ' + $('.input-room').val(),
      clonedLocLine = locEl.parent().clone();
  newFoodEl.prepend(locEl.parent());
  foodToAddEl.prepend(clonedLocLine);
  foodToAddEl.find('.loc').html(locStr);
  
  // replace input for description
  var descEl = foodToAddEl.find('.desc'),
      descStr = descEl.children().first().val(),
      clonedDescLine = descEl.parent().clone();
  newFoodEl.prepend(descEl.parent());
  foodToAddEl.prepend(clonedDescLine);
  foodToAddEl.find('.desc').html(descStr);
  
  // add timestamp
  var time = (new Date()).getTime();
  foodToAddEl.append([
    '<div class="line">',
      '<label>when:</label>',
      '<div class="time">',
        '<span>', getTimeForPost(0, time), '</span>',
        '<input type="hidden" value="', 0, '" />',
      '</div>',
    '</div>',
    '<input type="hidden" value="', id, '" />'
  ].join(''));
  
  // set interval to update the time
  foodToAddEl.everyTime('60s', function() {
    var timeEl = $(this).find('.time span');
    timeEl.html(getTimeForPost(0, time));
  });
  
  // remove submit button
  foodToAddEl.find('.submit').parent().remove();
  
  // clean up classes
  foodToAddEl.removeClass('new-food');
  if(!isFirstPost) {
    foodToAddDivEl.addClass('food-div').removeClass('new-food-div');
  }
  else {
    // if it's the first post, we don't need a div following it
    foodToAddDivEl.remove();
  }
  
  // prepend original html for adding more food
  $('#food-list-inner').prepend(newFoodDivEl).prepend(newFoodEl);
  clearInputs();
}

function validateInput(el) {
  if(el.val().length == 0) {
    if(el[0].nodeName === 'SELECT') {
      el.parent().addClass('required');
    }
    else {
      el.addClass('required');
    }
    el.focus();
    return false;
  }
  
  if(el[0].nodeName === 'SELECT') {
    el.parent().removeClass('required');
  }
  else {
    el.removeClass('required');  
  }
  return true;
}
