var CONSTANTS = {
    media_root: "http://media.livebus.org"
};
CONSTANTS.icon_root = CONSTANTS.media_root + "/images/marker/";

var LIVEBUS = {
    templates: {
        // Stop info window header
        info_head: '<div class="stopInfo"><h3>%(name) <span>%(time)</span></h3>',
        // Loading arrivals error
        info_error: '<p class="stopMessage">We\'re having problems fetching bus times for this stop right now, please try again later.</p>',
        // Loading arrivals progress bar
        info_loading: '<p class="stopMessage"><img src="%(root)/images/loading.gif" width="300" height="50" alt="Loading..."></p>',
        // Stop info window footer
        info_foot: '<p class="stopMessage"><a href="%(url)"><img src="%(root)/images/button-details.gif" width="188" height="29" alt="More details and routes"></a></p>',
        // Stop SMS info
        info_sms: '<p class="stopMessage">Text <strong>%(sms)</strong> to <em>%(number)</em> (%(cost)) for live updates to your mobile. <a href="%(url)" title="Information on the traveline-txt service">[?]</a></p></div>'
    },
    
    showStatus: function (status) {
        // Add a status alert to the map
        var status_icon = new GIcon();
        status_icon.image = CONSTANTS.media_root + "/images/" + status + ".gif";
        status_icon.iconSize = new GSize(300, 50);
        status_icon.shadow = CONSTANTS.media_root + "/images/status-shadow.png";
        status_icon.shadowSize = new GSize(304, 55);
        status_icon.iconAnchor = new GPoint(150, 25);
        LIVEBUS.status_marker = new GMarker(LIVEBUS.map.getCenter(), {
            icon: status_icon,
            clickable: true
        });
        GEvent.addListener(LIVEBUS.status_marker, "click", function () {
            LIVEBUS.map.removeOverlay(LIVEBUS.status_marker);
        });
        LIVEBUS.map.addOverlay(LIVEBUS.status_marker);
        if (status === "notfound") {
            // Remove the "no stops found" message after a second
            setTimeout(function () {
                GEvent.trigger(LIVEBUS.status_marker, "click");
            }, 1000);
        }
    },
    
    showInfo: function (point, stop) {
        var pixelPoint = LIVEBUS.map.fromLatLngToDivPixel(point);
        var offset;
        if (LIVEBUS.on_route) {
            offset = new GPoint(6, -6);
        } else {
            offset = new GPoint(12, -31);
        }
        var offsetPoint = new GPoint(pixelPoint.x + offset.x, pixelPoint.y + offset.y);
        point = LIVEBUS.map.fromDivPixelToLatLng(offsetPoint);
        var times_api = stop.Info;
        var info_head = COMMON.template(LIVEBUS.templates.info_head, {
            "name": stop.Name,
            "time": COMMON.getCurrentTime()
        });
        var info_loading = COMMON.template(LIVEBUS.templates.info_loading, {
            "root": CONSTANTS.media_root
        });
        var info_foot = COMMON.template(LIVEBUS.templates.info_foot, {
            "url": stop.URL,
            "root": CONSTANTS.media_root
        });
        if (LIVEBUS.detailed_stop && LIVEBUS.detailed_stop.Code == stop.Code) {
            info_foot = "";
        }
        var info_sms;
        if (stop.SMS) {
            info_sms = COMMON.template(LIVEBUS.templates.info_sms, {
                "sms": stop.SMS.Code,
                "number": stop.SMS.Number,
                "cost": stop.SMS.Cost,
                "url": stop.SMS.URL
            });
        } else {
            info_sms = "</div>";
        }
        var info_html = info_head + info_loading + info_foot + info_sms;
        ArrivalsProcessor = {
            loadTimes: function (o) {
                this.openInfo(o.responseText);
            },
            loadError: function (o) {
                this.openInfo(LIVEBUS.templates.info_error);
            },
            openInfo: function (info_contents) {
                info_html = info_head + info_contents + info_foot + info_sms;
                LIVEBUS.map.openInfoWindowHtml(point, info_html);
            }
        };
        // Open the window with a loading bar
        LIVEBUS.map.openInfoWindowHtml(point, info_html, {
            onOpenFn: function() {
                // Make a request for arrivals and load the info window again with times
                YAHOO.util.Connect.asyncRequest("GET", times_api + '?limit=5', {
                    success: ArrivalsProcessor.loadTimes,
                    failure: ArrivalsProcessor.loadError,
                    scope: ArrivalsProcessor
                });
            }
        });
    },
    
    switchHighlight: function (list, count, link) {
        // Clear current focus using an intermediary static array for performance
        var to_clear = [];
        for (var i = 0; i < count; i++) {
            if (/\bfocus\b/.exec(list[i].className)) {
                to_clear[to_clear.length] = list[i];
            }
        }
        for (i = 0; i < to_clear.length; i++) {
            to_clear[i].className = to_clear[i].className.replace("focus", "");
        }
        to_clear = null;
        if (link) {
            // Highlight the clicked list item.
            link.parentNode.className = link.parentNode.className + " focus";
        }
    },
    
    stop_points: {},
    createMarker: function (stop, is_main, is_focus, focus_link) {
        var point = new GLatLng(parseFloat(stop.Latitude), parseFloat(stop.Longitude));
        // Set the icon type
        var type_prefix = LIVEBUS.on_route && "Small" || "Large";
        var focus_suffix = is_focus && "Focus" || "";
        var main_suffix = is_main && "Main" || "";
        var icon = LIVEBUS.icons[type_prefix + (focus_suffix || main_suffix)];
        var marker_title = stop.Name + " (" + stop.Code + ")";
        var marker = new GMarker(point, {
            icon: icon,
            title: marker_title
        });
        LIVEBUS.stop_points[stop.Code] = point;
        if (LIVEBUS.focus_marker) {
            // Remove the current focus marker
            LIVEBUS.map.removeOverlay(LIVEBUS.focus_marker);
        }
        if (is_focus) {
            // Set the new focus marker
            LIVEBUS.focus_marker = marker;
            LIVEBUS.switchHighlight(LIVEBUS.stop_list, LIVEBUS.stop_count, focus_link || null);
        }
        GEvent.addListener(marker, "click", function () {
            // Switch focus
            var link = LIVEBUS.stop_list && document.getElementById("s" + stop.Code) || false;
            LIVEBUS.createMarker(stop, is_main, true, link);
            // Info window
            LIVEBUS.showInfo(marker.getPoint(), stop);
        });
        LIVEBUS.map.addOverlay(marker);
    },
    
    loadStops: function (stops_api, linked_title) {
        // Prepare the map
        LIVEBUS.map.getInfoWindow().hide();
        if (LIVEBUS.target) {
            LIVEBUS.map.removeOverlay(LIVEBUS.target);
        }
        LIVEBUS.map.clearOverlays();
        LIVEBUS.showStatus("loading");
        // API response callback object
        var process_stops = {
            success: function (o) {
                try {
                    var json = eval("(" + o.responseText + ")");
                } catch (e) {
                    this.failure();
                }
                // Update the title text
                LIVEBUS.map_title = document.getElementById("mapTitle");
                if (linked_title) {
                    LIVEBUS.map_title.innerHTML = '<a href="' + json.URL + '">' + json.Title + '</a>';
                } else {
                    LIVEBUS.map_title.innerHTML = json.Title;
                }
                // Add the stops to the global object
                LIVEBUS.stop_objects = json.Stops || [json];
                LIVEBUS.object_count = LIVEBUS.stop_objects.length;
                LIVEBUS.on_route = json.Stops && json.Number;
                if (json.Bounds) {
                    var sw = new GLatLng(json.Bounds.South, json.Bounds.West);
                    var ne = new GLatLng(json.Bounds.North, json.Bounds.East);
                    var bounds = new GLatLngBounds(sw, ne);
                }
                json = null;
                LIVEBUS.map.removeOverlay(LIVEBUS.status_marker);
                var stop;
                for (var i = 0; i < LIVEBUS.object_count; i++) {
                    // Create a marker for each stop and prime its click listener
                    stop = LIVEBUS.stop_objects[i];
                    if (!LIVEBUS.detailed_stop) {
                        LIVEBUS.detailed_stop = LIVEBUS.stop_detail && stop.Main && stop;
                    }
                    LIVEBUS.createMarker(stop, stop.Main, false, false);
                }
                var zoom;
                if (bounds) {
                    zoom = LIVEBUS.map.getBoundsZoomLevel(bounds);
                } else {
                    zoom = 17;
                }
                LIVEBUS.map.setCenter(bounds && bounds.getCenter() || LIVEBUS.center, zoom);
                GEvent.trigger(LIVEBUS.map, "stopsloaded");
            },
            failure: function (o) {
                LIVEBUS.map.removeOverlay(LIVEBUS.status_marker);
                LIVEBUS.showStatus("notfound");
                GEvent.trigger(LIVEBUS.map, "notfound");
            }
        };
        // Load stops
        var connection = YAHOO.util.Connect.asyncRequest("GET", stops_api, process_stops);
    },
    
    getStops: function () {
        // Check that we can have a map
        var map_div = document.getElementById("map") || null;
        if (!map_div || !GBrowserIsCompatible()) {
            return undefined;
        }
        // Load the map and center
        LIVEBUS.map = new GMap2(map_div, {
            draggableCursor: "crosshair"
        });
        LIVEBUS.map.addControl(new GSmallMapControl());
        LIVEBUS.map.addControl(new GScaleControl());
        LIVEBUS.map.addControl(new LIVEBUS.LocateStopsControl());
        if (LIVEBUS.bounds) {
            LIVEBUS.map.setCenter(LIVEBUS.bounds.getCenter(), LIVEBUS.map.getBoundsZoomLevel(LIVEBUS.bounds));
        } else {
            LIVEBUS.map.setCenter(LIVEBUS.center, LIVEBUS.zoom || 12);
        }
        var stops_api, i, url;
        // Prime the conditions
        LIVEBUS.stop_detail = document.getElementById("stop") || null;
        var stop_list = document.getElementById("stopList") || null;
        var locality_list = document.getElementById("localities") || null;
        var route_list = document.getElementById("routeLists") || null;
        if (LIVEBUS.stop_detail) {
            // For stop detail pages load the stop onto the map
            stops_api = window.location + "json/";
            // Listen for nearby stops toggle and update map accordingly
            var container = document.getElementById("nearby") || null;
            if (container) {
                var toggler = container.getElementsByTagName("h2")[0].getElementsByTagName("a")[0];
                var toggle_image = toggler.getElementsByTagName("img")[0];
                container.className = container.className.replace("expanded", "collapsed");
                toggler.onclick = function () {
                    var linked_title;
                    if (/\bexpanded\b/.exec(container.className)) {
                        // Collapse nearby stops
                        url = window.location;
                        linked_title = false;
                        toggle_image.src = toggle_image.src.replace("collapse.gif", "expand.gif");
                        container.className = container.className.replace("expanded", "collapsed");
                        if (LIVEBUS.stop_list) {
                            LIVEBUS.switchHighlight(LIVEBUS.stop_list, LIVEBUS.stop_count);
                        }
                    } else if (/\bcollapsed\b/.exec(container.className)) {
                        // Expand nearby stops
                        url = this.href;
                        linked_title = true;
                        toggle_image.src = toggle_image.src.replace("expand.gif", "collapse.gif");
                        container.className = container.className.replace("collapsed", "expanded");
                    }
                    stops_api = url + "json/";
                    LIVEBUS.loadStops(stops_api, linked_title);
                    return false;
                };
            } else {
                LIVEBUS.loadStops(stops_api, false);
            }
        }
        if (stop_list) {
            // Returns the stop object for the given code
            function getStopObject(code) {
                for (i = 0; i < LIVEBUS.object_count; i++) {
                    if (LIVEBUS.stop_objects[i].Code ===  code) {
                        return LIVEBUS.stop_objects[i];
                    }
                }
                return false;
            }
            // For stop lists (routes, localities, search results, nearby, etc...) load the stops onto the map
            var suffix = document.getElementById("parametric") && "&format=json" || "json/";
            stops_api = window.location + suffix;
            LIVEBUS.stop_list = stop_list.getElementsByTagName("li");
            LIVEBUS.stop_count = LIVEBUS.stop_list.length;
            LIVEBUS.loadStops(stops_api, false);
            var link, code, stop, is_main, marker;
            // Highlight clicked list items
            for (i = 0; i < LIVEBUS.stop_count; i++) {
                link = LIVEBUS.stop_list[i].getElementsByTagName("a")[0];
                if (link) {
                    link.onclick = function () {
                        code = this.id.substring(1);
                        stop = getStopObject(code);
                        if (stop) {
                            is_main = stop.Main;
                            point = LIVEBUS.stop_points[code];
                            LIVEBUS.createMarker(stop, is_main, true, this);
                            LIVEBUS.showInfo(point, stop);
                            window.scrollTo(0,0);
                        } else {
                            LIVEBUS.switchHighlight(LIVEBUS.stop_list, LIVEBUS.stop_count, false);
                            LIVEBUS.getStops();
                        }
                        return false;
                    };
                }
            }
        }
        // Load stops for clicked item
        function loadClickStops(link, list, count) {
            LIVEBUS.switchHighlight(list, count, link);
            stops_api = link.href + "json/";
            LIVEBUS.loadStops(stops_api, true);
            window.scrollTo(0,0);
        }
        if (locality_list) {
            // For place lists listen for clicks before loading stops
            var localities = locality_list.getElementsByTagName("li");
            var locality_count = localities.length;
            for (i = 0; i < locality_count; i++) {
                localities[i].getElementsByTagName("a")[0].onclick = function () {
                    loadClickStops(this, localities, locality_count);
                    return false;
                };
            }
        }
        if (route_list) {
            // For route lists listen for clicks before loading stops
            var routes = route_list.getElementsByTagName("dt");
            var route_count = routes.length;
            for (i = 0; i < route_count; i++) {
                routes[i].getElementsByTagName("a")[0].onclick = function () {
                    loadClickStops(this, routes, route_count);
                    return false;
                };
            }
        }
    },
    
    loadStopArrivals: function () {
        // API response callback object
        var request = YAHOO.util.Connect.asyncRequest("GET", window.location + 'info/', {
            success: function (o) {
                document.getElementById('arrivalTimes').innerHTML = o.responseText;
            },
            failure: function (o) {
                document.getElementById('arrivalTimes').innerHTML = LIVEBUS.templates.info_error;
            },
            argument: { limit: 5 }
        });
    },
    
    LocateStopsControl: function () {
    },
    Rectangle: function (center, opt_weight, opt_color, opt_deltaX, opt_deltaY) {
        // Set the style
        this.weight_ = opt_weight || 1;
        this.color_ = opt_color || "#1d4222";
        // Set the bounds
        this.deltaX_ = opt_deltaX || 0.002;
        this.deltaY_ = opt_deltaX || 0.001;
        this.getRectBounds = function (center_point) {
            var southWest, northEast, rectBounds;
            southWest = new GLatLng(center_point.lat() - this.deltaY_, center_point.lng() + this.deltaX_);
            northEast = new GLatLng(center_point.lat() + this.deltaY_, center_point.lng() - this.deltaX_);
            rectBounds = new GLatLngBounds(southWest, northEast);
            return rectBounds;
        };
        this.bounds_ = this.getRectBounds(center);
    },
    icons: {
        Large: new GIcon({
            image: CONSTANTS.icon_root + "marker.png",
            iconSize: new GSize(24, 31),
            shadow: CONSTANTS.icon_root + "marker-shadow.png",
            shadowSize: new GSize(42, 40),
            transparent: CONSTANTS.icon_root + "marker-click.png",
            imageMap: [6,0, 17,0, 23,6, 23,17, 15,25, 8,25, 0,17, 0,6],
            printImage: CONSTANTS.icon_root + "marker.png",
            mozPrintImage: CONSTANTS.icon_root + "marker.png",
            printShadow: CONSTANTS.icon_root + "marker-shadow.png",
            iconAnchor: new GPoint(12, 31),
            infoWindowAnchor: new GPoint(24, 0)
        }),
        Small: new GIcon({
            image: CONSTANTS.icon_root + "marker-small.png",
            iconSize: new GSize(13, 13),
            transparent: CONSTANTS.icon_root + "marker-small-click.png",
            imageMap: [4,0, 8,0, 9,1, 10,1, 11,2, 11,3, 12,4, 12,8, 11,9, 11,10, 10,11,
                9,11, 8,12, 4,12, 3,11, 2,11, 1,10, 1,9, 0,8, 0,4, 1,3, 1,2, 2,1, 3,1],
            printImage: CONSTANTS.icon_root + "marker-small.png",
            mozPrintImage: CONSTANTS.icon_root + "marker-small.png",
            iconAnchor: new GPoint(6, 6),
            infoWindowAnchor: new GPoint(13, 0)
        })
    }
};


