/**
 * Map tools
 *
 * @author dettier, mista_k
 */
(function () {

    // Stockholm city
    var defaulPoint = {
        lat: 59.33284,
        lng: 18.06427,
        zoom: 13
    };

    var ampPattern = /&amp;/g;

    TRAVEL.namespace('TRAVEL.Map', 'TRAVEL.Map.Control', 'TRAVEL.Map.Util');

    /**
     * Google map «on demand» loader
     *
     * @singleton
     *
     * @method  init        Fetch base component from Google
     * @method  start       Called by init method after base component was loaded
     * @method  complete    Perform additional loadings and fire «complete» event
     *
     * @event   complete
     */

    TRAVEL.Map.Loader = (function () {
        var self = TRAVEL.Observable2();

        self.init = function () {
            var script = document.createElement("script");
            script.src = TRAVEL.mapLink + '&callback=TRAVEL.Map.Loader.start';
            script.type = "text/javascript";
            document.getElementsByTagName("head")[0].appendChild(script);
        };

        self.start = function () {
            if (typeof google !== 'undefined') {
                google.load('maps', '2', {
                    'callback': self.complete,
                    'language': TRAVEL.language || 'en'
                });
            }
        };

        self.complete = function () {
            $.getScript(baseUrl + 'new-js/map/extinfowindow.js', function () {
                self.notify('complete');
            });
        };

        return self;
    })();


    /**
     * «Super first» page map widget
     *
     * @event   complete
     */
    TRAVEL.Map.SuperFirst = function (root) {

        var self = TRAVEL.Observable2(this);
        var map = null;

        // zoom handle
        var zoomHandle = null;
        
        // private shortcuts
        var util = TRAVEL.Map.Util, gm;


        function drawBorders(map, borders) {

            var i, j;
            var border, bordersCount, coordsCount;
            var polygonArray, polygon;

            for (i = 0, bordersCount = borders.length; i < bordersCount; i++) {
                polygonArray = [];
                border = borders[i];

                for (j = 0, coordsCount = border.coordinates.length; j < coordsCount; j++) {
                    polygonArray.push(new gm.LatLng(border.coordinates[j].lat, border.coordinates[j].lng));
                }

                if (coordsCount > 0) {
                    polygonArray.push(new gm.LatLng(border.coordinates[0].lat, border.coordinates[0].lng));
                }

                polygon = new gm.Polygon(polygonArray, "#000000", 1, 1, "#000000", 0.0);
                border.polygon = polygon;

                // TODO: XN-114
                // map.addOverlay(polygon);

                (function(region) {
                    gm.Event.addListener(polygon, "click", function(overlay, point) {
                        self.notify("onRegionClick", region.link);
                    });
                })(borders[i]);
            }

            if (borders.length > 1) {
                gm.Event.addListener(map, "mousemove", function(p) {
                    for (var i = 0; i < borders.length; i++) {
                        if (util.contains(borders[i].polygon, p)) {
                            self.notify("onRegionMoveIn", borders[i].name);
                            return;
                        }
                    }

                    self.notify("onRegionMoveOut");
                });
            }
        }
        


        function drawMap(map, model) {

            model = $.extend(model || {}, defaulPoint);
            map.setCenter(new gm.LatLng(model.lat, model.lng), model.zoom);

            gm.Event.addListener(map, 'zoomIn', function () {
                map.zoomIn();
                self.notify('zoom');
            });

            gm.Event.addListener(map, 'zoomOut', function () {
                map.zoomOut();
                self.notify('zoom');
            });

            gm.Event.addListener(map, 'moveNorth', function () {
                map.panDirection(0, 1);
            });

            gm.Event.addListener(map, 'moveSouth', function () {
                map.panDirection(0, -1);
            });
            gm.Event.addListener(map, 'moveEast', function () {
                map.panDirection(-1, 0);
            });

            gm.Event.addListener(map, 'moveWest', function () {
                map.panDirection(1, 0);
            });

            // add borders
            if (model.borders) {
                drawBorders(map, model.borders);
            }
        }


        self.isShown = function () {
            return !!map;
        };

        self.getGoogleMap = function () {
            return map;
        };

        self.create = function (model) {
            try {
                if (!map) {
                    if (typeof google === 'undefined') {
                        TRAVEL.Map.Loader.attachObserver('complete', function () {
                            gm = google.maps;
                            map = new gm.Map2(root);

                            // bind custom gadgets and events
                            map.addControl(TRAVEL.Map.Control.zoom());
                            map.addControl(TRAVEL.Map.Control.pan());

                            $('body').unload(gm.Unload);
                            drawMap(map, model);

                            self.notify('complete');
                        });
                        TRAVEL.Map.Loader.init();
                    }
                } else {
                    drawMap(map, model);
                }
            } catch (exc) {}
        };


        self.placeMarkersInProgress = false;
        self.placeMarkersRequest = false;

        function updateMarkers(markersData, provider) {

            map.clearOverlays();
            
            // Create a base icon for all of our markers that specifies the
            // shadow, icon dimensions, etc.
            var baseIcon = {
                image: baseUrl + 'i-wl/map/search-marker.png',
                shadow: baseUrl + 'new-i/s.gif',
                iconSize: new gm.Size(17, 26),
                shadowSize: new gm.Size(0, 0),
                iconAnchor: new gm.Point(1, 24),
                infoWindowAnchor: new gm.Point(13, 3),
                transparent: baseUrl + 'i-wl/map/search-marker-transparent.png',
                imageMap: [3,2, 13,2, 15,4, 15,15, 13,17, 8,17, 1,24, 1,4, 3,2]
            };

            /* Based on article «Running CPU Intensive JavaScript Computations in a Web Browser»
               http://www.julienlecomte.net/blog/2007/10/28/                                        */
            (function (data) {
                var CHUNK_LENGTH = 20;
                var i = 0;

                (function () {
                    for (var j = 0; j < CHUNK_LENGTH && i < data.length; j++) {
                        (function (mData) {
                            var marker = new gm.Marker(new gm.LatLng(mData.x, mData.y), {
                                'icon': new gm.Icon(baseIcon),
                                'title': mData.n
                            });
                            map.addOverlay(marker);
                            gm.Event.addListener(marker, "click", function() {
                                provider.apply(marker, [mData.id, marker]);
                            });
                        })(data[i]);
                        i++;
                    }

                    if (i < data.length && !self.placeMarkersRequest) {
                        // Process next chunk
                        setTimeout(arguments.callee, 0);
                    } else {
                        // finalize process
                        self.placeMarkersInProgress = false;
                    }
                })();

            })(TRAVEL.Map.Util.clusterMarkers(map, markersData));
        }


        self.placeMarkers = function (data, provider) {
            var retryCount = 20;
            // set flag
            self.placeMarkersRequest = true;
            // wait until previous placing in progress
            (function () {

                if (!self.placeMarkersInProgress) {

                    self.placeMarkersRequest = false;
                    self.placeMarkersInProgress = true;

                    if (data.length === 0) {
                        map.clearOverlays();
                        self.placeMarkersInProgress = false;
                        return;
                    }

                    if (zoomHandle) {
                        gm.Event.removeListener(zoomHandle);
                    }

                    TRAVEL.Map.Util.setBounds(map, data);

                    updateMarkers(data, provider);
                    
                    zoomHandle = gm.Event.addListener(map, 'zoomend', function () {
                        updateMarkers(data, provider);
                    });

                } else {
                    // retry
                    retryCount--;
                    if (retryCount) {
                        setTimeout(arguments.callee, 100);
                    }
                }
            })();

        };
    };


    /**
     * Utilities
     *
     */
    $.extend(TRAVEL.Map.Util, {
        /**
         * @method  contains    Does check of a finding of a point in a polygon
         * @param   poly
         * @param   point
         */
        contains: function(poly, point) {
            var i, j;
            var xi, yi, xj, yj;
            var oddNodes = false;
            var x = point.lng();
            var y = point.lat();
            var vertexCount = poly.getVertexCount() - 1;

            for (i = 0, j = 0; i <= vertexCount; i++) {

                j = (j >= vertexCount) ? 0 : j + 1;

                yi = poly.getVertex(i).lat();
                yj = poly.getVertex(j).lat();

                if ((yi < y && yj >= y) || (yj < y && yi >= y)) {

                    xi = poly.getVertex(i).lng();
                    xj = poly.getVertex(j).lng();

                    if ((xi + (xj - xi) * (y - yi) / (yj - yi)) < x) {
                        oddNodes = !oddNodes;
                    }
                }
            }

            return oddNodes;
        },

        /**
         * @method  setBounds   Compute center point and bounds level
         * @param   map
         * @param   data
         */
        setBounds: function (map, data) {
            var gm = google.maps;
            var bounds, i;
            var lat, lng, z;

            bounds = new gm.LatLngBounds();

            for (i = 0; i < data.length; i++) {
                lat = data[i].lat || data[i].x || defaulPoint.lat;
                lng = data[i].lng || data[i].y || defaulPoint.lng;
                bounds.extend(new gm.LatLng(lat, lng));
            }

            if (data.length === 1) {
                z = data[0].zoom;
            } else {
                z = map.getBoundsZoomLevel(bounds);
            }

            map.setCenter(bounds.getCenter(), z);
        },


        /**
         * @method  filterData  Filter data accoding curent zoom lever
         * @param   map         Google map object
         * @param   data        Array to filter
         * @param   field
         * @return              Filtered array
         */
        clusterMarkers: function (map, data, field) {

            var GRID_SIZE = 4;

            field = field || 'r';

            var gm = google.maps;
            var i, j;
            var r = [], marker, lat, lng, p;

            for (i = 0; i < data.length; i++) {
                if ((typeof data[i].lat !== 'undefined' || typeof data[i].x !== 'undefined') &&
                    (typeof data[i].lng !== 'undefined' || typeof data[i].y !== 'undefined')) {
                    // process marker
                    lat = data[i].lat || data[i].x;
                    lng = data[i].lng || data[i].y;
                    p = map.fromLatLngToContainerPixel(new gm.LatLng(lat, lng));

                    marker = {
                        'id': data[i].id,
                        'n': data[i].n,     // name
                        'r': data[i].r,     // rating
                        'lat': lat,
                        'lng': lng,
                        'x': lat,           // compatible copy
                        'y': lng,
                        'px': p.x,          // screen x
                        'py': p.y           // screen y
                    };

                    r.push(marker);
                }
            }

            for (i = 0; i < r.length - 1; i++) {
                if (typeof r[i].cx === 'undefined' || typeof r[i].cy === 'undefined') {
                    // set center of cluster
                    r[i].cx = r[i].px;
                    r[i].cy = r[i].py;
                }

                for (j = r.length - 1; j > i; j--) {
                    if (Math.abs(r[i].cx - r[j].px) < GRID_SIZE && Math.abs(r[i].cy - r[j].py) < GRID_SIZE) {
                        lat = (r[i].cx + r[j].px) / 2;
                        lng = (r[i].cy + r[j].py) / 2;
                        // remove point with lower rating
                        if (r[i][field] < r[j][field]) {
                            r[i] = r[j];
                        }
                        r[i].cx = lat;
                        r[i].cy = lng;
                        r.splice(j, 1);
                    }
                }
            }

/*
            if (typeof TRAVEL.dump === 'undefined') {
                TRAVEL.dump = [];
            }

            TRAVEL.dump.push({'zoom': map.getZoom(), 'points': (function (r) {
                    var d = [], i;
                    for (i = 0; i < r.length; i++) {
                        d.push({
                            'id': r[i].id,
                            'n': r[i].n,
                            'r': r[i].r,
                            'x': r[i].x.toFixed(6),
                            'y': r[i].y.toFixed(6)
                        });
                    }
                    return d;
                })(r)
            });
*/

            return r;
        }
    });




    /**
     * Package of map controls
     *
     */
    TRAVEL.Map.Control = (function () {

        return {
            /**
             * Zoom slider
             *
             * @event   zoomIn
             * @event   zoomOut
             */
            zoom: function () {
                var gm = google.maps;
                var zoomGadget = Object.beget(new gm.Control());

                $.extend(zoomGadget, {
                    initialize: function (mapObj) {
                        var container = document.createElement('div');
                        container.className = 'zoom-gadget';

                        var img = document.createElement('div');
                        img.className = 'zoom-gadget-img';
                        container.appendChild(img);

                        var btnZoomOut = document.createElement('div');
                        btnZoomOut.style.left = '44px';
                        btnZoomOut.style.top = '7px';
                        btnZoomOut.title = 'Zoom Out';
                        btnZoomOut.className = 'zoom-gadget-btn';
                        gm.Event.addDomListener(btnZoomOut, 'click', function () {
                            gm.Event.trigger(mapObj, 'zoomOut');
                        });
                        container.appendChild(btnZoomOut);

                        var btnZoomIn = document.createElement('div');
                        btnZoomIn.style.left = '145px';
                        btnZoomIn.style.top = '7px';
                        btnZoomIn.title = 'Zoom In';
                        btnZoomIn.className = 'zoom-gadget-btn';
                        gm.Event.addDomListener(btnZoomIn, 'click', function () {
                            gm.Event.trigger(mapObj, 'zoomIn');
                        });
                        container.appendChild(btnZoomIn);

                        var slider = document.createElement('div');
                        slider.className = 'zoom-gadget-slider';
                        container.appendChild(slider);

                        mapObj.getContainer().appendChild(container);

                        // setup slider
                        $(slider).slider({
                            'animate': true,
                            'min': 4,
                            'max': 17,
                            'steps': 13,
                            'change': function (e, ui) {
                                var zoomLevel = $(slider).slider('value', 0);
                                var oldLevel = $(slider).data('mapZoomLevel');
                                if (oldLevel !== zoomLevel) {
                                    mapObj.setZoom(zoomLevel);
                                    $(slider).data('mapZoomLevel', oldLevel);
                                }
                            }
                        });
                        $(slider).data('mapZoomLevel', -1);

                        gm.Event.addListener(mapObj, 'zoomend', function (oldLevel, newLevel) {
                            if ($(slider).data('mapZoomLevel') !== newLevel) {
                                $(slider).slider("moveTo", newLevel, 0);
                            }
                        });

                        return container;
                    },

                    getDefaultPosition: function () {
                        return new gm.ControlPosition(gm.ANCHOR_BOTTOM_RIGHT, new gm.Size(91, 25));
                    }
                });

                return zoomGadget;
            },

            /**
             * Pan circle
             *
             * @event   moveNorth
             * @event   moveEast
             * @event   moveSouth
             * @evetn   moveWest
             */
            pan: function () {
                var gm = google.maps;
                var panGadget = Object.beget(new gm.Control());

                $.extend(panGadget, {
                    initialize: function (mapObj) {
                        var container = document.createElement('div');
                        container.className = 'pan-gadget';

                        var img = document.createElement('div');
                        img.className = 'pan-gadget-img';
                        container.appendChild(img);

                        var btnMoveNorth = document.createElement('div');
                        btnMoveNorth.style.left = '31px';
                        btnMoveNorth.style.top = '3px';
                        btnMoveNorth.title = 'Move north';
                        btnMoveNorth.className = 'pan-gadget-btn';
                        gm.Event.addDomListener(btnMoveNorth, 'click', function () {
                            gm.Event.trigger(mapObj, 'moveNorth');
                        });
                        container.appendChild(btnMoveNorth);

                        var btnMoveSouth = document.createElement('div');
                        btnMoveSouth.style.left = '31px';
                        btnMoveSouth.style.top = '59px';
                        btnMoveSouth.title = 'Move south';
                        btnMoveSouth.className = 'pan-gadget-btn';
                        gm.Event.addDomListener(btnMoveSouth, 'click', function () {
                            gm.Event.trigger(mapObj, 'moveSouth');
                        });
                        container.appendChild(btnMoveSouth);

                        var btnMoveEast = document.createElement('div');
                        btnMoveEast.style.left = '59px';
                        btnMoveEast.style.top = '31px';
                        btnMoveEast.title = 'Move east';
                        btnMoveEast.className = 'pan-gadget-btn';
                        gm.Event.addDomListener(btnMoveEast, 'click', function () {
                            gm.Event.trigger(mapObj, 'moveEast');
                        });
                        container.appendChild(btnMoveEast);

                        var btnMoveWest = document.createElement('div');
                        btnMoveWest.style.left = '3px';
                        btnMoveWest.style.top = '31px';
                        btnMoveWest.title = 'Move west';
                        btnMoveWest.className = 'pan-gadget-btn';
                        gm.Event.addDomListener(btnMoveWest, 'click', function () {
                            gm.Event.trigger(mapObj, 'moveWest');
                        });
                        container.appendChild(btnMoveWest);

                        mapObj.getContainer().appendChild(container);

                        return container;
                    },

                    getDefaultPosition: function () {
                        return new gm.ControlPosition(gm.ANCHOR_BOTTOM_RIGHT, new gm.Size(11, 25));
                    }
                });

                return panGadget;
            }
        };
    })();


})();