//////
// Copyright (C) 2009: Fabián Ezequiel Gallina
// Contact list: lugro-mesh-dev@lugro.org.ar
// Version 0.1
//
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//
//////

/**
 * This file is a complete rewrite of the WifiDog's original
 * hotspots_status_map.js written by Francois Proulx. Almost
 * everything has changed so I recommend you to take a look before
 * making any changes :).
 */

/**
 * @package    Nightwing
 * @subpackage WifiDog Extensions
 * @author     Fabián Ezequiel Gallina <galli.87@gmail.com>
 * @copyright  2009 Fabián Ezequiel Gallina
 * @version    Subversion $Id$
 * @link       http://nightwing.lugro-mesh.org.ar/
 */

/**
 * Constructor
 *
 * @params string frame The frame where the Google Map will be rendered
 * @params string images_path The root path were images are stored
 *
 * @return NightwingHotspotsMap instance
 */
function NightwingHotspotsMap(frame, images_path) {

    /**
     * Builds an GIcon object. This works as a factory method.
     *
     * @params string image The image the icon will use
     * @params GSize size The size of the image
     * @params string shadow The image used to render the shadow
     * @params GSize shadow_size The size of the shadow image
     * @params GPoint anchor The relative position of the Icon
     * @params GPoint bubble_anchor The relative position of the InfoWindow
     *
     * @return GIcon
     */
    this.build_icon = function(image, size, shadow, shadow_size, anchor, bubble_anchor) {
        var icon = new GIcon();
        icon.image = image;
        icon.iconSize = size;
        icon.shadow = shadow;
        icon.shadowSize = shadow_size;
        icon.iconAnchor = anchor;
        icon.infoWindowAnchor = bubble_anchor;
        return icon;
    }

    // Magic starts here.
    // This is where the constructor starts :)
    if (GBrowserIsCompatible()) {

        /**
         * The Google Map instance
         */
        this.map = new GMap2(document.getElementById(frame));
        this.map.addControl(new GLargeMapControl());
        this.map.addControl(new GMapTypeControl());

        /**
         * Images root folder path
         */
        this.images_path = images_path;

        /**
         * Internet label configured in VIS
         */
        this.internet_label = "0.00";

        /**
         * Internet address configured in VIS
         */
        this.internet_address = "0.0.0.0";

        /**
         * Internet id
         */
        this.internet_id = "0";

        /**
         * Line opacity for connections between nodes
         */
        this.connection_line_opacity = 0.8;

        /**
         * Line opacity for connections between nodes
         */
        this.connection_line_weight = 3;

        /**
         * Color used for the head of arrows
         */
        this.arrow_head_color = "#000";

        /**
         * Color used for high quality connections
         */
        this.high_quality_color = "#00ff00";

        /**
         * Color used for medium quality connections
         */
        this.medium_quality_color = "#f57900";

        /**
         * Color used for low quality connections
         */
        this.low_quality_color = "#ff0000";

        /**
         * High quality connection limit
         */
        this.high_quality_value_limit = 1.25;

        /**
         * Medium quality connection limit
         */
        this.medium_quality_value_limit = 1.67;

        /**
         * Offset for connection's parallel lines
         */
        this.connection_parallel_offset = parseFloat(+0.0001);

        /**
         * Icon used for nodes UP
         */
        this.up_icon = this.build_icon(
            this.images_path + "HotspotStatusMap/up.png", new GSize(20, 34),
            this.images_path + "HotspotStatusMap/shadow.png", new GSize(37, 34),
            new GPoint(10, 20), new GPoint(10, 1)
        );

        /**
         * Icon used for nodes DOWN
         */
        this.down_icon = this.build_icon(
            this.images_path + "HotspotStatusMap/down.png", new GSize(20, 34),
            this.images_path + "HotspotStatusMap/shadow.png", new GSize(37, 34),
            new GPoint(10, 20), new GPoint(10, 1)
        );

        /**
         * Icon used for nodes which are not DEPLOYED
         */
        this.unknown_icon = this.build_icon(
            this.images_path + "HotspotStatusMap/unknown.png", new GSize(22, 34),
            this.images_path + "HotspotStatusMap/blank.gif", new GSize(22, 34),
            new GPoint(11, 30), new GPoint(11, 1)
        );

        /**
         * Icon used to mark nodes connected to Internet
         */
        this.internet_icon = this.build_icon(
            this.images_path + "HotspotStatusMap/internet.png", new GSize(20, 20),
            this.images_path + "HotspotStatusMap/blank.gif", new GSize(20, 20),
            new GPoint(10, 30), new GPoint(0, 0)
        );

        /**
         * Icon displayed in all InfoWindow instances
         */
        this.info_window_icon = 'lugro-mesh.jpeg';

        /**
         * Indexed Array used to decide if we need to draw a parallel
         * with the offset defined in connection_parallel_offset
         */
        this.existing_polylines = new Array();

        /**
         * JSON array containing nodes objects
         */
        this.nodes = null;

        /**
         * JSON array containing relationships between nodes
         */
        this.connections = null;

    } else {
        alert("Google Maps is not supported in your browser");
    }

    /**
     * Sets the initial position and zoom of the map
     *
     * @params float lat The latitude coord.
     * @params float lng The longitude coord.
     * @params int zoom The zoom ratio.
     *
     * @return void
     */
    this.set_initial_position = function(lat, lng, zoom)
    {
        this.map.setCenter(new GLatLng(lat, lng), zoom);
    }

    /**
     * Sets the initial map type
     *
     * @params string map_type The map type
     *
     * @return void
     */
    this.set_map_type = function(map_type)
    {
        this.map.setMapType(map_type);
    }

    /**
     * Builds the node infoWindow
     *
     * @params GMarker marker The node marker
     * @params node node The node to generate the infoWindow
     *
     * @return GMarker
     */
    this.build_node_info_window = function(marker, node) {
        GEvent.addListener(
            marker, "click",
            function() { marker.openInfoWindow(node.html) }
        );
        return marker;
    }

    /**
     * Builds the HTML for the infoWindow of a node
     *
     * @params node node The node used to build the html
     *
     * @return string
     */
    this.build_node_html = function(node) {
        var html = '<table><tr><td><img src="' + this.images_path +
            "HotspotStatusMap/" + this.info_window_icon + '"/></td><td>';

        html += "<b>" + node.name + "</b>";
        html += "<br />"

        if(node.civic_number) {
            html += "<i>" + node.civic_number + ",&nbsp;</i>";
        }

        if(node.street_name) {
            html += "<i>" + node.street_name + ",&nbsp;</i>";
        }

        html += "<br />";

        if(node.city) {
            html += "<i>" + node.city + ",&nbsp;</i>";
        }

        if(node.province) {
            html += "<i>" + node.province + ",&nbsp;</i>";
        }

        html += "<br />";

        if(node.postal_code) {
            html += "<i>" + node.postal_code+ ",&nbsp;</i>";
        }

        if(node.country) {
            html += "<i>" + node.country + "</i>";
        }

        html += "<br />";

        if(node.telephone) {
            html += "<i>" + node.telephone + "</i><br />";
        }

        if(node.transit_info) {
            html += "<b>" + node.transit_info + "</b><br />";
        }

        if(node.website_url) {
            html += "<a href='" + node.website_url + "'>" + node.website_url + "</a>";
        }

        html += "</td></tr></table>";
        node.html = html;
    }

    /**
     * Draws the internet icon for a node
     *
     * @params node node The node to generate the internet icon
     *
     * @return void
     */
    this.draw_internet_icon = function(node) {
        var point = new GLatLng(parseFloat(node.latitude), parseFloat(node.longitude));
        var marker = new GMarker(point, this.internet_icon);
        this.map.addOverlay(marker);
    }

    /**
     * Calculates the appropiate color for the connection taking into
     * account the label values
     *
     * @params string|float label The label for the connection
     *
     * @return string Hexadecimal color
     */
    this.calculate_connection_color = function(label) {
        var color;
        label = parseFloat(label);
        if(label >= 1 && label <= parseFloat(this.high_quality_value_limit)) {
            color = this.high_quality_color;
        } else if(parseFloat(this.high_quality_value_limit) < label &&
                  label <= parseFloat(this.medium_quality_value_limit)) {
            color = this.medium_quality_color;
        } else {
            color = this.low_quality_color;
        }
        return color;
    }

    /**
     * Draws a connection between two nodes or adds the Internet
     * connection icon if the node's neighbour is the Internet
     *
     * @params node router The main node
     * @params node neighbour The neighbour node
     * @params string label The label for the connection
     *
     * @return void
     */
    this.draw_connection = function(router, neighbour, label) {

        // If the neighbour is internet then draw the internet icon
        if(neighbour.ip == this.internet_address) {
            this.draw_internet_icon(router);
            return;
        }

        var polyhash = "h" + router.latitude + router.longitude +
            neighbour.latitude + neighbour.longitude;

        var polyhashreverse =  "h" + neighbour.latitude + neighbour.longitude +
            router.latitude + router.longitude;

        // If there is no line drawn in between the nodes let the coords alone
        if(!this.existing_polylines[polyhash] &&
           !this.existing_polylines[polyhashreverse]) {
            rlatitude = router.latitude;
            rlongitude = router.longitude;
            nlatitude = neighbour.latitude;
            nlongitude = neighbour.longitude;
        } else {
            // A line exists between the nodes, lets draw a parallel
            rlatitude = parseFloat(router.latitude) + this.connection_parallel_offset;
            rlongitude = parseFloat(router.longitude) + this.connection_parallel_offset;
            nlatitude = parseFloat(neighbour.latitude) + this.connection_parallel_offset;
            nlongitude = parseFloat(neighbour.longitude) + this.connection_parallel_offset;
        }

        var arrow = new GArrow(
            this.map,
            [new GLatLng(rlatitude, rlongitude),
             new GLatLng(nlatitude, nlongitude)]
        );

        arrow.line_color = this.calculate_connection_color(label);
        arrow.head_color = this.arrow_head_color;
        arrow.opacity = this.connection_line_opacity;
        arrow.weight = this.connection_line_weight;
        arrow.opacity = this.connection_line_opacity;
        arrow.tooltip = "Link quality: " + Math.round(100/parseFloat(label)) + "%";
        arrow.draw();

        this.existing_polylines[polyhash] = true;
        this.existing_polylines[polyhashreverse] = true;
    }

    /**
     * Loads the map
     *
     * @return void
     */
    this.load = function() {
        var self = this;
        GDownloadUrl(
            'nightwing_hotspots_status.php',
            function(data, responseCode) {
                var json = eval('(' + data + ')');
                self.draw(json.nodes, json.connections)
	    }
        );
    }

    /**
     * Draws all map's elements
     * @param JSON nodes The nodes of the map
     * @param JSON connections The relationship representation of nodes
     *
     * @return void
     */
    this.draw = function(nodes, connections) {

        this.nodes = nodes;
        this.connections = connections;

        var node, icon;
        // Draw all nodes
        for(i in this.nodes) {
            node = nodes[i];
            if( node.node_id != "INTERNET" &&
                node.node_id != "INVALID") {
                if(node.status) {
                    switch(node.status.node_deployment_status) {
                    case 'DEPLOYED':
                    case 'IN_PLANNING':
                    case 'IN_TESTING':
                        if(node.status.is_up == 't') {
                            icon = this.up_icon;
                        } else {
                            icon = this.down_icon;
                        }
                        break;
                    default:
                        icon = this.unknown_icon;
                    }
                } else {
                    icon = this.unknown_icon;
                }
                var point = new GLatLng(parseFloat(node.latitude), parseFloat(node.longitude));
                var marker = new GMarker(point, icon);
                this.build_node_html(node)
                this.build_node_info_window(marker, node);
                this.map.addOverlay(marker);
            }
        }

        var connection, router, neighbour;
        // Draw all the connections between nodes
        for(i=0; i < this.connections.length; i++) {
            connection = this.connections[i];
            try {
                router = this.nodes[connection.router_id];
                router.ip = connection.router;
            } catch (e) {
                /* The router is not in the nodes array so the node is
                 * marked as INVALID */
                router = {"node_id": 'INVALID'};
            }
            try {
                neighbour = this.nodes[connection.neighbour_id]
                neighbour.ip = connection.neighbour;
            } catch (e) {
                if(connection.neighbour_id == this.internet_id) {
                    /* if the neighbour_id matches the internet_id then
                     * the node is marked as INTERNET */
                    neighbour = {
                        "node_id": 'INTERNET',
                        "ip": this.internet_address
                    };
                } else {
                    /* The neighbour is not in the nodes array and it
                     * is not INTERNET so the node is marked as
                     * INVALID */
                    neighbour = {"node_id": 'INVALID'};
                }
            }
            // We can't draw a connection between internet and a node
            if(router.node_id !== 'INVALID' &&
               router.node_id !== 'INTERNET') {
                // But we can draw a connection between a node and Internet
                if(neighbour.node_id != 'INVALID') {
                    this.draw_connection(router, neighbour, connection.label);
                }
            }
        }
    }
}