LIVEBUS.icons.LargeMain = new GIcon(LIVEBUS.icons.Large, CONSTANTS.icon_root + "marker-alt.png");
LIVEBUS.icons.LargeFocus = new GIcon(LIVEBUS.icons.Large, CONSTANTS.icon_root + "marker-focus.png");
LIVEBUS.icons.LargeFocus.shadow = null;
LIVEBUS.icons.SmallMain = new GIcon(LIVEBUS.icons.Small, CONSTANTS.icon_root + "marker-small-alt.png");
LIVEBUS.icons.SmallFocus = new GIcon(LIVEBUS.icons.Small, CONSTANTS.icon_root + "marker-small-focus.png");
LIVEBUS.icons.Focus = new GIcon(LIVEBUS.icons.LargeFocus);

// Creates a button and returns it as the control element
LIVEBUS.LocateStopsControl.prototype = new GControl();
LIVEBUS.LocateStopsControl.prototype.initialize = function (map) {
    // Make the button
    var button = document.createElement("button");
    button.id = "locator";
    var button_text = "Locate Stops";
    button.innerHTML = button_text;
    function locateStops(e) {
        var code = e.keyCode || e.which;
        var allow = code == 1 || code == 3 || code == 13;
        if (!LIVEBUS.target && allow) {
            // Add a target that can follow the mouse position
            LIVEBUS.target = new LIVEBUS.Rectangle(LIVEBUS.map.getCenter());
            LIVEBUS.map.addOverlay(LIVEBUS.target);
            // Change the button's style and text.
            button.className = "active";
            button.innerHTML = "Now click on the map to find stops";
            // Add mouse listeners to move the rectangle
            var mouseover_listener = GEvent.addListener(LIVEBUS.map, "mouseover", function (position) {
                // Add a Rectangle, making sure we've removed any extra ones first.
                LIVEBUS.map.removeOverlay(LIVEBUS.target);
                LIVEBUS.target = new LIVEBUS.Rectangle(position);
                LIVEBUS.map.addOverlay(LIVEBUS.target);
            });
            var mousemove_listener = GEvent.addListener(LIVEBUS.map, "mousemove", function (position) {
                // If the Rectangle's been removed, add it back.
                if (!LIVEBUS.target) {
                    LIVEBUS.target = new LIVEBUS.Rectangle(position);
                    LIVEBUS.map.addOverlay(LIVEBUS.target);
                }
                // Move the Rectangle
                LIVEBUS.target.redraw(true, position);
            });
            var mouseout_listener = GEvent.addListener(LIVEBUS.map, "mouseout", function (position) {
                // Remove the Rectangle.
                LIVEBUS.map.removeOverlay(LIVEBUS.target);
            });
            // Load stops when the map is clicked
            var click_listener = GEvent.addListener(LIVEBUS.map, "click", function (overlay, point) {
                if (point) {
                    var stops_api = LIVEBUS.area_url + "stops/point/?format=json&latitude=" + point.lat() + "&longitude=" + point.lng();
                    LIVEBUS.loadStops(stops_api, true);
                }
            });
            GEvent.addListener(LIVEBUS.map, "stopsloaded", function () {
                // Remove the rectangle and click listeners
                LIVEBUS.map.removeOverlay(LIVEBUS.target);
                LIVEBUS.target = null;
                GEvent.removeListener(click_listener);
                GEvent.removeListener(mouseover_listener);
                GEvent.removeListener(mousemove_listener);
                GEvent.removeListener(mouseout_listener);
                // Disable the button
                button.className = "";
                button.innerHTML = button_text;
            });
            GEvent.addListener(LIVEBUS.map, "removeoverlay", function (overlay) {
                // Remove the rectangle object if its overlay is cleared.
                if (overlay == LIVEBUS.target) {
                    LIVEBUS.target = null;
                }
            });
        }
    }
    GEvent.addDomListener(button, "click", locateStops);
    GEvent.addDomListener(button, "keypress", locateStops);
    // Add the button to the map
    LIVEBUS.map.getContainer().appendChild(button);
    return button;
};
// By default, the control will appear at the top right corner of the map with 7px padding.
LIVEBUS.LocateStopsControl.prototype.getDefaultPosition = function () {
    return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(7, 7));
};

