var currentlySelectedLocation = 0;
var currentlySelectedLocationText = null;
var locationNodes = null;

var weatherData = null;

// Menu items
var cursorModeMenuItem = null;
var tabModeMenuItem = null;

// Menu id's
var CMD_CURSOR_MODE = 10;
var CMD_TAB_MODE = 11;

// "Layman's terms" for forecast array indices
var TODAY = 0;
var TOMORROW = 1;
var DAY_AFTER_TOMORROW = 2;
var currentlySelectedForecast = TODAY;

window.onload = init;

// Initializes the widget
function init() {
  createMenu();

  window.menu.showSoftkeys();

  // Store the locations
  var locationListElement = document.getElementById("locationList");
  locationNodes = locationListElement.getElementsByTagName("li");

  // Enable tab mode navigation
  widget.setNavigationEnabled(false);

  // Call keyPress function when a key gets pressed
  document.onkeypress = keyPress;
  // Highlight the initial location
  updateLocationHighlight(-1, 0);

  // Hide the forecast view (the location list is the first view)
  document.getElementById("forecast").style.display = "none";
}

// Creates the main menu
function createMenu() {
  // Create the menu items
  cursorModeMenuItem = new MenuItem("Cursor mode", CMD_CURSOR_MODE);
  tabModeMenuItem = new MenuItem("Tab mode", CMD_TAB_MODE);

  // Assign callback functions to menu items
  cursorModeMenuItem.onSelect = onMenuItemSelected;
  tabModeMenuItem.onSelect = onMenuItemSelected;

  // Tab mode is the default so let's add cursor mode item to the main menu
  window.menu.append(cursorModeMenuItem);
}

// Gets called when a menu item is selected
function onMenuItemSelected(menuId) {
  switch (menuId) {
    case CMD_CURSOR_MODE:
      // Enable cursor mode navigation
      widget.setNavigationEnabled(true);
      window.menu.append(tabModeMenuItem);
      window.menu.remove(cursorModeMenuItem);
      break;
    case CMD_TAB_MODE:
      // Enable tab mode navigation
      widget.setNavigationEnabled(false);
      window.menu.append(cursorModeMenuItem);
      window.menu.remove(tabModeMenuItem);
      break;
  }
}

// Fetches RSS data (XML) from external server
function fetch(locationCode, locationText) {
  // If locationCode is null, the user wants to enter a custom code
  if (locationCode == null) {
    locationCode = prompt("Enter the location code "
      + "(e.g. EUR|FI|FI007|OULU for Oulu, Finland):", "");
    // Only real input is accepted
    if (locationCode == null || locationCode.trim().length == 0) {
      return;
    }
  }
  currentlySelectedLocationText = locationText;
  var req = createXMLHttpRequest();
  if (req) {
    var url = "http://rss.accuweather.com/rss/liveweather_rss.asp?metric=1&locCode=" + locationCode;

    loadXMLDoc(req, url);
  }
}

// Creates an XMLHttpRequest object
function createXMLHttpRequest() {
  var req = null;

  try {
    req = new XMLHttpRequest();
    // Make sure that the browser supports overrideMimeType
    if (typeof req.overrideMimeType != "undefined") {
      req.overrideMimeType("text/xml");
    }
  } catch (ex) {
    req = null;
  }

  return req;
}

// Loads target XML document (url) into XMLHttpRequest
function loadXMLDoc(req, url) {
  // Register a callback function which gets called when the request state changes
  req.onreadystatechange = function() {
    processStateChange(req);
  };
  // Open an asynchronous (asyncFlag = true) request to the specified URL
  req.open("GET", url, true);
  // Transmit the request
  req.send(null);
}

// Processes state changes of XMLHttpRequest
function processStateChange(req) {
  // Request states are 0 through 4, where 4 equals complete
  if (req.readyState == 4) {
    // Server returns numeric code 200 for "OK".
    if (req.status == 200) {
      // Parse RSS data and display the forecast for today.
      weatherData = parseRSSData(req);
      // If weatherData remains null, the location code provided was invalid.
      if (weatherData == null) {
        alert("Invalid location code.");
        return;
      }
      displayForecast(TODAY);
    } else {
      alert("There was a problem retrieving the XML data.");
    }
  }
}

