

var UI = {
    Utils: {
        addToContainer: function(element, container) {
            Element.cleanWhitespace($(container));
            if (container.innerHTML == "") {
                container.appendChild(element);
            }
            else {
                var lastElement = $(container).lastChild;
                Element.insert($(lastElement), { after: element });
            }
        },
        isWithinBounds: function(gLatLngInner, gLatLngOuter) {
            if (!gLatLngInner || !gLatLngOuter) return null;
            var innerSW = gLatLngInner.getSouthWest();
            var innerNE = gLatLngInner.getNorthEast();
            var outerSW = gLatLngOuter.getSouthWest();
            var outerNE = gLatLngOuter.getNorthEast();
            if (innerSW.lat() >= outerSW.lat() &&
                    innerSW.lng() >= outerSW.lng() &&
                    innerNE.lat() <= outerNE.lat() &&
                    innerNE.lng() <= outerNE.lat())
                return true;
            else
                return false;
        },
        boundsToGLatLngBounds: function(bounds) {
            var sw = new GLatLng(bounds.w, bounds.s);
            var ne = new GLatLng(bounds.e, bounds.n);
            return new GLatLngBounds(sw, ne);
        },
        getMapGExtendedBounds: function() {
            var extendedBounds = UI.Utils.getMapExtendedBounds();
            return UI.Utils.boundsToGLatLngBounds(extendedBounds);
        },
        gLatLngBoundsToBounds: function(gLatLngBounds) {
            var bounds = UI.Map.getBounds();
            var sw = bounds.getSouthWest();
            var ne = bounds.getNorthEast();
            var w = sw.lat();
            var s = sw.lng();
            var e = ne.lat();
            var n = ne.lng();

            return { w: w, e: e, n: n, s: s };
        },
        getMapExtendedBounds: function() {
            // gets current viewport bounds and extends size

            var bounds = UI.Map.getBounds();
            var sw = bounds.getSouthWest();
            var ne = bounds.getNorthEast();
            var w = sw.lat();
            var s = sw.lng();
            var e = ne.lat();
            var n = ne.lng();
            latWidth = Math.abs(w - e) / 2;
            lngHeight = Math.abs(n - s) / 2;
            var dw = w - latWidth;
            var de = e + latWidth;
            var dn = n + lngHeight;
            var ds = s - lngHeight;

            return { w: dw, e: de, n: dn, s: ds };
        },
        getMapCentre: function() {
            var centre = UI.Map.getCenter();
            return { lat: centre.lat(), lng: centre.lng() };
        }
    },
    Elements: {
        createDayOptions: function(select) {
            select = $(select);
            var points = Data.PointManager.getFirstDays();
            points.each(function(point) {
                var opt = new Element("option", { value: point.day_id });
                var date = point.date.getDate().toString().length == 1 ? '0' + point.date.getDate() : point.date.getDate();
                var month = (point.date.getMonth() + 1).toString().length == 1 ? '0' + (point.date.getMonth() + 1) : point.date.getMonth() + 1;
                var fullDate = date + '/' + month + '/' + point.date.getFullYear();
                opt.update(fullDate + " : " + point.title_en);
                UI.Utils.addToContainer(opt, select);
            });
        },
        selectValue: function(selectionBox, value) {
            var sel = $(selectionBox);
            // change selection
            for (var i = 0; i < sel.options.length; i++) {
                if (sel.options[i].value == value) {
                    sel.selectedIndex = i;
                    break;
                }
            }
        }
    },
    MarkerManager: {} 
};


