var currentlySelectedLocation = 0;
var currentlySelectedLocationText = null;
var locationNodes = null;
var prevCustomLocCode = "";  // Previously entered custom location code

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 CURRENT = 0;
var TODAY = 1;
var TOMORROW = 2;
var currentlySelectedForecast = CURRENT;

window.onload = init;

// Initializes the widget
function init() {
    window.menu.showSoftkeys();

    // Store the locations
    var locationTableElement = document.getElementById("locationTable");
    locationNodes = locationTableElement.getElementsByTagName("td");

    // Enable tab mode navigation
    widget.setNavigationEnabled(false);

    // Call keyPress function when a key gets pressed
    document.onkeypress = keyPress;
    
    // Call windowResized function when the window gets resized.
    // TODO: onresize event is not raised on devices. See the file
    // dev_specific.js for more information.
    document.onresize = windowResized;
    
    // Highlight the initial location
    updateLocationHighlight(-1, 0);

    // Hide the forecast view (the city view is the first view)
    document.getElementById("forecastContainer").style.display = "none";

    // Check if there's a need for some device-specific customization
    devSpecificInit();
}

// 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(elementNumber, 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):",
            prevCustomLocCode);
        // Only real input is accepted
        if (locationCode == null || locationCode.trim().length == 0) {
            return;
        }
        // Store the entered code
        prevCustomLocCode = locationCode;
    }
    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(CURRENT);
        } 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("locationsContainer").style.display !=
        "none") {
        showForecast();
    }

    // Update the forecast heading
    updateForecastHeading(targetDay);

    // 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 temperatureHigh = parsedDescription[0];
    var temperatureLow = parsedDescription[1];

    // If the low temperature is null, it means we parsed the current forecast
    if (temperatureLow == null) {
        displayCurrentTemperature(temperatureHigh);
    } else {
        displayTemperatures(temperatureHigh, temperatureLow);
    }

    displayTextualForecast(parsedDescription[2]);

    displayGraphicalForecast(parsedDescription[3]);
}

// Updates the forecast heading: Location + date
function updateForecastHeading(targetDay) {
    var title = weatherData[targetDay]["title"];

    // The title is either "Currently: Sunny: 11C" or "10/27/2008 Forecast".
    // Define the date string according to the title.
    var dateString = null;
    var currentlyIndex = title.indexOf("Currently");
    if (currentlyIndex != -1) {
        dateString = "Now";
    } else {
        var date = title.substring(0, title.indexOf(" "));
        dateString = formatDate(date);
    }

    var forecastHeadingElement = document.getElementById("forecastHeading");
    forecastHeadingElement.innerHTML = currentlySelectedLocationText +
        " \u2013 " + dateString;
}

// Displays the current temperature
function displayCurrentTemperature(currentTemperature) {
    // Hide the temperature forecast
    document.getElementById("highTemperature").style.display = "none";
    document.getElementById("lowTemperature").style.display = "none";

    var curTemperatureElement = document.getElementById("currentTemperature");

    // Change the element background according to the current temperature
    mapElementBackgroundToTemperature(curTemperatureElement,
        currentTemperature);

    // Display the current temperature element
    curTemperatureElement.style.display = "block";

    var temperatureTextElements =
        curTemperatureElement.getElementsByTagName("span");
    temperatureTextElements[0].innerHTML = "";
    temperatureTextElements[1].innerHTML = currentTemperature;
    temperatureTextElements[2].innerHTML = "\u00B0C";
}

// Displays the temperature forecast (high and low)
function displayTemperatures(temperatureHigh, temperatureLow) {
    // Hide the current temperature
    document.getElementById("currentTemperature").style.display = "none";
    
    var temperatureElements = [
        document.getElementById("highTemperature"),
        document.getElementById("lowTemperature")
    ];

    var temperatureTexts = ["High", "Low"];
    var temperatures = [temperatureHigh, temperatureLow];

    for (var i = 0; i < temperatureElements.length; i++) {
        // Change the element background according to the temperature
        mapElementBackgroundToTemperature(temperatureElements[i],
            temperatures[i]);

        temperatureElements[i].style.display = "block";

        var temperatureTextElements =
            temperatureElements[i].getElementsByTagName("span");

        temperatureTextElements[0].innerHTML = temperatureTexts[i];
        temperatureTextElements[1].innerHTML = temperatures[i];
        temperatureTextElements[2].innerHTML = "\u00B0C";
    }
}