// Parses RSS data residing in XMLHttpRequest
function parseRSSData(req) {
  var rssData = [];
  var items = req.responseXML.getElementsByTagName("item");
  // There are four items (three forecasts and one info item) in a correct
  // forecast. If there is only one item (the info item), the location code was invalid.
  if (items.length == 1) {
    return null;
  }

  for (var i = 0; i < items.length; i++) {
    var nodedata = [];
    for (var j = 0; j < items[i].childNodes.length; j++) {
      var childNode = items[i].childNodes[j];

      // Text nodes and empty nodes are dismissed
      if (childNode.nodeType != 3 && childNode.childNodes.length > 0) {
        var childNodeName = childNode.nodeName;
        var childNodeData = childNode.childNodes[0].data;
        nodedata[childNodeName] = childNodeData;
      }
    }

    rssData.push(nodedata);
  }

  return rssData;
}

// Displays the weather forecast (RSS) for target day as HTML
function displayForecast(targetDay) {
  if (document.getElementById("locations").style.display != "none") {
    showForecastElement();
  }

  // Update the location heading
  var locationHeadingElement = document.getElementById("locationHeading");
  locationHeadingElement.removeChild(locationHeadingElement.childNodes[0]);
  locationHeadingElement.appendChild(document.createTextNode(currentlySelectedLocationText));

  // Update visibility of the navigation arrows
  updateNavigationArrows(targetDay);

  // Grab the forecast ("description" element) for target day
  var description = weatherData[targetDay]["description"];
  var parsedDescription = parseDescription(description);

  var forecastText = document.createTextNode(parsedDescription[0]);

  var forecastImg = document.createElement("img");
  forecastImg.src = parsedDescription[1];

  // Clear out the existing forecast
  clearForecast();

  // Construct the textual forecast
  var textualForecastElement = document.getElementById("textualForecast");
  textualForecastElement.appendChild(forecastText);

  // Construct the graphical forecast
  var graphicalForecastElement = document.getElementById("graphicalForecast");
  graphicalForecastElement.appendChild(forecastImg);
}

// Updates visibility of the navigation arrows
function updateNavigationArrows(day) {
  // If day == TODAY, it is not possible to navigate to the previous day
  document.getElementById("arrowLeft").style.display = (day == TODAY) ? "none" : "block";
  // If day == DAY_AFTER_TOMORROW, it is not possible to navigate to the next day
  document.getElementById("arrowRight").style.display = (day == DAY_AFTER_TOMORROW) ? "none" : "block";
}

// Separates different parts of the description (namely textual and graphical
// forecast) from each other and returns them in an array
function parseDescription(description) {
  var descriptionData = [];

  // Parse the textual forecast and push it into the array
  var textualForecast = description.substring(0, description.indexOf("<"));
  descriptionData.push(textualForecast);

  // Parse the graphical forecast and push it into the array
  var imageLinkPrefix = "<img src=\"";
  var startIndex = description.indexOf("<") + imageLinkPrefix.length;  // inclusive
  // The RSS data is not valid for the part of the graphical forecast: there is
  // an extra space before the greater than sign that closes the img tag
  var gtIndex = description.indexOf(" >");
  if (gtIndex < 0) {
    gtIndex = description.indexOf(">");
  }
  var endIndex = gtIndex - "\"".length;  // exclusive
  var graphicalForecast = description.substring(startIndex, endIndex);
  descriptionData.push(graphicalForecast);

  return descriptionData;
}

// Removes the existing text and image
function clearForecast() {
  var textualForecastElement = document.getElementById("textualForecast");
  // There is only one text element, so the first child will do
  textualForecastElement.removeChild(textualForecastElement.childNodes[0]);

  var graphicalForecastElement = document.getElementById("graphicalForecast");
  // There is only one image element, so the first child will do
  graphicalForecastElement.removeChild(graphicalForecastElement.childNodes[0]);
}

