
/**
 * the DealsNearMe class generates and manages a google
 * Map object and uses geolocation (if approved) to watch
 * the visitors position (also movement) and identify
 * potentially near users.
 **/
function DealsNearMe()
{
    this.map            = null;
    this.infowindow     = null;
    this.coords         = {};
    this.updateInterval = null;
    this.myPosMarker    = null;
    this.userMarkers    = [];
    this.autoComplete   = null;
    this.spiderAround   = null;
    this.eventSource    = null;

    this.markerIcons = {
        currentPosition: "http://hyperlocaldeals.com/static/images/google_maps_icon/me-marker.png",
        unloggedUser: "http://hyperlocaldeals.com/static/images/google_maps_icon/inactive-marker.png",
        loggedUser: "http://hyperlocaldeals.com/static/images/google_maps_icon/deal-icon.png",
        pulledOver: "http://hyperlocaldeals.com/static/images/google_maps_icon/yellow-marker.png",
        alertUser: "http://hyperlocaldeals.com/static/images/google_maps_icon/RedDot.gif"
    }

    /**
     * register a watchPosition interval. This function can be
     * used in pages where a Google Map is available. It will
     * get the visitor's current position by geolocation and
     * watch it move + update the marker when movement is
     * registered.
     *
     * @return DealsNearMe
     **/
    this.initialize = function()
    {
        var self = this;

        // get current position, then create map and center.
        this.watchMyPosition(function(obj, update_listing)
        {
            coordinates = obj.getCoordinates();
            current_pos = new google.maps.LatLng(coordinates.latitude, coordinates.longitude);

            if (obj.map != null) {
                obj.updateMyMarker(coordinates, true);
                return false;
            }

            // first time map creation.
            obj.map = new google.maps.Map(document.getElementsByClassName('map-canvas')[0], {
                center: current_pos,
                zoom: 14,
                scrollwheel: false
            });

            obj.initSpider();

            icon = obj.markerIcons.currentPosition;

            html = $("#my-marker").html();
            obj.myPosMarker = obj.createMarker(current_pos, html, icon, "My Position");

            // map is displayed, we can also get deal's positions
            obj.addMarkersFromDirectory();

            if (update_listing === true)
              obj.updateProjectsListing();
        });

        return this;
    }

    this.initSpider = function()
    {
        this.spiderAround = new OverlappingMarkerSpiderfier(this.getMap(), {markersWontMove: true, markersWontHide: true, keepSpiderfied: true});

        if (this.infowindow == null)
            this.infowindow = new google.maps.InfoWindow();

        var map  = this.getMap();
        var self = this;
        this.spiderAround.addListener("click", function(marker, event)
        {
            self.infowindow.setContent(marker.content);
            self.infowindow.open(self.getMap(), marker);
        });

        var shadow = new google.maps.MarkerImage(
            'https://www.google.com/intl/en_ALL/mapfiles/shadow50.png',
            new google.maps.Size(37, 34),  // size   - for sprite clipping
            new google.maps.Point(0, 0),   // origin - ditto
            new google.maps.Point(10, 34)  // anchor - where to meet map location
        );
        this.spiderAround.addListener('spiderfy', function(markers) {
            for(var i = 0; i < markers.length; i ++) {
                markers[i].setShadow(null);
            }
            self.infowindow.close();
        });
        this.spiderAround.addListener('unspiderfy', function(markers) {
            for(var i = 0; i < markers.length; i ++) {
                markers[i].setShadow(shadow);
            }
        });
    }

    /**
     * getter method for map property.
     *
     * @return google.maps.Map
     **/
    this.getMap = function()
    {
        return this.map;
    }

    /**
     * getter method for coords property.
     * Represent the current visitor's coordinates.
     *
     * @return Object {latitude: decimal, longitude: decimal}
     **/
    this.getCoordinates = function()
    {
        return this.coords;
    }

    /**
     * This method is used to execute the navigator.geolocation
     * API call to watchPosition(). Also it defines 2 callback
     * functions that will be called respectively on success
     * geolocation and on error.
     * The callback parameter must be a function that will
     * be executed after the geolocation process.
     *
     * @param callback  callable
     * @return DealsNearMe
     **/
    this.watchMyPosition = function(callback)
    {
        var obj = this;
        function successWatch(position)
        {
            user_id = $("#current_user_id").html();
            s_lat   = $("[name=search_location_lat]").val();
            s_lng   = $("[name=search_location_lng]").val();
            update_listing = false;

            if (!user_id && !s_lat && !s_lng) {
              // not logged in, show current pos but needs
              // update of the results listing.
              obj.coords = position.coords;
              update_listing = true;
            }
            else if (s_lat && s_lng) {
              // center on SEARCH LOCATION
              obj.coords = {latitude: s_lat, longitude: s_lng};
            }
            else {
              // center on ME POSITION
              geolocation = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
              obj.coords  = position.coords;
            }
            callback(obj, update_listing);
        }

        function errorWatch()
        {
            // The error callbacks simply uses 'fallback' coordinates
            // so that the features of the website are not disabled.
            fallback = $("#fallback-coordinates").text().split(",");
            obj.coords = {latitude: fallback[0], longitude: fallback[1]};
            callback(obj, false);
        }

        watchOpts = {
            enableHighAccuracy:true,
            timeout:60000,
            maximumAge:0
        };
        var watchId = navigator.geolocation.watchPosition(successWatch, errorWatch, watchOpts);
        return this;
    }

    /**
     * This method is used to execute the navigator.geolocation
     * API call to getCurrentPosition(). This is used to get a
     * coordinates pair about the actual current position. This
     * method does not track movement of the visitor, it only sets
     * the coords property of the DealsNearMe object and executes
     * the given callback function given as parameter.
     *
     * @param callback  callable
     * @return DealsNearMe
     **/
    this.getCurrentPosition = function(callback)
    {
        var obj = this;
        function successCallback(position)
        {
            geolocation = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
            obj.coords  = position.coords;
            callback(obj);
        }

        function errorCallback()
        {
            fallback = $("#fallback-coordinates").text().split(",");
            obj.coords = {latitude: fallback[0], longitude: fallback[1]};
            callback(obj);
        }

        watchOpts = {
            enableHighAccuracy:true,
            timeout:60000,
            maximumAge:0
        };
        var watchId = navigator.geolocation.getCurrentPosition(successCallback, errorCallback, watchOpts);
        return this;
    }

    /**
     * This method iterates through .user-coords elements
     * and creates markers for the given user on the map.
     *
     * @return DealsNearMe
     **/
    this.addMarkersFromDirectory = function()
    {
        var self = this;

        // also we need to iterate through listed users for generating
        // the markers on the map corresponding to each user.
        $(".user-coords").each(function()
        {
            var current_user = parseInt($("#current_user_id").text());
            coords  = $(this).attr("data-coordinates");
            uid     = $(this).attr("data-uid");

            if (uid == current_user)
                return true;

            info_html = $(this).find(".infowindow-content").html();

            if (coords == "Unknown")
                // no coords for user.
                return true;

            // create marker for the user.
            coords = coords.split(",");
            gpos   = new google.maps.LatLng(coords[0], coords[1]);

            icon = self.markerIcons.loggedUser;

            // create marker and store.
            marker = self.createMarker(gpos, info_html, icon, "Deal Position");
            self.userMarkers.push({"uid": uid, "marker": marker, "status": status});
        });

        //this.liveMarkersTick();
        return this;
    }

    /**
     * This method executes an AJAX request that fetches users
     * and their respective coordinates from the database.
     * This method is used in liveMarkersTick() to be executed
     * every 5 seconds.
     *
     * @return DealsNearMe
     **/
    this.fetchLiveMarkers = function()
    {
        var self = this;

        $.ajax({
            url: "/category/ajaxGetDealsCoordinates",
            success: function(d)
            {
                var d = eval(d);
                for (i in d) {
                    user_row   = d[i];
                    user_id    = user_row.id;
                    new_coords = user_row.coords;
                    html       = user_row.html;

                    geoloc     = new_coords.split(",");
                    new_coords = {latitude: geoloc[0], longitude: geoloc[1]};

                    // find marker and update (or create) as needed.
                    marker = self.updateUserMarker(user_id, new_coords, html);
                }
            },
            complete: function(e) {
                setTimeout(function() { self.fetchLiveMarkers(); }, 20000);
            }
        });

        return this;
    }

    /**
     * Allows to automatically check for new user positions
     * or logged_in states in real-time on the map.
     * @return void
     **/
    this.liveMarkersTick = function()
    {
        var self = this;
        setTimeout(function() { self.fetchLiveMarkers(); }, 20000);
    }
/*
    this.fetchLiveStatuses = function()
    {
        var self = this;

        $.ajax({
            url: "/directory/ajaxGetUsersStatuses",
            success: function(d)
            {
                var d = eval(d);
                for (i in d) {
                    user_row   = d[i];
                    user_id    = user_row.id;
                    status     = user_row.status;
                    text       = user_row.text;
                    color      = user_row.color;

                    status_before = $(".user-status[data-uid='" + user_id + "']").attr("data-status");

                    // update Current Status
                    $(".user-status[data-uid='" + user_id + "']").html(text);
                    $(".user-status[data-uid='" + user_id + "']").attr("data-status", status);
                    $(".user-status[data-uid='" + user_id + "']").css("color", color);

                    //if (status_before != status && (status == 'alert-family' || status == 'alert-nearby'))
                      // notify users with sound.
                    //  $("audio#alert_audio")[0].play();
                }
            },
            complete: function(d) {
                setTimeout(function() {self.fetchLiveStatuses();}, 1000);
            }
        });

        return this;
    }
*/
    this.liveStatusTick = function()
    {
        var self = this;
        //setTimeout(function() { self.fetchLiveStatuses(); }, 1000);

        if (typeof EventSource == "undefined")
            return false;

        self.eventSource = new EventSource("sse.php");

        /* A user updated his or her status. */
        self.eventSource.addEventListener('statuschange', function(e)
        {
            e = e || event, msg = '';
            //console.log('statuschange: ', e.data);
        }, false);

        self.eventSource.addEventListener('ping', function(e)
        {
            e = e || event, msg = '';
            //console.log('ping: ', e.data);
        }, false);

        self.eventSource.addEventListener('error', function(e)
        {
            e = e || event, msg = '';

            switch( e.target.readyState ){
            case EventSource.CONNECTING:
                break;

            // if error was fatal
            case EventSource.CLOSED:
                console.log("Connection closed.");
                break;
            }

        }, false);
    }


    /**
     * create a marker on the google map. Provided
     * argument MUST be a google.maps.LatLng object.
     *
     * @param coord     google.maps.LatLng
     * @param text      string
     * @param icon      string
     * @return DealsNearMe
     **/
    this.createMarker = function(coord, text, icon, title)
    {
        var self   = this;
        var marker = new google.maps.Marker({
            flat: true,
            map: this.getMap(),
            position: coord,
            icon: icon,
            content: text,
            title: title,
            optimized: false,
            visible: true
        });
        self.spiderAround.addMarker(marker);
        return marker;
    }

    /**
     * This method UPDATES a google map marker to the given
     * new coordinates. Also it will pan the map so that the
     * movement can be seen.
     *
     * @param coords                Object {latitude: decimal, longitude: decimal}
     * @param update_last_coord     boolean     wether to update the user coord in DB, or not
     * @param status                string      'ok', 'pulled', 'alert-family', 'alert-nearby', "alert-lawyers-nearby"
     * @return DealsNearMe
     **/
    this.updateMyMarker = function(coords, update_last_coord, status)
    {
        current_pos = new google.maps.LatLng(coords.latitude, coords.longitude);

        this.map.panTo(current_pos);
        this.myPosMarker.setPosition(current_pos);

        icon = this.markerIcons.currentPosition;

        this.myPosMarker.setIcon(icon);

        return this;
    }

    /**
     * update a user_id's current marker position to the last
     * one fetched from database. The coords parameter must be
     * a object containing 'latitude' and 'longitude' fields.
     *
     * @param   integer     user_id
     * @param   object      coords  MUST contain 'latitude' and 'longitude'
     * @param   boolean     is_logged_in
     * @return google.maps.Marker
     **/
    this.updateUserMarker = function(user_id, coords, html)
    {
        if (user_id == parseInt($("#current_user_id").text()))
            return false;

        var self = this;

        icon = self.markerIcons.loggedUser;

        current_pos = new google.maps.LatLng(coords.latitude, coords.longitude);
        marker      = null;

        for (i = 0; i < this.userMarkers.length; i++) {
            var obj = this.userMarkers[i];

            if (parseInt(obj.uid) != user_id)
                continue;

            marker = obj.marker;

            marker_lat = marker.getPosition().lat().toFixed(6);
            marker_lng = marker.getPosition().lng().toFixed(6)
            coords_lat = parseFloat(coords.latitude).toFixed(6);
            coords_lng = parseFloat(coords.longitude).toFixed(6);

            if (status != obj.status)
                // icon must be updated even if position did not change
                marker.setIcon(icon);

            // if the marker is at the same position we don't need to update.
            if (marker_lat == coords_lat && marker_lng == coords_lng)
                return marker;

            // marker needs an update, then finish.
            marker.setPosition(current_pos);
            return marker;
        }

        // this is a new user, not listed on the directory.
        // the marker will be created and then stored.

        // first we add the directory item to the bottom of the list.
        //$(".news-row").last().css("border-bottom", "1px solid #dcdcdc");
        //$(html).insertAfter($(".news-row").last());

        //info_html = $(".user-coords[data-uid='" + user_id + "'] .infowindow-content").html();
        //marker    = this.createMarker(current_pos, info_html, icon, "User Position");

        //this.userMarkers.push({"uid": user_id, "marker": marker, "status": status});
        return marker;
    }

    /**
     * geocode the entered address. This method is
     * called onblur of the Change Location field.
     *
     * @return DealsNearMe
     **/
    this.geocodeSearchAddress = function()
    {
        var ajson = "https://maps.googleapis.com/maps/api/geocode/json?address=" + $('[name=search_location]').val()
        $.ajax({
            dataType: "json",
            url: ajson,
            success: function(d) {
                z = d.results[0].geometry.location
                $('[name=search_location_lat]').val(z.lat);
                $('[name=search_location_lng]').val(z.lng);
            }
        });
        return this;
    }

    /**
     * update the projects listing on the right
     * of the deals-nearme map.
     *
     * @return DealsNearMe
     **/
    this.updateProjectsListing = function()
    {
        coords = this.getCoordinates();

        if (! $("#projects-wrapper .projects-listing-wrapper").length)
          return this;

        $.ajax({
            url: "/project/getNearbyProjects",
            type: "get",
            data: {
              latitude: coords.latitude,
              longitude: coords.longitude,
              is_mobile: 1
            },
            success: function(html)
            {
              $("#projects-wrapper .projects-listing-wrapper").html(html);
            }
        });

        return this;
    }
}