// Displays the textual forecast (e.g. "Partly sunny")
function displayTextualForecast(textualForecast) {
    var textualForecastElement = document.getElementById("textualForecast");

    // Clear out the existing forecast
    textualForecastElement.innerHTML = "";

    // Construct new textual forecast
    var forecastText = document.createTextNode(textualForecast);
    textualForecastElement.appendChild(forecastText);
}

// Displays an image with the src attribute set to graphicsSource
function displayGraphicalForecast(graphicsSource) {
    var graphicalForecastElement =
        document.getElementById("graphicalForecast");

    // Clear out the existing forecast
    graphicalForecastElement.innerHTML = "";
    
    // Construct new graphical forecast
    var forecastImg = document.createElement("img");
    forecastImg.src = graphicsSource;
    graphicalForecastElement.appendChild(forecastImg);
}

// Updates visibility of the navigation arrows
function updateNavigationArrows(day) {
    // If day == CURRENT, it is not possible to navigate to the previous
    // forecast
    document.getElementById("arrowLeft").style.display =
        (day == CURRENT) ? "none" : "block";
    // If day == TOMORROW, it is not possible to navigate to the next
    // forecast
    document.getElementById("arrowRight").style.display =
        (day == TOMORROW) ? "none" : "block";
}

// Separates different parts of the description from each other and returns
// them in an array
function parseDescription(description) {
    var descriptionData = [];

    // Parse the textual forecast
    var textualForecast = description.substring(0, description.indexOf("<"));
    var currentlyIndex = textualForecast.indexOf("Currently");
    if (currentlyIndex != -1) {
        // If the word "Currently" was found, we are parsing the current
        // weather conditions. For example:
        // "Currently in OULU, FINLAND (OULUN LAANI): 6 °C and Partly Sunny".
        // Find a colon somewhere after the word "Currently"
        var colonIndex = textualForecast.indexOf(":", currentlyIndex);
        var temperatureStart = colonIndex + 2;
        // Temperature value ends in space
        var temperatureEnd = textualForecast.indexOf(" ", temperatureStart);
        // Now we have the temperature
        var temperatureValue = textualForecast.substring(temperatureStart,
            temperatureEnd);
        // Push it into the array
        descriptionData.push(temperatureValue);
        
        // Because we are parsing the current weather conditions, there are no
        // "high" and "low" temperatures, but only one element. Let's assign
        // the "low" temperature as null.
        descriptionData.push(null);
        
        // Parse the weather information
        var andIndex = textualForecast.indexOf("and", temperatureEnd);
        var forecast = textualForecast.substring(andIndex + 4);
        // Push it into the array
        descriptionData.push(forecast);
    } else {
        // We are parsing the forecast
        // For example: "High: 7 C Low: 7 C Overcast; p.m. rain, breezy"
        // Find the first colon
        var colonIndexHigh = textualForecast.indexOf(":");
        var temperatureStartHigh = colonIndexHigh + 2;
        // Temperature value ends in space
        var temperatureEndHigh = textualForecast.indexOf(" ",
            temperatureStartHigh);
        // Now we have the high temperature
        var temperatureValueHigh = textualForecast.substring(
            temperatureStartHigh, temperatureEndHigh);
        // Push it into the array
        descriptionData.push(temperatureValueHigh);

        // Find the second colon
        var colonIndexLow = textualForecast.indexOf(":", colonIndexHigh + 1);
        var temperatureStartLow = colonIndexLow + 2;
        // Temperature value ends in space
        var temperatureEndLow = textualForecast.indexOf(" ",
            temperatureStartLow);
        // Now we have the low temperature
        var temperatureValueLow = textualForecast.substring(
            temperatureStartLow, temperatureEndLow);
        // Push it into the array
        descriptionData.push(temperatureValueLow);

        // Parse the forecast
        var celsiusIndexLow = textualForecast.indexOf("C", temperatureEndLow);
        var forecast = textualForecast.substring(celsiusIndexLow + 2);
        // Push it into the array
        descriptionData.push(forecast);
    }
    
    // Parse the graphical forecast
    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 imageSrc = description.substring(startIndex, endIndex);
    // Map the image URL to local image
    var graphicalForecast = mapImageURLToImage(imageSrc);
    descriptionData.push(graphicalForecast);

    return descriptionData;
}