// Gets called when a key is pressed either in location view or forecast view
function keyPress(keyEvent) {
  if (document.getElementById("forecast").style.display === "none") {
    // The key was pressed in location view. Delegate the key press to appropriate function.
    keyPressedInLocationView(keyEvent);
  } else {
    // The key was pressed in forecast view. Delegate the key press to appropriate function.
    keyPressedInForecastView(keyEvent);
  }
}

// Gets called when a key is pressed in location view
function keyPressedInLocationView(keyEvent) {
  // Store the previous selection
  var previouslySelectedLocation = currentlySelectedLocation;
  if (keyEvent.charCode == 63497) {
    // Key: "Up"
    if (currentlySelectedLocation <= 0) {
      // We are already at the beginning of the list so there's nothing to do
      return;
    }
    currentlySelectedLocation--;
  } else if (keyEvent.charCode == 63498) {
    // Key: "Down"
    if (currentlySelectedLocation >= locationNodes.length - 1) {
      // We are already at the end of the list so there's nothing to do
      return;
    }
    currentlySelectedLocation++;
  } else if (keyEvent.charCode == 63557) {
    // Key: "Center"
    // Create a "fetch" function call and execute it
    var functionCall = locationNodes[currentlySelectedLocation].getAttribute("onclick");
    eval(functionCall);
  } else {
    // Other key presses are of no interest
    return;
  }
  updateLocationHighlight(previouslySelectedLocation, currentlySelectedLocation);
}

// Gets called when a key is pressed in forecast view
function keyPressedInForecastView(keyEvent) {
  if (keyEvent.charCode == 63495) {
    // Key: "Left"
    if (currentlySelectedForecast <= 0) {
      // We are already showing the first forecast so there's nothing to do
      return;
    }
    // Change the forecast for the previous day (not yesterday but the day before the current day)
    currentlySelectedForecast--;
  } else if (keyEvent.charCode == 63496) {
    // Key: "Right"
    if (currentlySelectedForecast >= 2) {
      // We are already showing the last forecast so there's nothing to do
      return;
    }
    // Change the forecast for the next day (not tomorrow but the day after the current day)
    currentlySelectedForecast++;
  } else {
    // We don't want the forecast to get updated if other than above keys are pressed
    return;
  }
  displayForecast(currentlySelectedForecast);
}

// Gets called when a navigation arrow is clicked. This is only possible in forecast view.
// Direction indicates which arrow was clicked.
function navigationArrowClicked(direction) {
  var charCode = -1;
  if (direction == "left") {
    charCode = 63495; // a code for the "Left" key on the keypad
  } else if (direction == "right") {
    charCode = 63496; // a code for the "Right" key on the keypad
  }
  // Simulate keyEvent object by creating a regular object and adding the above
  // declared character code into it as a property
  var keyEvent = new Object();
  keyEvent.charCode = charCode;
  keyPressedInForecastView(keyEvent);
}

// Removes the highlight of the previously highlighted location item in the
// location list and highlights the currently selected item in the list
function updateLocationHighlight(previousSelection, currentSelection) {
  if (previousSelection >= 0) {
    locationNodes[previousSelection].removeAttribute("class");
  }
  locationNodes[currentSelection].setAttribute("class", "selectedItem");
}

// Shows the placeholder for weather forecast
function showForecastElement() {
  widget.prepareForTransition("fade");

  // Hide the location list and show the forecast view
  document.getElementById("locations").style.display = "none";
  document.getElementById("forecast").style.display = "block";

  // Change the right softkey so that it shows the location list
  window.menu.setRightSoftkeyLabel("Back", showLocations);

  // Refresh the view
  setTimeout("widget.performTransition();", 0);
}

// Shows the location list
function showLocations() {
  widget.prepareForTransition("fade");

  // Hide the forecast view and show the location list
  document.getElementById("forecast").style.display = "none";
  document.getElementById("locations").style.display = "block";

  // Restore the default right softkey
  window.menu.setRightSoftkeyLabel("", null);

  // Refresh the view
  setTimeout("widget.performTransition();", 0);
}