var Data = {
    Utils: {
        inRange: function(num, lower, upper) {
            return (num >= lower && num <= upper);
        },
        mergeObjects: function(obj1, obj2) {
            // note overwrites obj1 keys that are in obj2
            $H(obj2).each(function(obj) {
                obj1[obj.key] = obj.value;
            });
            return obj1;
        }
    },
    Validation: {
        isInteger: function(s) {
            return parseInt(s, 10) === s;
        },
        isFloat: function(s) {
            return parseFloat(s) === s;
        },
        isAlphabet: function(s) {
            var alphaRegex = /^[a-zA-Z]+$/;
            if (s.match(alphaRegex))
                return true;
            return false;
        },
        isAllowed: function(s) {
            var regex = /[!@$%^&()<>?|_=+*\/\~:;{}]+/g;
            if (!s.match(regex))
                return true;
            return false;
        }
    },
    URLs: {
        map: "search-by-map.html",
        getDayPoints: "/search_winners/getDayPoints.asp",
        search: "/search_winners/searchWinners.asp"
    }
};
    var Marker = {
        create: function(point, name, html, marker_style) {
            var marker = new GMarker(point, { icon: marker_style, title: name });
            return marker;
        }
    };

    var ZoomLevel = {
        Constants: {
            country: [3, 4],  // lower = more zoomed in
            province: [5, 7],
            day: [8, 9]
        },
        Settings: {
            minZoom: [5, 7], // min levels points can show
            zoomHash: {}  
            // add points at specific zoom levels - { "dayid_sequence": 7" }  ex. day_id 4, sequence 46, zoom 3 - { 4_46": 3 }
        },
        minimum: 3,
        maximum: 9,
        ZoomIn: function() {
            var centreMap = false;
            var currLevel = ZoomLevel.getLevel();
            var previous = ZoomLevel.previous;
            var next;
            var zoom = UI.Map.getZoom();
            if (!currLevel) { // zoom level does not exist in Zoomlevel.Constants - so need to adjust zoom                
                if (previous) {
                    if (previous == "day") return;  // can't zoom in more than day level
                    else {

                        if (zoom > ZoomLevel.Constants[previous][1]) {
                            var next = Object.keys(ZoomLevel.Constants)[ZoomLevel.getZoomLevelIndex(previous) + 1];
                            var newZoom = ZoomLevel.Constants[next][0]; // zoom level
                            ZoomLevel.previous = next; // must be set before zoom change
                            UI.Map.setCenter(UI.Map.getCenter(), newZoom);
                        }
                    }
                }
            }
            ZoomLevel.previous = ZoomLevel.getLevel();
            if (previous != ZoomLevel.previous) document.fire("zoomlevel:change");
        },
        ZoomOut: function() {
            var currLevel = ZoomLevel.getLevel();
            var previous = ZoomLevel.previous;
            var next;
            var zoom = UI.Map.getZoom();
            if (!currLevel) { // zoom level does not exist in Zoomlevel.Constants - so need to adjust zoom                
                if (previous) {
                    if (previous == "country") return;  // can't zoom out more than country level
                    else
                        if (zoom < ZoomLevel.Constants[previous][0]) {
                        var next = Object.keys(ZoomLevel.Constants)[ZoomLevel.getZoomLevelIndex(previous) - 1];
                        var newZoom = ZoomLevel.Constants[next][1]; // zoom level   
                        ZoomLevel.previous = next;
                        UI.Map.setCenter(UI.Map.getCenter(), newZoom);
                    }
                }
            }
            ZoomLevel.previous = ZoomLevel.getLevel();
            if (previous != ZoomLevel.previous) document.fire("zoomlevel:change");
        },
        disableOnZoomLevelChangeEvent: function() {
            this.zoomLevelChangeEventEnabled = false;
        },
        enableOnZoomLevelChangeEvent: function() {
            this.zoomLevelChangeEventEnabled = true;
        },
        getZoomLevelIndex: function(level) {
            var keys = Object.keys(ZoomLevel.Constants);
            for (var i = 0; i < keys.length; i++) {
                if (level == keys[i]) return i;
            }
            return null;
        },
        getLevel: function(level) {
            if (!level)
                level = UI.Map.getZoom();

            var levels = ZoomLevel.Constants;
            var keys = Object.keys(levels);
            var currLevel = null;
            for (var i = 0; i < keys.length; i++) {
                var lowerLevel = levels[keys[i]][0];
                var upperLevel = levels[keys[i]][1];
                if (Data.Utils.inRange(level, lowerLevel, upperLevel)) {
                    currLevel = keys[i];
                    break;
                }
            }
            return currLevel;

        },
        onZoomEnd: function(oldLevel, newLevel) {
            UI.MarkerManager.removeTemporaryDayPoints();

            if (ZoomLevel.zoomLevelChangeEventEnabled) {
                var currLevel = ZoomLevel.getLevel(newLevel);
                if (oldLevel < newLevel) {
                    ZoomLevel.ZoomIn();
                }
                if (oldLevel > newLevel) {
                    ZoomLevel.ZoomOut();
                }
            }


            ZoomLevel.enableOnZoomLevelChangeEvent();
        },
        onZoomLevelChange: function(e) {
        },
        onDragEnd: function(e) {
        }
    };


    var SearchManager = Class.create({
        initialize: function() {
            this.requestTimer = null;
            this.requestParams = null;
            this.requestTimeout = 50; // timeout incase user changes options quickly
            this.defaultPageOptions = { orderBy: 'name', page: 1 };
            this.defaultSearchOptions = { searchType: 'coord' };
            this.searchOptions = this.defaultSearchOptions;
        },
        setRequestTimeout: function(timeout) {
            this.requestTimeout = timeout;
        },
        setSearchOptions: function(searchOptions) {            
            this.searchOptions = searchOptions;
        },
        getSearchOptions: function() {
            var searchOptions = this.searchOptions;
            this.searchOptions = this.defaultSearchOptions; // restore defaults
            return searchOptions;
        },
        getParams: function(searchOpts, bounds) {
            if (!bounds) return searchOpts;
            var params = bounds;
            params = Data.Utils.mergeObjects(params, searchOpts);
            return params;
        },
        requestResults: function() {
            UI.Search.showLoading();
            var params = this.requestParams;
            var searchManager = this;
            var url = Data.URLs.search;
            var page = this.requestParams.page;

            new Ajax.Request(url, {
                method: 'post',
                parameters: params,
                onSuccess: function(transport) {
                    var response = transport.responseJSON;

                    var winners = transport.responseJSON.winners;
                    var matches = parseInt(transport.responseJSON.matches);

                    if (winners) {
                        winners.each(function(winner) {
                            var dayPoint = Data.PointManager.getDay(winner.day_id);

                            winner.fromLocation = dayPoint.getFromLocation();
                            winner.toLocation = dayPoint.getToLocation();
                            winner.day = "Day " + winner.day_id + " - " + dayPoint.getFormattedDate();
                        });

                        UI.Search.display(winners, matches, page);
                    }
                    else
                        UI.Search.clear("No matches found.");

                }
            });
        },
        getResultsByProvince: function(province) {
        },
        getResultsByDay: function(day_id) {
            if (!day_id || day_id == '') return;
            Data.SearchManager.getResults({ day_id: day_id, orderBy: 'name', page: 1, searchType: 'day' });
        },
        getResultsByName: function(searchParams) {
            searchParams = Data.Utils.mergeObjects(searchParams, this.defaultPageOptions);
            searchParams = Data.Utils.mergeObjects(searchParams, { searchType: 'name' });
            Data.SearchManager.getResults(searchParams);
        },
        getResults: function(searchParams) {  // searchOpts { orderBy: 'name desc' }
            UI.Search.showLoading();

            if (this.requestTimer) {
                window.clearTimeout(this.requestTimer);
                this.requestParams = null;
            }
            this.requestParams = searchParams || this.getSearchOptions();

            if (this.requestParams.searchType == 'coord') {
                var bounds = UI.Utils.gLatLngBoundsToBounds(UI.Map.getBounds())
                this.requestParams = Data.Utils.mergeObjects(this.requestParams, bounds);
            }

            if (!this.requestParams.page)
                this.requestParams = Data.Utils.mergeObjects(this.requestParams, this.defaultPageOptions);

            this.requestTimer = window.setTimeout('Data.SearchManager.requestResults()', this.requestTimeout);
        },
        changePage: function(page) {
            this.requestParams.page = page;
            this.requestResults();
        },
        toggleSearch: function(searchColumn) {  // ie. 'name'
            // toggle search (ascending or descending), use same parameters (bounds)  
            // reset page to 1
            if (this.requestParams) {
                var orderBy = this.requestParams.orderBy;
                if (orderBy.indexOf(searchColumn) != -1) {
                    if (orderBy.indexOf('desc') != -1)
                        this.requestParams.orderBy = searchColumn;
                    else
                        this.requestParams.orderBy = searchColumn + ' desc';
                }
                else
                    this.requestParams.orderBy = searchColumn;

                this.requestParams.page = 1;
                this.requestResults();
            }
            return false;
        }
    });



    var Pager = Class.create({
        initialize: function(itemTotal, startPage){
            this.page = startPage;
            this.pageSize = 10;
            this.pageMax = Math.ceil(itemTotal*1.0 / this.pageSize);
            this.total = itemTotal;
        },
        next: function(){
            if (this.page < this.pageMax){
                this.page++;
                return this.page;
            }
            return null;
        },
        prev: function(){
            if (this.page > 1){
                this.page--;
                return this.page;
            }
            return null;
        },
        getMax: function(){
            return this.pageMax;
        },
        jumpTo: function(page){
            page = parseInt(page);
            if (!Data.Validation.isInteger(page)) return null;
            if (this.page != page && page > 0 && page <= this.pageMax){
                this.page = page;
                return page;
            }
            return null;
        },
        getResultDisplay: function(){
            //Results 10 to 20 of 28  
            return 'R&eacute;sultats ' + ((this.page-1)*this.pageSize+1) + ' &agrave; ' + (this.page*this.pageSize < this.total ? this.page*this.pageSize: this.total) + ' de ' + this.total;
        }
    });


    var SearchUI = Class.create({
        initialize: function() {
            this.panel = $('searchPanel');
            this.pager = new Pager(1);
            this.body = $('searchBody');
            this.title = $('searchTitle');
            this.resultDisplay = $('resultDisplay');
            this.pageInput = $('indexpage');
            this.searchPageMax = $('searchPageMax');
            this.pageInput.value = 1;

            this.fullPager = $('fullPager');
            this.litePager = $('litePager');
            this.bydate = $('bydate');
            this.nameInput = $('lastname');
            this.initialInput = $('initial');

            this.updateTitle("R&eacute;sultats de la recherche");


            var headerHTML, headerNoLinkHTML;
            headerHTML = '<tr>';
            headerHTML += '<td class="contentframework-dataheadertop"><h4><a href="" onclick="return Data.SearchManager.toggleSearch(\'name\');">Nom de famille</a></h4></td>';
            headerHTML += '<td class="contentframework-dataheadertop"><h4>Premi&egrave;re initiale</h4></td>';
            headerHTML += '<td class="contentframework-dataheadertop"><h4>Jour du relais</h4></td>';
            headerHTML += '<td class="contentframework-dataheadertop"><h4>De :</h4></td>';
            headerHTML += '<td class="contentframework-dataheadertop"><h4>&Agrave; :</h4></td>';
            headerHTML += '</tr>';
 

            headerNoLinkHTML = '<tr>';
            headerNoLinkHTML += '<td class="contentframework-dataheadertop"><h4>Nom de famille</h4></td>';
            headerNoLinkHTML += '<td class="contentframework-dataheadertop"><h4>Premi&egrave;re initiale</h4></td>';
            headerNoLinkHTML += '<td class="contentframework-dataheadertop"><h4>Jour du relais</h4></td>';
            headerNoLinkHTML += '<td class="contentframework-dataheadertop"><h4>De :</h4></td>';
            headerNoLinkHTML += '<td class="contentframework-dataheadertop"><h4>&Agrave; :</h4></td>';
            headerNoLinkHTML += '</tr>';

            this.headerHTML = headerHTML;
            this.headerNoLinkHTML = headerNoLinkHTML;
            this.clear();

        },
        hide: function() {
            this.panel.hide();
        },
        show: function() {
            this.panel.show();
        },
        showLoading: function() {
            this.show();
            this.clear("T&eacute;l&eacute;chargement des r&eacute;sultats...");
        },
        clear: function(msg) {
            this.body.update("");
            msg = msg || "";
            this.resultDisplay.update(msg);
            this.fullPager.hide();
            this.litePager.hide();
            if (msg != '') this.show();
        },
        display: function(results, matches, page) {
            this.show();
            if (results.length == 0 || matches == 0) {
                this.clear("Aucun r&eacute;sultat.");
                return;
            }

            this.pager = new Pager(matches, page);
            this.resultDisplay.update(this.pager.getResultDisplay());
            this.searchPageMax.update(this.pager.getMax());
            this.pageInput.value = page;
            if (this.pager.getMax() > 1) {
                this.fullPager.show(); this.litePager.hide();
            }
            else {
                this.fullPager.hide(); this.litePager.show();
            }

            var html = '';
            results.each(function(winner, index) {
                if (index % 2 == 0)
                    html += '<tr class="contentframework-altrow">';
                else
                    html += '<tr>';
                html += '<td>' + winner.last_name + '</td>';
                html += '<td>' + winner.first_name.substr(0, 1).toUpperCase() + '</td>';
                html += '<td>' + winner.day.replace("Day", "Jour") + '</td>';
                html += '<td>' + winner.fromLocation + '</td>';
                html += '<td>' + winner.toLocation + '</td>';
                html += '<tr>';
            });
            this.body.update(this.headerHTML + html);
            this.stripe();
        },
        updateTitle: function(msg) {
            this.title.update(msg);
        },
        updateResultDisplay: function(msg) {
            this.resultDisplay.update(msg);
        },
        stripe: function() {
            stripe();
        },
        searchDay: function() {
            if (this.bydate)
                Data.SearchManager.getResultsByDay(this.bydate.value);
            return false;
        },
        searchName: function() {
            UI.ErrorManager.clear();
            var hasError = false;
            var nameInput = '';
            var initialInput = '';
            if (this.nameInput)
                nameInput = this.nameInput.value;
            if (nameInput.length < 1) {
                document.fire("error:show", { error: { id: this.nameInput.id, msg: "Veuillez indiquer votre nom."} });
                hasError = true;
            }
            else if (!Data.Validation.isAllowed(nameInput)) {
                // error - write no results found
                hasError = true;
                document.fire("error:show", { error: { id: this.nameInput.id, msg: "Le nom contient des caract&egrave;res non valides."} });
            }
            if (this.initialInput) {
                initialInput = this.initialInput.value;
                if (!Data.Validation.isAllowed(initialInput)) {
                    // error - write no results found
                    //UI.Search.clear("No matches found.");
                    hasError = true;
                    document.fire("error:show", { error: { id: this.initialInput.id, msg: "Le initiales contient des caract&egrave;res non valides."} });
                }
            }

            if (hasError) return false;

            Data.SearchManager.getResultsByName({ name: nameInput, initial: initialInput });

            return false;

        },
        nextPage: function() {
            var page = this.pager.next();
            if (page)
                this.changePage(page);
            return false;
        },
        prevPage: function() {
            var page = this.pager.prev();
            if (page)
                this.changePage(page);
            return false;
        },
        changePage: function(page) {
            // ajax call to change page
            Data.SearchManager.changePage(page);
        },
        jumpToPage: function() {
            var page = this.pageInput.value;
            page = this.pager.jumpTo(page);
            if (page) {
                this.changePage(page);
            }
            return false;
        }

    });

    var ErrorManager = Class.create({
        initialize: function() {
            Element.observe(document, 'error:show', onError.bindAsEventListener(this));
            var errorManager = this;
            function onError(e) {
                var error = e.memo.error;
                if (error) {
                    errorManager.showError(error.id, error.msg);
                }
            }
            this.errorIDs = new Array();
        },
        addErrorID: function(id) {
            this.errorIDs.push(id);
        },
        showError: function(id, msg) {
            var anchorID = "anchor-byname";
            var errorID = "error-" + id;
            //Please enter the winner's last name.
            $("error-messagetop-" + id).update(msg);
            if ($("error-message-byname").innerHTML == "") {
                $("error-message-byname").update(msg);
            }
            else {
                $("error-message-byname").update($("error-message-byname").innerHTML + "<br />" + msg);
            }

            $(anchorID).up('tr').addClassName('contentframework-required-cellhighlight');
            $(anchorID).next('ul').show();
            //$(anchorID).up().next().down().next('input').addClassName('contentframework-required-highlight');
            $(id).addClassName('contentframework-required-highlight');
            $(errorID).show();
            $('error-top').show();
        },
        hideError: function(id) {
            var anchorID = "anchor-byname";
            var errorID = "error-" + id;
            $(anchorID).up('tr').removeClassName('contentframework-required-cellhighlight');
            $(anchorID).next('ul').hide();
            $(id).removeClassName('contentframework-required-highlight');
            $(errorID).hide();
        },
        clear: function() {
            $('error-top').hide();
            $("error-message-byname").update("");

            var errorManager = this;
            this.errorIDs.each(function(id) {
                $("error-messagetop-" + id).update("");
                errorManager.hideError(id);
            });
        }
    });


    var PointManager = Class.create({
        initialize: function() {
            this.collections = {};
            var collections = this.collections;
            Object.keys(ZoomLevel.Constants).each(function(zoomLevel) {
                collections[zoomLevel] = new PointCollection();
            });

            this.toUpdate = new Array();
            this.ajaxEnable = true;
            this.selectedDay = null;
            this.provinceCoord = {
                AB: { lat: 53.40953185308643, lng: -114.609375, zoom: 5 },
                BC: { lat: 53.40953185308643, lng: -117.4658203125, zoom: 5 },
                MB: { lat: 53.48804553605622, lng: -95.9326171875, zoom: 5 },
                NB: { lat: 46.6795944656402, lng: -65.1708984375, zoom: 6 },
                NL: { lat: 51.20688339486562, lng: -64.6875, zoom: 5 },
                NS: { lat: 45.166547157856016, lng: -62.40234375, zoom: 6 },
                NU: { lat: 69.59589006237648, lng: -92.28515625, zoom: 3 },
                NT: { lat: 62.99515845212052, lng: -117.509765625, zoom: 4 },
                ON: { lat: 47.040182144806664, lng: -86.0009765625, zoom: 5 },
                PE: { lat: 46.21785176740299, lng: -62.81982421875, zoom: 7 },
                QC: { lat: 49.809631563563094, lng: -72.1142578125, zoom: 5 },
                SK: { lat: 52.26815737376817, lng: -105.27099609375, zoom: 6 },
                YT: { lat: 64.39693778132846, lng: -126.298828125, zoom: 4 }
            };
        },
        getProvinceCoord: function(prov) {
            return this.provinceCoord[prov];
        },
        selectDay: function(day_id) {
            var dayPoint = this.getDay(day_id);
            var previous = this.selectedDay;
            this.selectedDay = dayPoint;
            //UI.MapUtils.updateMarkers({ prevSelectedDay: previous, selectedDay: dayPoint });
        },
        getAllDays: function() {
            return this.collections["day"].get();
        },
        getFirstDays: function() {
            var days = new Array();
            var collection = this.collections["day"];
            var dayCollection = collection.get();

            dayCollection.each(function(day) {
                var dayPoint = day.value.getFirstPoint();
                days.push(dayPoint);
            });

            return days.sortBy(function(day) { return day.date; });
        },
        addPoints: function(points, level, noUpdateMap) { // array of points
            var collection = this.collections["day"];
            var pointManager = this;

            points.each(function(point, index) {
                point.day_id = parseInt(point.day_id);
                var dayPoint = collection.get(point.day_id);
                point.sequence = parseInt(point.sequence);

                point.lat = parseFloat(point.lat);
                point.lng = parseFloat(point.lng);

                if (point.title_en) point.title_en = point.title_en.replace("Arrival Ceremony in Victoria", "Point de d&eacute;part est Victoria");
                if (point.title_en) point.title_en = point.title_en.replace("Sept-Iles", "Ville de Qu&eacute;bec");
                if (point.title_en) point.title_en = point.title_en.replace("Ville de Quebec", "Sept-&Icirc;les");    
                if (point.title_en) point.title_en = point.title_en.replace("Levis", "L&eacute;vis");    
                if (point.title_en) point.title_en = point.title_en.replace("Trois-Rivieres", "Trois-Rivi&egrave;res");    
                if (point.title_en) point.title_en = point.title_en.replace("Montreal", "Montr&eacute;al");                    

                if (dayPoint) {
                    var fullDay = dayPoint.getFirstPoint();
                    point.date = fullDay.date;
                    point.m_center = fullDay.m_center;
                    point.m_label_location = fullDay.m_label_location;
                    point.title_en = fullDay.title_en;
                }

                if (point.m_center) {
                    var coord = point.m_center.split(',');
                    point.m_center_lat = parseFloat(coord[0]);
                    point.m_center_lng = parseFloat(coord[1]);
                    point.zoom = parseInt(coord[2]);
                    //point.minZoom = point.zoom;
                }


                if (!dayPoint || !dayPoint.points[point.sequence]) {
                    // day or sequence does not exist, add it

                    dayPoint = collection.add(point);

                    if (!noUpdateMap || noUpdateMap == "undefined") {

                        dayPoint.setCompleted(true);
                    }
                    else {
                        /*
                        if (point.zoom < ZoomLevel.Constants["province"][0])
                        point.minZoom = point.zoom;
                        else
                        point.minZoom = ZoomLevel.Constants["province"][0];  // set minimum to province zoom level
                        */
                    }

                    pointManager.toUpdate.push({ point: point, level: level });
                }

            });
            if (!noUpdateMap) {
                document.fire("points:update", { points: this.getNewPoints() });
            }
        },
        getNewPoints: function() {
            var toUpdate = this.toUpdate;
            this.initZoomLevels(toUpdate);
            this.toUpdate = new Array();
            return toUpdate;
        },
        initZoomLevels: function(points) {
            var days = points.collect(function(pt) { return pt.point.day_id; }).uniq();
            var pointManager = this;
            days.each(function(day_id) {
                var dayPoint = pointManager.getDay(day_id);
                dayPoint.initZoomLevels();
            });
        },
        getProvinceBounds: function(province) {
            return this.collections["day"].getBounds(province);
        },
        getViewable: function() {
            return this.collections["day"].getViewable();
        },
        getByZoom: function(zoom) {
            return this.collections["day"].getByZoom(zoom);
        },
        getAllDayPoints: function() {
            var url = Data.URLs.getDayPoints + "?searchType=all_days";
            new Ajax.Request(url, {
                method: 'post',
                onSuccess: function(transport) {
                    var response = transport.responseJSON;
                    //alert(response);
                    var points = transport.responseJSON.points;
                    if (points) {
                        //alert("adding points : " + points.length);
                        Data.PointManager.addPoints(points, "day");
                    }

                    //UI.MapUtils.updateLines();

                    //Data.PointManager.createOverlay(ZoomLevel.getLevel());     
                }
            });
        },
        getPointsByCoordinates: function(bounds) {
            var url = Data.URLs.getDayPoints + "?searchType=coord";
            new Ajax.Request(url, {
                method: 'post',
                parameters: bounds,
                onSuccess: function(transport) {
                    var response = transport.responseJSON;
                    //alert(response);
                    var points = transport.responseJSON.points;
                    if (points) {
                        //alert("adding points : " + points.length);
                        Data.PointManager.addPoints(points, "day");
                    }
                    else
                        alert("Error getting points");

                    //UI.MapUtils.updateLines();

                    //Data.PointManager.createOverlay(ZoomLevel.getLevel());     
                }
            });
        },
        getPointsByDay: function(day_id) {
            var pointManager = this;
            var url = Data.URLs.getDayPoints + "?searchType=day";
            new Ajax.Request(url, {
                method: 'post',
                parameters: { day_id: day_id },
                onSuccess: function(transport) {
                    var response = transport.responseJSON;
                    //alert(response);
                    var points = transport.responseJSON.points;
                    if (points) {
                        Data.PointManager.addPoints(points, "day");
                        var dayPoint = Data.PointManager.getDay(day_id);
                        //pointManager.selectDay(day_id);
                        document.fire("panTo:day", { day: dayPoint });
                    }
                    else
                        alert("Error getting points");
                }
            });
        },
        hasPoints: function(centreLatLng, level, zoom) {  // centreLatLng not used anymore - using getBounds instead

            var bounds = UI.Map.getBounds();


            var collection = this.collections["day"];

            var dayCollection = collection.getByZoom(zoom);
            if (!dayCollection) return false;

            return !dayCollection.any(function(dayPoint) {
                // any daypoint that is not complete and is in the viewable bounds
                return !dayPoint.value.isComplete && dayPoint.value.inBounds(bounds);
            });

        },
        getDay: function(day_id) {
            return this.collections["day"].get(day_id);
        },
        loadDay: function(day_id) {
            if (day_id == '' || !day_id) return;
            $('byprovince').selectedIndex = 0;
            var dayPoint = this.getDay(day_id);

            Data.SearchManager.setSearchOptions({ day_id: day_id, searchType: 'day' });
            document.fire("panTo:day", { day: dayPoint });
        }


    });

    var DayPoint = Class.create({
        initialize: function(day_id) {
            this.points = {};
            this.day_id = parseInt(day_id);
            this.isComplete = false; // initialize.  isComplete when all points for day are added
        },
        setCompleted: function(isComplete) {
            this.isComplete = isComplete;
        },
        get: function(sequence) {
            if (sequence)
                return this.points[sequence];
            else {
                return $H(this.points).sortBy(function(point) {
                    return point.key * 1.0;
                });
            }
        },
        getFromLocation: function() {
            //var point = this.getFirstPoint();
            //return point.location_en + ", " + point.province;
            var fromStr = "";
            var location = this.title_en.split(" ");
            var isFrom = true;
            location.each(function(str) {
                if (str == "-") {
                    isFrom = false;
                }
                else if (isFrom) {
                    fromStr += " " + str;
                }
            });
            return fromStr;
            //return location[0];
        },
        getToLocation: function() {
            //var point = this.getLastPoint();
            //return point.location_en + ", " + point.province;

            var toStr = "";
            var isTo = false;
            var location = this.title_en.split(" ");
            location.each(function(str) {
                if (str == "-")
                    isTo = true;
                else if (isTo) {
                    toStr += " " + str;
                }
            });
            return toStr;
        },
        getFirstPoint: function() {
            return this.points[Object.keys(this.points).min()];
        },
        getLastPoint: function() {
            var lastSequence = Object.keys(this.points).max();
            return this.points[lastSequence];
        },
        getFormattedDate: function() {
            var point = this.getFirstPoint();
            var date = point.date.getDate().toString().length == 1 ? '0' + point.date.getDate() : point.date.getDate();
            var month = (point.date.getMonth() + 1).toString().length == 1 ? '0' + (point.date.getMonth() + 1) : point.date.getMonth() + 1;
            var fullDate = date + '/' + month + '/' + point.date.getFullYear();
            return fullDate;
        },
        isFirst: function(sequence) {
            return (this.points[sequence] == this.getFirstPoint());
        },
        isLast: function(sequence) {
            return (this.points[sequence] == this.getLastPoint());
        },
        getAtZoom: function(zoom) {
            return $H(this.points).findAll(function(point) {
                //writeLog("zoom: " + zoom + " minZoom:" + point.value.minZoom);
                return point.value.minZoom <= zoom;
            }).sortBy(function(pt) { pt.key * 1.0 });
        },
        add: function(point) {
            if (point.date)
                point.date = new Date(point.date);
            if (point.zoom) this.setIdealZoom(point.zoom);
            if (point.title_en) this.title_en = point.title_en;
            //if (point.minZoom) this.setMinZoom(point.minZoom);
            //if (point.maxZoom) this.setMaxZoom(point.maxZoom);
            this.points[point.sequence] = point;
        },
        // minimum zoom at which all points for this day are visible
        setMinZoom: function(zoom) {
            this.minZoom = zoom;
        },
        setIdealZoom: function(zoom) {
            this.idealZoom = zoom;
        },
        isVisibleAtIdealZoom: function() {
            if (this.idealZoom < this.minZoom)
                return false;
            else
                return true;
        },
        initZoomLevels: function() {
            var dayPoint = this;
            var minZoom = ZoomLevel.minimum;
            $H(this.points).each(function(pt) {
                var point = pt.value;
                point.minZoom = dayPoint.getDefaultMinZoom(point.sequence); // minimum point at which this point is visible
                minZoom = Math.max(minZoom, point.minZoom);
            });
            this.setMinZoom(minZoom);
        },
        // returns the minimum zoom level at which the given point should be shown on the map
        getDefaultMinZoom: function(sequence) {
            var point = this.points[sequence];
            var maxZoom = ZoomLevel.maximum;
            var minZoom = ZoomLevel.Settings.zoomHash[point.day_id + "_" + point.sequence];
            if (minZoom)
                return minZoom;

            if (this.isFirst(sequence)) {
                if (this.day_id % 2 == 0) // even days are put on minZoom[0]
                    minZoom = ZoomLevel.Settings.minZoom[0];
                else
                    minZoom = ZoomLevel.Settings.minZoom[0] + 1;
            }
            else if (this.isLast(sequence)) {
                minZoom = ZoomLevel.Settings.minZoom[1];
            }
            else {
                minZoom = Math.max(point.zoom, ZoomLevel.Settings.minZoom[1]);
            }
            return minZoom <= maxZoom ? minZoom : maxZoom;
        },
        getBounds: function() {
            var keys = Object.keys(this.points);
            if (keys.length == 0) return null;
            var points = this.points;

            var minLat, maxLat, minLng, maxLng;
            var point = points[keys[0]];
            minLat = point.lat; maxLat = point.lat;
            minLng = point.lng; maxLng = point.lng;
            keys.each(function(key) {
                point = points[key];
                if (point.lat < minLat) minLat = point.lat;
                if (point.lat > maxLat) maxLat = point.lat;
                if (point.lng < minLng) minLng = point.lng;
                if (point.lng > maxLng) maxLng = point.lng;
            });

            return { w: minLat, e: maxLat, n: maxLng, s: minLng };
        },
        inBounds: function(gLatLngBounds) {
            var bounds = gLatLngBounds;
            var keys = Object.keys(this.points);
            if (keys.length == 0) return null;
            var points = this.points;

            for (var i = 0; i < keys.length; i++) {
                var point = points[keys[i]];
                if (bounds.containsLatLng(new GLatLng(point.lat, point.lng)))
                    return true;
            }
            return false;
        },
        contains: function(latLng) {
            var bounds = this.getBounds();
            if (!bounds) return false;
            if (latLng.lat >= bounds.w && latLng.lat <= bounds.e && latLng.lng <= bounds.n && latLng.lng >= bounds.s)
                return true;
            else
                return false;
        }
    });

    var PointCollection = Class.create({
        initialize: function() {
            this.collection = new Hash();
        },
        add: function(point) {
            var dayPoint = this.collection.get(point.day_id);
            if (!dayPoint){
                dayPoint = new DayPoint(point.day_id);
            }
            dayPoint.add(point);
            this.collection.set(point.day_id, dayPoint);
            return dayPoint;
        },
        get: function(day_id){
            if (!day_id)
                return this.collection.sortBy(function(day){ return day.value.day_id; });
            else
                return this.collection.get(day_id);
        },
        getByZoom: function(zoom){
            var collection = this.collection.findAll(function(dayPoint){
                var point = dayPoint.value.getFirstPoint();
                return (point.zoom*1.0 <= zoom*1.0);
            });
            return collection;            
        },
        getAllPointsAtZoom: function(zoom){        
            // this includes province level points (first and last in sequence
            var points = new Array();
            
            var collection = this.collection.each(function(dayPoint){                
                points.concat(dayPoint.value.getAtZoom(zoom));
                //return (point.zoom*1.0 <= zoom*1.0);
            });
            
            
            return points;
        },
        getViewable: function(){
            var bounds = UI.Map.getBounds();
            var zoom = UI.Map.getZoom();
            var collection = this.getByZoom(zoom).findAll(function(dayPoint){
                return dayPoint.value.inBounds(bounds);
            });
            return collection;
        },
        getBounds: function(province){
            // get day points that are in this province
            // get bounds for each day -- find the min / max bounds of the province

            var dayCollection = this.collection.findAll(function(dayPoint){
                return dayPoint.value.getFirstPoint().province.indexOf(province) != -1; // get points in province
            });

            var minLat, maxLat, minLng, maxLng;
            minLat = 1000; maxLat = -1000;
            minLng = 1000; maxLng = -1000;
            dayCollection.each(function(dayPoint){
                var dayBounds = dayPoint.value.getBounds();
                if (dayBounds.w < minLat) minLat = dayBounds.w;
                if (dayBounds.e > maxLat) maxLat = dayBounds.e;
                if (dayBounds.s < minLng) minLng = dayBounds.s;
                if (dayBounds.n > maxLng) maxLng = dayBounds.n;
            });            
            
            return { w: minLat, e: maxLat, n: maxLng, s: minLng };
        }
    });


    var MapUtilsUI = Class.create({
        initialize: function() {
            document.observe('panTo:day', this.onPanToDay.bindAsEventListener(this));
            this.panMoveTimer = null;
            this.panMoveTimeout = 50;
        },
        centreMap: function(latLng, zoomLevel) {
            if (!latLng) {
                UI.Map.setCenter(UI.Map.getCenter(), zoomLevel);
            }
            // should get centre value from JSON
            // adjust zoom level to fit all points?
        },
        panTo: function(bounds, level) {

            /*alert(bounds.w);
            alert(bounds.e);
            alert(bounds.s);
            alert(bounds.n);*/
            var zoomLevel = UI.Map.getBoundsZoomLevel(UI.Utils.boundsToGLatLngBounds(bounds), new GSize(768, 350));
            var minZoomLevel = ZoomLevel.Constants[level][0];
            if (zoomLevel < minZoomLevel) zoomLevel = minZoomLevel;
            //UI.Map.panTo(new GLatLng((bounds.w*1.0 + bounds.e*1.0)/2.0, (bounds.n*1.0 + bounds.s*1.0)/2.0));
            UI.Map.setCenter(new GLatLng((bounds.w * 1.0 + bounds.e * 1.0) / 2.0, (bounds.n * 1.0 + bounds.s * 1.0) / 2.0), zoomLevel);
            //UI.Map.getBoundsZoomLevel(UI.Utils.boundsToGLatLngBounds(bounds));            
            //alert(zoomLevel);
            //UI.Map.setCenter(new GLatLng((bounds.w + bounds.e)/2, (bounds.n + bounds.s)/2), zoomLevel);            
        },
        panToProvince: function(province) {
            if (province == "") return;
            $('bydate').selectedIndex = 0;
            //Data.SearchManager.setSearchType('coord');
            Data.SearchManager.setSearchOptions({ province: province, searchType: 'province' });

            //var bounds = Data.PointManager.getProvinceBounds(province);
            //UI.MapUtils.panTo(bounds, "province");
            var coords = Data.PointManager.getProvinceCoord(province);
            UI.Map.setCenter(new GLatLng(coords.lat, coords.lng), coords.zoom);
        },
        panToDay: function(day) {
            // var bounds = Data.PointManager.getDayBounds(day_id);
        },
        onPanToDay: function(e) {
            // disable onZoomLevelChanged Event
            ZoomLevel.disableOnZoomLevelChangeEvent();

            var day = e.memo.day;
            var point = day.getFirstPoint();

            //var coord = point.m_center.split(',');
            //alert(point.m_center_lat + " " + point.m_center_lng + " " + point.zoom);
            UI.Map.setCenter(new GLatLng(point.m_center_lat * 1.0, point.m_center_lng * 1.0), point.zoom * 1.0);
            /*
            if (!day.isVisibleAtIdealZoom())
            UI.MarkerManager.addTemporaryDayPoint(day.day_id);
            */
            //writeLog("day_id: " + day.day_id + " zoom: " + point.zoom + " min zoom: " + day.minZoom + " isVisible: " + day.isVisibleAtIdealZoom(point.zoom));
            if (!day.isVisibleAtIdealZoom()) {
                UI.MarkerManager.addTemporaryDayPoint(day.day_id);
            }

        },
        panMove: function(x, y) {
            UI.Map.panDirection(x, y);
            //this.panMoveTimeout = 60;

            //this.panMoveTimer = window.setTimeout('UI.MapUtils.panMove(' + x + ',' + y + ')', this.panMoveTimeout);
        },
        cancelPanMove: function() {
            if (this.panMoveTimer)
                window.clearTimeout(this.panMoveTimer);
        },
        onMoveEnd: function(e) {
            Data.SearchManager.getResults();
            //writeLog(UI.Map.getCenter().lat() + " " + UI.Map.getCenter().lng() + " " + UI.Map.getZoom());
        },
        updateMarkers: function(dayMarkers) {
            //{ prevSelectedDay: previous, selectedDay: dayPoint }
            if (dayMarkers) {
                var prevSelectedDay = dayMarkers.prevSelectedDay;
                var selectedDay = dayMarkers.selectedDay;
                if (prevSelectedDay) {
                    var dayPoint = prevSelectedDay;
                    var points = dayPoint.get();
                    points.each(function(point) {
                        var pt = point.value;
                        if (pt.marker) UI.MarkerManager.removeMarker(pt.marker);
                        UI.MarkerManager.addPoint(pt, "day");
                    });
                }
                if (selectedDay) {
                    var dayPoint = selectedDay;
                    var points = dayPoint.get();
                    points.each(function(point) {
                        var pt = point.value;
                        if (pt.marker) UI.MarkerManager.removeMarker(pt.marker);
                        UI.MarkerManager.addPoint(pt, "day", blue_dot);
                    });
                }
            }
        },
        addMainOverlay: function() {
            var zoom = UI.Map.getZoom();
            var dayCollection = Data.PointManager.getAllDays();
            var mapUtils = this;

            var polylineEncoder = new PolylineEncoder(14, 2, 0.00005, true);  // 00001


            var startPoint = null;
            /*
            dayCollection.each(function(dayPoint) {

                //$H(points).each(function(pointSeq, index){
            //writeLog("day_id :" + dayPoint.value.day_id);
            var a_points = new Array();
            var lastPoint;
            var inbetween_day_points = new Array();
            var points = dayPoint.value.get();
            points.each(function(point, index) {
            //writeLog("sequence :" + point.value.sequence);
            //var point = pointSeq.value;
            //writeLog(point.lat);


                    if (Data.Validation.isFloat(point.value.lat) && Data.Validation.isFloat(point.value.lng)) {
            var gpoint = new GLatLng(point.value.lat, point.value.lng);
            if (index == 0) {
            if (lastPoint) {
            inbetween_day_points.push(lastPoint);  // last point from previous day
            inbetween_day_points.push(gpoint);  // first point of this day
            //UI.Map.addOverlay(new GPolyline(inbetween_day_points, "#FDAA0B", 4));
            UI.Map.addOverlay(polylineEncoder.dpEncodeToGPolyline(inbetween_day_points, "#FDAA0B", 3));
            inbetween_day_points.clear();
            lastPoint = null;
            }
            }
            //if (dayIndex == 0) alert(point.lat);
            a_points.push(gpoint);
            if (index == points.size() - 1) {
            lastPoint = gpoint;
            }
            }
            });
            UI.Map.addOverlay(polylineEncoder.dpEncodeToGPolyline(a_points, "#FDAA0B", 3));

                //UI.Map.addOverlay(new GPolyline(a_points, "#FDAA0B", 4));
            a_points.clear();



            });

            */

            var startPoint = null;
            var a_points = new Array();

            dayCollection.each(function(dayPoint) {

                //$H(points).each(function(pointSeq, index){
                //writeLog("day_id :" + dayPoint.value.day_id);
                var points = dayPoint.value.get();
                points.each(function(point) {
                    //writeLog("sequence :" + point.value.sequence);
                    //var point = pointSeq.value;
                    //writeLog(point.lat);
                    if (Data.Validation.isFloat(point.value.lat) && Data.Validation.isFloat(point.value.lng)) {
                        var gpoint = new GLatLng(point.value.lat, point.value.lng);

                        //if (dayIndex == 0) alert(point.lat);
                        a_points.push(gpoint);
                    }
                });



            });
            UI.Map.addOverlay(polylineEncoder.dpEncodeToGPolyline(a_points, "#ffaf00", 3, 0.7));


        },
        updateLines: function() {
            var zoom = UI.Map.getZoom();
            var dayCollection = Data.PointManager.getAllDays();
            UI.Map.clearOverlays();
            var mapUtils = this;
            //alert(zoom);
            dayCollection.each(function(dayPoint) {
                var points = dayPoint.value.getAtZoom(zoom);
                if (points)
                    mapUtils.addOverlay(points, zoom);
            });
            UI.MarkerManager.refresh();
        },
        addOverlay: function(points, zoom) {
            //var points = dayPoint.points;
            var a_points = new Array();
            //$H(points).each(function(pointSeq, index){
            points.each(function(point) {
                //var point = pointSeq.value;
                //writeLog(point.lat);
                var gpoint = new GLatLng(point.value.lat, point.value.lng);
                //if (dayIndex == 0) alert(point.lat);
                a_points.push(gpoint);
            });
            UI.Map.addOverlay(new GPolyline(a_points, "#FDAA0B", 5));
        }
    });


    function writeLog(msg) {
        $('errorLog').innerHTML += msg + "<br />";
    }    



