(function($) {
    if (!window["strava"]) window["strava"] = {};

    if (!window["strava"]["asset"]) window["strava"]["asset"] = {
        host: function() {
            return window._asset_host;
        },
        image: function(url) {
            return strava.asset.url("/images/" + url);
        },
        url: function(url) {
            return strava.asset.host() + url;
        },
        sprite: function(){
            return "sprite07172010.png";
        }
    };

    if (!window["strava"]["i18n"])
        window["strava"]["i18n"] = {
            prettyNumber: function(nStr){
                nStr += '';
                var x = nStr.split('.');
                var x1 = x[0];
                var x2 = x.length > 1 ? '.' + x[1] : '';
                var rgx = /(\d+)(\d{3})/;
                while (rgx.test(x1)) x1 = x1.replace(rgx, '$1' + ',' + '$2');
                return x1 + x2;
            },
            twof: function(number){
                if (!number) return "00";
                if (number <= 9) return "0"+number;
                return number;
            },
            prettyTime: function(time, options){
                options = $.extend({ hours: true, minutes: true, seconds: true}, options);
                var hours = Math.floor(time/60/60);
                var minutes = Math.floor(time/60-hours*60);
                var seconds = Math.floor(time-minutes*60-hours*60*60);

                return (options.hours ? strava.i18n.twof(hours)+( options.minutes || options.seconds ? ":" : "") : "")+(options.minutes ? strava.i18n.twof(minutes)+(options.seconds ? ":" : "") : "")+(options.seconds ? strava.i18n.twof(seconds) : "");
            },
            shortTime: function(time, options){
                time = Math.abs(time);
                options = $.extend({ hours: false, minutes: true, seconds: true}, options);
                var hours = Math.floor(time/60/60);
                var minutes = Math.floor(time/60-hours*60);
                var seconds = Math.floor(time-minutes*60-hours*60*60);

                return (options.hours || hours > 0 ? strava.i18n.twof(hours)+( options.minutes || minutes > 0 || options.seconds || seconds > 0 ? ":" : "") : "")+(options.minutes || minutes > 0 ? strava.i18n.twof(minutes)+(options.seconds || seconds > 0 ? ":" : "") : "")+(options.seconds || seconds > 0 ? strava.i18n.twof(seconds) : "");
            }
        };

    if (!window["strava"]["forms"])
        window["strava"]["forms"] = {
            Slider: function(selector, options){
                options = $.extend({
                    start: 0,
                    end: 100,
                    step: 1,
                    start_input: "#"+selector+"_start",
                    end_input: "#"+selector+"_end",
                    label: "."+selector,
                    div: "#"+selector,
                    plus: false,
                    time: false,
                    hours: true,
                    minutes: true,
                    seconds: true
                }, options);

                this.perform_slide = function(start, end){
                    $(options.start_input).val(start);
                    $(options.end_input).val(end);
                    var display_start = start;
                    var display_end = end;
                    if (options.time){
                        display_start = strava.i18n.prettyTime(start*60*60, { hours: options.hours, minutes: options.minutes, seconds: options.seconds });
                        display_end = strava.i18n.prettyTime(end*60*60, { hours: options.hours, minutes: options.minutes, seconds: options.seconds });
                    } else {
                        display_start = strava.i18n.prettyNumber(start);
                        display_end = strava.i18n.prettyNumber(end);                        
                    }
                    if (options.plus && end == options.end) $(options.label).html(display_start+" - "+display_end+"+");
                    else $(options.label).html(display_start+" - "+display_end);
                };

                this.start = parseInt($(options.start_input).val());
                this.end = parseInt($(options.end_input).val());
                var slider = this;

                this.actual_slider = $(options.div).slider({
                  range: true,
                  min: options.start,
                  max: options.end,
                  step: options.step,
                  values: [this.start, this.end],
                  slide: function(event, ui) {
                    slider.perform_slide(ui.values[0], ui.values[1]);
                  }
                });

                this.reset = function(){
                    if (!slider.actual_slider) return false;
                    slider.actual_slider.slider("values", 0, options.start);
                    slider.actual_slider.slider("values", 1, options.end);
                };

                this.perform_slide(this.start, this.end);
            }
        };

    if (!window["strava"]["sliders"])
        window["strava"]["sliders"] = {
            Slider: function(route, options) {
                options = $.extend({
                    start_icon: "select-start",
                    end_icon: "select-end",
                    start_color: "#54B03A",
                    end_color: "#FF6257",
                    color: "#105CB6",
                    start: null,
                    end: null,
                    highlight: null,
                    selector: "#slider-range",
                    step: 1,
                    slide: null }, options);

                var slider = this;

                this.object = $(options.selector);
                this.route = route;
                this.map = route.map;
                this.start = options.start;
                this.end = options.end;
                this.min = 0;
                this.max = slider.route.polyline.getLength()-1;
                if (options.start == "first") this.start = this.min;
                else if (!this.start) this.start = this.min;
                else this.start = parseInt(this.start);

                if (options.end == "last") this.end = this.max;
                else if (!this.end) this.end = Math.floor(route.polyline.getLength() / 2);
                else this.end = parseInt(this.end);

                this.start_icon = new strava.maps.Icon(options.start_icon, route.polyline.getAt(this.start), this.map);
                this.end_icon = new strava.maps.Icon(options.end_icon, route.polyline.getAt(this.end), this.map);
                this.route.setHighlight(this.start, this.end);

                this.object.append("<div class='actual-slider' style='margin-bottom: 10px; width: " + (this.object.width() - 80) + "px; margin-left: auto; margin-right: auto'/>");
                this.object.append("<div id='start-control' style='text-align: center; float: left; width: 130px'/>");
                this.object.find("#start-control").append("<h4>Move Start Point</h4>");
                this.object.find("#start-control").append("<button id='move-start-back' style='float: left' class='widget-button ui-state-default'>&lt; Back</button>");
                this.object.find("#start-control").append("<button id='move-start-forward' style='float: right' class='widget-button ui-state-default'>Forward &gt;</button>");

                this.object.append("<div id='end-control' style='text-align: center; float: right; width: 130px'/>");
                this.object.find("#end-control").append("<h4>Move End Point</h4>");
                this.object.find("#end-control").append("<button id='move-end-back' style='float: left' class='widget-button ui-state-default'>&lt; Back</button>");
                this.object.find("#end-control").append("<button id='move-end-forward' style='float: right' class='widget-button ui-state-default'>Forward &gt;</button>");

                this.actual_slider = this.object.find(".actual-slider");

                this.performSlide = function(start, end) {
                    if (start > end && end == slider.end) start = slider.start;
                    if (start < slider.min) start = slider.min;
                    if (end > slider.max) end = slider.max;
                    if (end < start) end = slider.end;

                    if (slider.start != start){
                        slider.start = start;
                        slider.start_icon.move(slider.route.polyline.getAt(slider.start));
                        if (!slider.start_icon.visible()) slider.start_icon.center();
                    }

                    if (slider.end != end){
                        slider.end = end;
                        slider.end_icon.move(slider.route.polyline.getAt(slider.end));
                        if (!slider.end_icon.visible()) slider.end_icon.center();
                    }

                    slider.route.setHighlight(slider.start, slider.end);

                    if ($.isFunction(options.slide))
                        options.slide(slider.start, slider.end);
                };

                this.getStep = function(){
                    var zoom = slider.map.google.getZoom();
                    if (zoom >= 17) return 1;
                    if (zoom >= 15) return 2;
                    if (zoom >= 13) return 5;
                    if (zoom >= 10) return 10;
                    return 20;
                };

                this.object.find("#move-start-forward").live("click", function() {
                    slider.actual_slider.slider("values", 0, slider.start + slider.getStep());
                    slider.performSlide(slider.actual_slider.slider("values", 0), slider.end);
                });
                this.object.find("#move-start-back").live("click", function() {
                    slider.actual_slider.slider("values", 0, slider.start - slider.getStep());
                    slider.performSlide(slider.actual_slider.slider("values", 0), slider.end);
                });

                this.object.find("#move-end-forward").live("click", function() {
                    slider.actual_slider.slider("values", 1, slider.end + slider.getStep());
                    slider.performSlide(slider.start, slider.actual_slider.slider("values", 1));
                });
                this.object.find("#move-end-back").live("click", function() {
                    slider.actual_slider.slider("values", 1, slider.end - slider.getStep());
                    slider.performSlide(slider.start, slider.actual_slider.slider("values", 1));
                });

                this.actual_slider.slider({
                    range: true,
                    min: slider.min,
                    max: slider.max,
                    values: [slider.start, slider.end],
                    step: options.step,
                    slide: function(event, ui) {
                        slider.performSlide(ui.values[0], ui.values[1]);
                    }
                });

                $(".ui-slider-handle:first").css("background", options.start_color);
                $(".ui-slider-handle:last").css("background", options.end_color);
                $(".ui-slider-range").css("background", options.color);

                this.performSlide(this.start, this.end);
            }
        };

    if (!window["strava"]["graphs"])
        window["strava"]["graphs"] = {
            Graph: function(options) {
                options = $.extend({
                    elevation: null,
                    data: null,
                    index: 0 }, options);

                this.data = options.data;
                this.index = options.index;
                this.elevation = options.elevation;

                this.key = this.data["y_keys"][this.index];
                this.y_labels = this.data["y_labels"][this.index];
                this.image = this.data["image_urls"][this.index].replace(/&amp;/gi, "&");
                this.time_range = this.data["time_range"];
                this.size = this.data["data"].length;
                this.width = this.data["width"];

                this.object = $("#" + this.key + "-graph");
                this.line = $("#" + this.key + "-line");
                this.label = $("#" + this.key + "-label");

                for (var j in this.y_labels)
                    this.object.append("<div style='display: inline; position: absolute; left: 3px; bottom: " + this.y_labels[j][1] + "px; font-size: 85%;' class='label y'>" + this.y_labels[j][0] + "</div>");

                this.object.css("background-image", "url(" + this.image + ")");

                if (this.object) this.object.show();
                if (this.line) this.line.show();
                if (this.label) this.label.show();

                if (this.elevation) this.elevation.register(this);

                var graph = this;

                this.move = function(index) {
                    if (graph.elevation) {
                        var time = graph.elevation.data[index][2];
                        var timePos = time / graph.time_range;
                        var timeIndex = Math.floor(timePos * graph.size);
                        var timePixelsPerPoint = graph.width / graph.size;
                        var timeLeft = Math.round(timeIndex * timePixelsPerPoint);
                        var labelLeft = timeLeft - 4;

                        if (labelLeft < 0) labelLeft = 0;
                        else if ((labelLeft + graph.label.width()) > graph.width - 8) labelLeft = graph.width - graph.label.width() - 8;

                        graph.line.css("left", timeLeft);
                        graph.label.css("left", labelLeft);
                        var yValue = graph.data["data"][timeIndex][graph.index];
                        graph.label.text(yValue)
                    }
                };
            },

            Route: function(options) {
                options = $.extend({
                    url: null,
                    data: null,
                    elevation: null }, options);

                this.data = null;
                this.graphs = new Array();
                var route = this;

                if (!strava.maps.loader) strava.maps.loader = new strava.maps.Loader();

                if (options.url)
                    strava.maps.loader.getJSON(options.url, function(data) {
                        route.data = data;

                        for (var i in route.data["y_keys"]) {
                            var graph = new strava.graphs.Graph({ index: i, data: route.data, elevation: options.elevation });
                            route.graphs.push(graph);
                            if (options.elevation)
                                graph.move(options.elevation.index);
                        }

                    });
                else if (options.data)
                    polyline.setGoogle(options.path);

            },

            Activity: function(id, elevation, options) {
                options = $.extend({
                    url: "/activities/" + id + "/graph_data",
                    id: id,
                    elevation: elevation
                }, options);

                return new strava.graphs.Route(options);
            },

            Effort: function(id, elevation, options) {
                options = $.extend({
                    url: "/segment_efforts/" + id + "/graph_data",
                    id: id,
                    elevation: elevation
                }, options);

                return new strava.graphs.Route(options);
            }
        };

    if (!window["strava"]["elevation"])
        window["strava"]["elevation"] = {
            EffortHighlights: function() {
                var efforts = new Array();
                var current_effort = null;

                this.selected = null;
                var effort_highlights = this;

                this.addSelector = function(selector) {
                    $(".elevation ." + selector + ".bar").each(function() {
                        efforts.push(new strava.elevation.EffortHighlight(this, { type: selector }));
                    });
                };

                this.setSelected = function(selected) {
                    effort_highlights.selected = selected;
                };

                this.move = function(position) {
                    var min_effort = null;
                    var min_containment = null;
                    for (var i = 0; i < efforts.length; i++) {
                        var effort = efforts[i];
                        if (effort.type == effort_highlights.selected) {
                            if (effort.left <= position && position <= effort.right) {
                                var current_containment = Math.abs(position - effort.left) + Math.abs(position - effort.right);
                                if (min_containment == null || current_containment < min_containment) {
                                    min_effort = effort;
                                    min_containment = current_containment;
                                }
                            }
                        }
                    }

                    if (min_effort != null) {
                        if (current_effort != min_effort && current_effort != null)
                            current_effort.fade();
                        if (!min_effort.highlighted) {
                            min_effort.highlight();
                            current_effort = min_effort;
                        }
                    }

                    if (min_effort != current_effort && current_effort != null) {
                        current_effort.hide();
                        current_effort = null;
                    }
                };
            },

            EffortHighlight: function(object, options) {
                options = $.extend({ type: "climb"}, options);

                this.type = options.type;
                this.bar = $(object);
                this.left = parseInt(this.bar.css("left"));
                this.right = this.left + this.bar.width();
                this.id = object.id.substring(object.id.lastIndexOf("-") + 1);
                this.row = $("#segment-effort-row-" + this.id);
                this.pad = $("#" + this.type + "-pad-" + this.id);
                this.highlighted = false;
                var effort = this;

                this.highlight = function() {
                    effort.highlighted = true;
                    effort.row.addClass("highlighted");
                    effort.bar.stop().show();
                    effort.pad.stop().show();
                    effort.bar.css("opacity", 1);
                    effort.pad.css("opacity", 1);
                };

                this.fade = function() {
                    effort.highlighted = false;
                    effort.row.removeClass("highlighted");
                    effort.bar.stop().animate({opacity: 0}, 2000);
                    effort.pad.stop().animate({opacity: 0}, 2000);
                };

                this.hide = function() {
                    effort.highlighted = false;
                    effort.row.removeClass("highlighted");
                    effort.bar.stop().hide();
                    effort.pad.stop().hide();
                };
            },

            Display: function(options) {
                options = $.extend({ type: "tracer", index: 0 }, options);

                // display the 'data', data is not an index
                this.move = function(data) {
                    if (!data) return false;
                    if (options.type == "rabbit") {
                        var rabbitTime = data[1];
                        var rabbitTimeLabel = strava.i18n.shortTime(rabbitTime);

                        var rabbitTimeDesc = null;
                        if (rabbitTime <= 0) rabbitTimeDesc = "ahead";
                        else rabbitTimeDesc = "behind";

                        $("#rabbit-time-" + options.index).text(rabbitTimeLabel);
                        $("#rabbit-post-time-" + options.index).text(rabbitTimeDesc);

                    } else {
                        var elevTime = data[2];
                        var elevAltitude = data[3];
                        $("#dot-data-elapsed").text(strava.i18n.shortTime(elevTime));
                        $("#dot-data-elev").text(elevAltitude);
                        for (var e = 4; e < data.length; e++)
                            $("#dot-data-" + (e - 4)).text(data[e]);
                    }
                };
            },

            Tracer: function(route, data, options) {
                options = $.extend({
                    selector: "#elevation-dot",
                    map: route.map,
                    icon: true,
                    index: 0,
                    dot: 1}, options);

                this.route = route;
                this.displays = new Array();
                this.object = $(options.selector);
                this.data = data;
                this.icon = null;
                this.map = options.map;
                var tracer = this;
                this.data_length = null;
                if (this.data) this.data_length = this.data.length;

                if (options.icon && this.map) this.icon = new strava.maps.Icon("tracer_"+options.dot, null, this.map);

                this.object.show();
                this.y_offset = parseInt(this.object.css("bottom"));

                if (this.data) this.displays.push(new strava.elevation.Display({ type: "rabbit", index: options.index }));
                else this.displays.push(new strava.elevation.Display({ type: "tracer", index: options.index }));

                this.fixIndex = function(index){
                    if (!tracer.data_length) return index;
                    if (index <= 0) return 0;
                    if (index >= tracer.data_length) return tracer.data_length-1;
                    return index;
                };

                this.move = function(index) {
                    // NOTE: the tracer index is the index into elevation data
                    // set, not the stream data set, e.g. if the elevation data set
                    // contains 300 points (but the stream has 3000) this index
                    // will be between 0 and 300
                    var fixed_index = tracer.fixIndex(index);

                    // if this is a rabbit, get its index into the route,
                    // e.g. if the 'main' index is 99, then find out what index
                    // the rabbit has when 'main' is at 99
                    if (tracer.data) fixed_index = tracer.data[fixed_index][0];

                    // move tracer on the elevation chart
                    var x_position = tracer.route.xPosition(fixed_index);
                    var y_position = tracer.route.yPosition(fixed_index);
                    tracer.object.css("left", x_position - 12);
                    tracer.object.css("bottom", y_position + tracer.y_offset - 9);

                    // if there's an icon on the map for this tracer then position
                    // it using latlng
                    if (options.icon) tracer.icon.move(tracer.route.latLng(fixed_index));

                    // if the tracer has data (it's a rabbit) move it by the tracer data
                    // otherwise move it my the route data
                    for (var d in tracer.displays)
                        if (tracer.data) tracer.displays[d].move(tracer.data[fixed_index]);
                        else tracer.displays[d].move(tracer.route.data[fixed_index]);
                };

                this.move(0);
            },

            Route: function(options) {
                options = $.extend({
                    climbs: false,
                    best_efforts: false,
                    laps: false,
                    selector: "#elevation-chart",
                    property: "width",
                    url: null,
                    data: null,
                    map: null,
                    tracer: true
                }, options);

                this.object = $(options.selector);

                if (options.property == "width") this.size = this.object.width();
                else this.size = this.object.height();
                this.efforts = null;
                this.registrations = new Array();
                this.data = null;
                this.tracers = new Array();
                this.step = null;
                this.map = options.map;
                this.registrations = new Array();
                this.index = 0;
                this.position = 0;
                this.latlng = new Array();
                this.data_length = 0;

                var route = this;

                if (options.climbs || options.best_efforts || options.laps) this.efforts = new strava.elevation.EffortHighlights();
                if (options.climbs) this.efforts.addSelector("climb");
                if (options.best_efforts) this.efforts.addSelector("best-effort");
                if (options.laps) this.efforts.addSelector("lap");

                if (!strava.maps.loader) strava.maps.loader = new strava.maps.Loader();

                this.addTracer = function(data, options) {
                    route.tracers.push(new strava.elevation.Tracer(route, data, options));
                };

                this.fixIndex = function(index){
                    if (index <= 0) return 0;
                    if (index >= route.data_length) return route.data_length-1;
                    return index;
                };

                this.xPosition = function(index) {
                    return route.fixIndex(index) * route.step;
                };

                this.yPosition = function(index) {
                    if (!route.data) return 0;
                    return route.data[route.fixIndex(index)][1];
                };

                this.latLng = function(index) {
                    if (route.latlng) return route.latlng[route.fixIndex(index)];
                    else return null;
                };

                this.setSelected = function(selected) {
                    if (this.efforts) this.efforts.setSelected(selected);
                };

                this.register = function(registration) {
                    this.registrations.push(registration);
                };

                this.object.mousemove(function(e) {

                    route.position = 0;
                    if (options.property == "width") route.position = e.pageX - $(this).offset().left;
                    else route.position = e.pageY - $(this).offset().top;

                    route.index = Math.floor(route.position / route.step);

                    for (var t in route.tracers)
                        route.tracers[t].move(route.index);

                    if (route.efforts) route.efforts.move(route.position);

                    for (var r in route.registrations)
                        route.registrations[r].move(route.index, route.position);
                });

                this.load = function(data){
                    route.data = data.data;
                    if (options.tracer) route.addTracer(null);

                    route.data_length = route.data.length;
                    route.step = route.size / route.data_length;

                    for (var l in route.data)
                        route.latlng.push(new google.maps.LatLng(route.data[l][0][0], route.data[l][0][1]));
                };

                this.loadRabbits = function(rabbitData) {
                    for (var d in rabbitData.data)
                      route.addTracer(rabbitData.data[d], { selector: "#rabbit-dot-" + d, index: d, dot: parseInt(d)+2 });

                    for (var l in rabbitData.labels) {
                      $("#rabbit-label-" + l).text(rabbitData.labels[l]);
                      $("#rabbit-legend-" + l).show();
                    }
                };

                this.load(options.data);

                // load rabbits if there are any
                if (options.url)
                  strava.maps.loader.getJSON(options.url, function(rabbitData) { route.loadRabbits(rabbitData); });
            },

            Activity: function(data, options) {
                options = $.extend({
                    climbs: true,
                    best_efforts: true,
                    laps: true,
                    data: data,
                    tracer: true,
                    map: null }, options);

                return new strava.elevation.Route(options);
            },

            Effort: function(data, options) {
                options = $.extend({
                    climbs: false,
                    best_efforts: false,
                    laps: false,
                    data: data,
                    tracer: true,
                    map: null }, options);

                return new strava.elevation.Route(options);
            }

        };

    if (!window["strava"]["maps"])
        window["strava"]["maps"] = {
            loader: null,

            Map: function(options) {
                options = $.extend({
                    selector: "#map_canvas",
                    type: google.maps.MapTypeId.TERRAIN,
                    index: 0,
                    width: null,
                    height: null }, options);

                this.object = $(options.selector + ":eq(" + options.index + ")");

                if (options.width) this.object.css("width", options.width);
                if (options.height) this.object.css("height", options.height);

                this.google = new google.maps.Map($(options.selector)[options.index]);
                this.google.setMapTypeId(options.type);

                var map = this;

                if (!strava.maps.loader) strava.maps.loader = new strava.maps.Loader();

                this.center = function(x1, y1, x2, y2) {
                    var bounds = new google.maps.LatLngBounds();
                    bounds.extend(new google.maps.LatLng(x1, y1));
                    bounds.extend(new google.maps.LatLng(x2, y2));
                    map.google.fitBounds(bounds);
                };

                this.getZoom = function(){
                    return this.google.getZoom();
                };
            },

            Polyline: function(url, map, options) {
                options = $.extend({
                    color: "#F00",
                    weight: 3,
                    opacity: 1,
                    center: true,
                    callback: null,
                    clickable: false,
                    path: new google.maps.MVCArray()
                }, options);

                this.map = map;
                this.path = options.path;
                this.google = null;
                this.bounds = null;
                this.color = options.color;

                var polyline = this;

                this.hide = function() {
                    polyline.google.setMap(null);
                };

                this.show = function() {
                    polyline.google.setMap(polyline.map.google);
                };

                this.setColor = function(color) {
                    polyline.color = color;
                    polyline.google.setOptions({ strokeColor: polyline.color });
                };

                this.setOpacity = function(opacity) {
                    polyline.google.setOptions({ strokeOpacity: opacity });
                };

                this.getLength = function() {
                    return polyline.path.getLength();
                };

                this.getAt = function(index) {
                    return polyline.path.getAt(index);
                };

                this.first = function() {
                    return polyline.path.getAt(0);
                };

                this.last = function() {
                    return polyline.path.getAt(polyline.getLength() - 1);
                };

                this.setPath = function(path) {
                    polyline.google.setPath(path);
                };

                this.center = function() {
                    if (!polyline.bounds) {
                        polyline.bounds = new google.maps.LatLngBounds();
                        polyline.google.getPath().forEach(function(element) {
                            polyline.bounds.extend(element);
                        });
                    }
                    polyline.map.google.fitBounds(polyline.bounds);
                };

                this.setGoogle = function(path) {
                    polyline.google = new google.maps.Polyline({
                        path: path,
                        strokeColor: options.color,
                        strokeOpacity: options.opacity,
                        strokeWeight: options.weight,
                        clickable: options.clickable,
                        map: polyline.map.google
                    });

                    if (options.center) polyline.center();
                    if ($.isFunction(options.callback)) options.callback(polyline);
                };

                if (url) {
                    strava.maps.loader.getJSON(url, function(data) {
                        for (var i in data)
                            polyline.path.push(new google.maps.LatLng(data[i][0], data[i][1]));

                        polyline.setGoogle(polyline.path);
                    });
                } else if (options.path)
                    polyline.setGoogle(options.path);
            },

            ZoomLoader: function(selector, route, options) {
                options = $.extend({
                    color: "#105CB6",
                    opacity: 0.75,
                    loader: "/images/ajax-loader.gif",
                    route: null,
                    hide: true }, options);

                var segments = new Array();
                var selected = null;

                $(selector).live("click", function() {
                    var id = parseInt($(this).attr("value"));
                    if (selected) segments[selected].hide();
                    if (options.hide && selected == id) return route.center();

                    if (!id)
                    if (route.highlight) route.highlight.center();
                    else route.center();
                    else {
                        var object = this;
                        $(object).parent().append("<img id='zoom_loader' src='" + options.loader + "' />");
                        $(object).hide();
                        segments[id] = new strava.maps.Segment(id, route.map, {
                            color: options.color,
                            opacity: options.opacity,
                            callback: function() {
                                $("#zoom_loader").remove();
                                $(object).show();
                                selected = id;
                            }
                        });
                    }
                });
            },

            Route: function(options) {
                var route = this;

                options = $.extend({
                    url: null,
                    map: null,
                    start: null,
                    end: null,
                    markers: false,
                    markers_url: null,
                    center: true,
                    color: "#FF0000",
                    opacity: 1,
                    hidden: false,
                    id: null,
                    callback: null}, options);

                this.id = options.id;
                this.map = options.map;
                this.markers = new Array();
                this.dependents = new Array();
                this.highlight = null;
                this.rabbit = null;
                this.start_icon = null;
                this.end_icon = null;

                this.center = function() {
                    if (route.polyline) route.polyline.center();
                };

                this.toggleDependent = function(start_index, end_index) {
                    var index = start_index + "|" + end_index;
                    if (route.dependents[index] && route.dependents[index].google.map) route.hideDependent(start_index, end_index);
                    else route.showDependent(start_index, end_index);
                };

                this.hideDependent = function(start_index, end_index) {
                    var index = start_index + "|" + end_index;
                    if (route.dependents[index]) {
                        route.dependents[index].hide();
                        route.polyline.center();
                    }
                };

                this.showDependent = function(start_index, end_index) {
                    for (var d in route.dependents)
                        route.dependents[d].hide();
                    var index = start_index + "|" + end_index;
                    if (route.dependents[index]) {
                        route.dependents[index].show();
                        route.dependents[index].center();
                    }
                    else {
                        var sub_polyline = new google.maps.MVCArray();
                        for (var p = start_index; p < end_index; p++)
                            sub_polyline.push(route.polyline.getAt(p));
                        route.dependents[index] = new strava.maps.Polyline(null, route.map, { path: sub_polyline, color: "#105CB6" });
                    }
                };

                this.setHighlight = function(start, end) {
                    var sub_polyline = new google.maps.MVCArray();
                    for (var p = start; p <= end; p++)
                        sub_polyline.push(route.polyline.getAt(p));
                    if (!route.highlight) route.highlight = new strava.maps.Polyline(null, route.map, { path: sub_polyline, color: "#105CB6" });
                    else route.highlight.setPath(sub_polyline);
                };

                this.hide = function() {
                    if (route.polyline) route.polyline.hide();
                    for (var m in route.markers)
                        route.markers[m].hide();
                    for (var d in route.dependents)
                        route.dependents[d].hide();
                };

                this.show = function() {
                    if (route.polyline) route.polyline.show();
                    for (var m in route.markers)
                        route.markers[m].show();
                    for (var d in route.dependents)
                        route.dependents[d].show();
                };

                this.setStart = function(index) {
                    route.start_icon = new strava.maps.Icon(index, route.polyline.first(), route.map);
                    route.markers.push(route.start_icon);
                };

                this.setEnd = function (index) {
                    route.end_icon = new strava.maps.Icon(index, route.polyline.last(), route.map);
                    route.markers.push(route.end_icon);
                };

                this.setMarkers = function(url) {
                    route.markers.push(new strava.maps.Markers(url, route.map, { polyline: route.polyline }));
                };

                new strava.maps.Polyline(options.url, options.map, {
                    callback: function(polyline) {
                        route.polyline = polyline;
                        route.polyline.setColor(options.color);
                        route.polyline.setOpacity(options.opacity);
                        if (options.center) route.polyline.center();
                        if (options.start) route.setStart(options.start);
                        if (options.end) route.setEnd(options.end);
                        if (options.hidden) route.hide();
                        if (options.markers && options.markers_url) route.setMarkers(options.markers_url);

                        if ($.isFunction(options.callback)) options.callback(route);
                    }
                });
            },

            Effort: function(id, map, options) {
                options = $.extend({
                    url: "/segment_efforts/" + id + "/map_data",
                    map: map,
                    start: null,
                    end: null,
                    markers: false,
                    color: "#105CB6",
                    center: true,
                    id: id
                }, options);

                return new strava.maps.Route(options);
            },

            Segment: function(id, map, options) {
                options = $.extend({
                    url: "/segments/" + id + "/map_data",
                    map: map,
                    start: null,
                    end: null,
                    markers: false,
                    color: "#FF0000",
                    center: true,
                    id: id
                }, options);

                return new strava.maps.Route(options);
            },

            Activity: function(id, map, options) {
                options = $.extend({
                    url: "/activities/" + id + "/map_data",
                    map: map,
                    start: "start",
                    end: "end",
                    markers: true,
                    markers_url: "/activities/" + id + "/map_markers",
                    color: "#FF0000",
                    center: true,
                    id: id
                }, options);

                return new strava.maps.Route(options);
            },

            Icon: function(index, position, map, options) {
                options = $.extend({
                    position: position,
                    map: map.google,
                    draggable: false,
                    clickable: false,
                    sprite: strava.asset.sprite() }, options);

                this.position = options.position;
                this.map = map;
                this.index = index;
                this.sprite = strava.asset.image(options.sprite);

                var size = new google.maps.Size(24, 24);
                var icon = this;

                this.show = function() {
                    icon.google.setVisible(true);
                };

                this.hide = function() {
                    icon.google.setVisible(false);
                };

                this.move = function(latlng) {
                    icon.google.setPosition(latlng);
                    icon.position = latlng;
                };

                this.center = function(){
                    if (icon.map) icon.map.google.setCenter(icon.position);    
                };

                this.visible = function(){
                    if (!icon.map) return false;
                    return icon.map.google.getBounds().contains(icon.position);
                };

                this.newPoint = function(lat, lng) {
                    return new google.maps.Point(lat, lng);
                };

                this.iconOptions = function(start, end, center_lat, center_lng, use_size) {
                    if (!center_lat) center_lat = 12;
                    if (!center_lng) center_lng = 12;
                    if (!use_size) use_size = size;

                    return { icon: new google.maps.MarkerImage(icon.sprite, use_size, this.newPoint(start, end), this.newPoint(center_lat, center_lng))};

                };

                var mile_marker_regexp = /mile_marker_(\d+)/;
                this.mile_marker = 0;
                if (mile_marker_regexp.test(index)) this.mile_marker = parseInt(mile_marker_regexp.exec(index)[1]);

                var tracer_marker_regexp = /tracer_(\d+)/;
                this.tracer_marker = 0;
                if (tracer_marker_regexp.test(index)) this.tracer_marker = parseInt(tracer_marker_regexp.exec(index)[1]);

                if (index == "start")
                    options = $.extend(this.iconOptions(288, 96), options);
                else if (index == "end")
                    options = $.extend(this.iconOptions(264, 96), options);
                else if (1 <= this.tracer_marker && 3 >= this.tracer_marker)
                    options = $.extend($.extend(this.iconOptions((this.tracer_marker + 12) * 24, 120, 12, 24),{
                        shadow: new google.maps.MarkerImage(this.sprite, this.size, this.newPoint(312,144), this.newPoint(0,24)) }), options);
                else if (1 <= this.mile_marker && 10 >= this.mile_marker)
                    options = $.extend(this.iconOptions((this.mile_marker - 1) * 24, 0), options);
                else if (15 <= this.mile_marker && 95 >= this.mile_marker)
                    options = $.extend(this.iconOptions((this.mile_marker - 15) / 5 * 24, 24), options);
                else if (100 <= this.mile_marker && 190 >= this.mile_marker)
                    options = $.extend(this.iconOptions((this.mile_marker - 100) / 10 * 24, 48), options);
                else if (200 <= this.mile_marker && 290 >= this.mile_marker)
                    options = $.extend(this.iconOptions((this.mile_marker - 200) / 10 * 24, 72), options);
                else if (index == "best_effort_1")
                    options = $.extend(this.iconOptions(0, 96), options);
                else if (index == "best_effort_5")
                    options = $.extend(this.iconOptions(24, 96), options);
                else if (index == "best_effort_10")
                    options = $.extend(this.iconOptions(48, 96), options);
                else if (index == "best_effort_20")
                    options = $.extend(this.iconOptions(72, 96), options);
                else if (index == "best_effort_30")
                    options = $.extend(this.iconOptions(96, 96), options);
                else if (index == "segment_start")
                    options = $.extend(this.iconOptions(312, 96), options);
                else if (index == "cat_1")
                    options = $.extend(this.iconOptions(120, 96), options);
                else if (index == "cat_2")
                    options = $.extend(this.iconOptions(144, 96), options);
                else if (index == "cat_3")
                    options = $.extend(this.iconOptions(168, 96), options);
                else if (index == "cat_4")
                    options = $.extend(this.iconOptions(192, 96), options);
                else if (index == "cat_HC")
                    options = $.extend(this.iconOptions(216, 96), options);
                else if (index == "select-start")
                    options = $.extend(this.iconOptions(48, 120, 12, 34, new google.maps.Size(24, 34)), options);
                else if (index == "select-end")
                    options = $.extend(this.iconOptions(24, 120, 12, 34, new google.maps.Size(24, 34)), options);

                this.google = new google.maps.Marker(options);
            },

            Markers: function(url, map, options) {
                options = $.extend({
                    polyline: null,
                    callback: null }, options);

                this.map = map;
                this.icons = new Array();
                var markers = this;

                this.show = function() {
                    for (var i in markers.icons)
                        markers.icons[i].show();
                };

                this.hide = function() {
                    for (var i in markers.icons)
                        markers.icons[i].hide();
                };

                strava.maps.loader.getJSON(url, function(data) {
                    for (var i in data)
                        if (data[i]["data"] instanceof Array)
                            markers.icons.push(new strava.maps.Icon(data[i]["icon"], new google.maps.LatLng(data[i]["data"][0], data[i]["data"][1]), markers.map));
                        else if (options.polyline)
                            markers.icons.push(new strava.maps.Icon(data[i]["icon"], options.polyline.getAt(data[i]["data"]), markers.map));

                    if ($.isFunction(options.callback)) options.callback(markers);
                });
            },

            // Loader acts as a client side cache for loading any server-side data.
            // Use: strava.maps.loader.getJSON(url, callback)
            Loader: function() {
                this.cache = {};
                var loader = this;

                this.hasItem = function(url) {
                    return typeof(loader.cache[url]) != 'undefined';
                };

                this.getJSON = function(url, callback) {
                    if (loader.hasItem(url)) return callback(loader.cache[url]);

                    $.getJSON(url, function(data) {
                        strava.maps.loader.cache[url] = data;
                        callback(loader.cache[url]);
                    });
                };
            }
        };

    $(document).ready(function(){
        $(".strava-icon").css("width", "24px").css("height", "24px").css("background-image", "url("+strava.asset.image(strava.asset.sprite())+")").css("display", "block").css("background-repeat", "no-repeat");
        $(".strava-icon.icon-cat-1").css("background-position", "-120px -96px");
        $(".strava-icon.icon-cat-2").css("background-position", "-144px -96px");
        $(".strava-icon.icon-cat-3").css("background-position", "-168px -96px");
        $(".strava-icon.icon-cat-4").css("background-position", "-192px -96px");
        $(".strava-icon.icon-cat-HC").css("background-position", "-216px -96px");
        $(".strava-icon.icon-dot-1").css("background-position", "-312px -96px");
        $(".strava-icon.icon-dot-2").css("background-position", "-336px -96px");
        $(".strava-icon.icon-dot-3").css("background-position", "-360px -96px");
        $(".strava-icon.icon-dot-4").css("background-position", "-384px -96px");
        $(".strava-icon.icon-dot-5").css("background-position", "-408px -96px");
        $(".strava-icon.icon-hidden").hide();
    });
})(jQuery);