LIVEBUS.Rectangle.prototype = new GOverlay();
LIVEBUS.Rectangle.prototype.toString = function () {
    return "[Rectangle]";
};
LIVEBUS.Rectangle.prototype.initialize = function (map) {
    // Create the DIV representing our rectangle
    var div = document.createElement("div");
    div.style.border = this.weight_ + "px solid " + this.color_;
    div.style.position = "absolute";
    // Our rectangle is flat against the map, so we add our selves to the
    // MAP_PANE pane, which is at the same z-index as the map itself (i.e.,
    // below the marker shadows)
    map.getPane(G_MAP_MAP_PANE).appendChild(div);
    
    this.map_ = map;
    this.div_ = div;
};
// Remove the main DIV from the map pane
LIVEBUS.Rectangle.prototype.remove = function () {
    this.div_.parentNode.removeChild(this.div_);
};
// Copy our data to a new Rectangle
LIVEBUS.Rectangle.prototype.copy = function () {
    return new LIVEBUS.Rectangle(this.bounds_, this.weight_, this.color_,
                                 this.backgroundColor_, this.opacity_);
};
// Redraw the rectangle based on the current projection and zoom level or a given center point
LIVEBUS.Rectangle.prototype.redraw = function (force, center) {
    // We only need to redraw if the coordinate system has changed
    if (!force) {
        return;
    }
    // Reset the bounds
    if (center) {
        this.bounds_ = this.getRectBounds(center);
    }
    // Calculate the DIV coordinates of two opposite corners of our bounds to
    // get the size and position of our rectangle
    var c1 = this.map_.fromLatLngToDivPixel(this.bounds_.getSouthWest());
    var c2 = this.map_.fromLatLngToDivPixel(this.bounds_.getNorthEast());
    
    // Now position our DIV based on the DIV coordinates of our bounds
    this.div_.style.width = Math.abs(c2.x - c1.x) + "px";
    this.div_.style.height = Math.abs(c2.y - c1.y) + "px";
    this.div_.style.left = (Math.min(c2.x, c1.x) - this.weight_) + "px";
    this.div_.style.top = (Math.min(c2.y, c1.y) - this.weight_) + "px";
};

COMMON.addEvent(window, "load", LIVEBUS.getStops, false);