// Maps an image URL (e.g.
// "http://vortex.accuweather.com/phoenix2/images/common/icons/01_31x31.gif")
// to local image (e.g. "gfx/weather_icons/sun.png")
function mapImageURLToImage(imageSource) {
    var lastSeparatorIndex = imageSource.lastIndexOf("/");
    var imageNumber = imageSource.substr(lastSeparatorIndex + 1, 2);
    var graphicalForecast = null;
    switch (imageNumber) {
        case "01":  // Sunny
            graphicalForecast = "sun.png";
            break;
        case "02":  // Sun and clouds
        case "03":
        case "04":
        case "05":
        case "06":
            graphicalForecast = "partly_sunny.png";
            break;
        case "07":  // Clouds
        case "08":
            graphicalForecast = "clouds.png";
            break;
        case "11":  // Fog
            graphicalForecast = "fog.png";
            break;
        case "12":  // Rain showers
        case "13":
        case "14":
            graphicalForecast = "cloud_and_rain.png";
            break;
        case "15":  // Thunderstorm
        case "16":
        case "17":
            graphicalForecast = "thunderstorm.png";
            break;
        case "18":  // Rain
            graphicalForecast = "rain.png";
            break;
        case "19":  // Snow
        case "20":
        case "21":
        case "22":
        case "23":
            graphicalForecast = "snow.png";
            break;
        case "24":  // Ice
            graphicalForecast = "ice.png";
            break;
        case "25":  // Hail
        case "26":
            graphicalForecast = "hail.png";
            break;
        case "29":  // Rain and snow
            graphicalForecast = "cloud_rain_snow.png";
            break;
        case "30":  // Hot
            graphicalForecast = "hot.png";
            break;
        case "31":  // Cold
            graphicalForecast = "cold.png";
            break;
        case "32":  // Hurricanes
            graphicalForecast = "hurricane.png";
            break;
        case "33":  // Night/clear
            graphicalForecast = "night_clear.png";
            break;
        case "34":  // Night/clouds
        case "35":
        case "36":
        case "37":
        case "38":
            graphicalForecast = "night_clouds.png";
            break;
        case "39":  // Night/rain
        case "40":
            graphicalForecast = "night_rain.png";
            break;
        case "41":  // Night/thunderstorm
        case "42":
            graphicalForecast = "night_thunderstorm.png";
            break;
        case "43":  // Night/snow
        case "44":
            graphicalForecast = "night_snow.png";
            break;
        default:
            // Unknown image, return the source URL unchanged (the result
            // doesn't look good, but this shouldn't happen in the first place)
            return imageSource;
    }
    return "gfx/weather_icons/" + graphicalForecast;
}

// Gets called when a key is pressed either in location view or forecast view
function keyPress(keyEvent) {
    if (document.getElementById("forecastContainer").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 || keyEvent.charCode == 63495) {
        // Key: "Up" || Key: "Left"
        if (currentlySelectedLocation <= 0) {
            // We are already at the beginning of the list so there's nothing
            // to do
            return;
        }
        currentlySelectedLocation--;
        updateLocationHighlight(previouslySelectedLocation,
            currentlySelectedLocation);
    } else if (keyEvent.charCode == 63498 || keyEvent.charCode == 63496) {
        // Key: "Down" || Key: "Right"
        if (currentlySelectedLocation >= locationNodes.length - 1) {
            // We are already at the end of the list so there's nothing to do
            return;
        }
        currentlySelectedLocation++;
        updateLocationHighlight(previouslySelectedLocation,
            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;
    }
}

// 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 the
// 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
// city view and highlights the currently selected item in it
function updateLocationHighlight(previousSelection, currentSelection) {
    if (previousSelection >= 0) {
        locationNodes[previousSelection].removeAttribute("class");
    }
    locationNodes[currentSelection].setAttribute("class", "selectedItem");
}

// Shows the placeholder for weather forecast
function showForecast() {
    // Reset the currently selected forecast
    currentlySelectedForecast = CURRENT;

    widget.prepareForTransition("fade");

    // Hide the location list and show the forecast view
    document.getElementById("locationsContainer").style.display = "none";
    document.getElementById("forecastContainer").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 table
function showLocations() {
    widget.prepareForTransition("fade");

    // Hide the forecast view and show the city view
    document.getElementById("forecastContainer").style.display = "none";
    document.getElementById("locationsContainer").style.display = "block";

    // Restore the default right softkey
    window.menu.setRightSoftkeyLabel("", null);

    // Refresh the view
    setTimeout("widget.performTransition();", 0);
}

