// Global variables

var mapDiv; // location of google map div
var map;    // main map instance

var METERS_TO_DEGREES = 1/111111; // 1meter = 1/111,111 degrees latitude
var totalPoints;  // total # points considered in clustering algorithm 
                  // initialize to "total_points" attribute in XML data 

var clusters = [];     // stores clusters parsed from XML data 
var uniqueSizes = [];  // unique array of unique cluster sizes
var uniqueAttr = [];  // unique array of unique cluster sizes
var seen = [];         // determine whether a cluster size has been added to uniqueSizes

var DBG_FLAG = false;
var icons = [];         // icons for cluster density markers

var sortType = {
    SIZE : 1,
    TOTAL_DWELL : 2,
    AVG_DWELL : 3,
    VISITS : 4,
}

var sortTypeStr = [];
sortTypeStr[sortType.TOTAL_DWELL] = 'total dwell-time';
sortTypeStr[sortType.AVG_DWELL] = 'avg. dwell-time';
sortTypeStr[sortType.SIZE] = 'size';
sortTypeStr[sortType.VISITS] = '# visits';

var sortMetric;
// initializes instance of google maps
function initMap() {
	//map = new GMap2(mapDiv);					// create "map" object
    map = new GMap2(document.getElementById("map"));
	map.addControl(new GScaleControl());		// add scale control
	map.addControl(new GLargeMapControl());		// add large map controls
	map.addControl(new GOverviewMapControl());		// add mini-map
	map.addControl(new GMapTypeControl(true));	// add map type control
    map.setCenter(new GLatLng(42.3468, -71.0919), 11);
}

// initializes icons for cluster density labeling
function initIcons() {
    var icon_paths = new Array 
    (   "icons/i_30_blue.png", 
        "icons/i_60_green.png", 
        "icons/i_65_green.png", 
        "icons/i_70_yellow.png", 
        "icons/i_75_yellow.png", 
        "icons/i_80_orange.png", 
        "icons/i_85_orange.png", 
        "icons/i_90_red.png"
    );

    for (var i in icon_paths) {
        var path = icon_paths[i];
        var icon = new GIcon();
        icon.image = path;
        icon.shadow = "icons/shadow.png";
        icon.iconSize = new GSize(12,20);
        icon.shadowSize = new GSize(22,20);
        icon.iconAnchor = new GPoint(6, 18);    
        icon.infoWindowAnchor = new GPoint(9, 5);
        icons.push(icon);
    }
}


// ============
// CLASSES
// ============

// Constructor
// input: 
//  center - GLatLng object
//  radius - radius of cluster in meters 
//  size - number of data points in cluster
function Cluster(center_, radius_, size_, total_dwell_, avg_dwell_, visits_, id_) {
//function Cluster(center_, radius_, size_) {
    this.center = center_;
    this.radius = radius_;
    this.size = size_;
    this.total = total_dwell_;
    this.avg_dwell = avg_dwell_;
    this.nvisits = visits_;
    this.id = id_;


    // create marker
    // this marker is created before drawing, based on density
    this.cmarker;

    // create polyline circle
    this.circle = createCirclePolyline(center_,radius_);
    //this.circle = createClusterCircle(center_,radius_);
    if (DBG_FLAG)
        dbg("cluster [" + this.size + "]: (" + this.center.lat() + ", " + this.center.lng() + ")"); 

    this.info_contents = 
        "<b>id:</b> " + this.id + "<br>" +
        "<b>center:</b> " + this.center.lat() + ", " + this.center.lng() + "<br>"+
        "<b>size:</b> " + this.size + "<br>" +
        "<b>total dwell:</b> " + this.total/3600.0 + "hrs. <br>" +
        "<b>avg dwell:</b> " + this.avg_dwell/3600.0 + "hrs. <br>" +
        "<b># visits:</b> " + this.nvisits;
}


// input:   c is a cluster
// ouput:   returns a marker with appropriate info window labelling
function createClusterMarker(c, icon_index) {
    var marker = new GMarker(c.center, icons[icon_index]);
    GEvent.addListener(marker, "mouseover", 
        function() {
            marker.openInfoWindowHtml(c.info_contents);
        }
    );
    
    return marker;
}


function Cluster_draw(c) {
    map.addOverlay(c.cmarker);
    map.addOverlay(c.circle);
}

function Cluster_erase(c) {
    map.removeOverlay(c.cmarker);
    map.removeOverlay(c.circle);
}

/*
function createCirclePolyline_NEW(center, radius) {
    var points = [];
    var radians = Math.PI/180;
    var longitudeOffset = radius / (Math.cos(center.lat()*radians)*111325);
    var latitudeOffset= radius / 111325;
	for(var i=0; i<360; i+=circleQuality){
		var P = new GLatLng(
			center.lat() + latitudeOffset * Math.sin(i*radians),
			center.lng() + longitudeOffset * Math.cos(i*radians)
			);
		points.push(P);
	}
	points.push(points[0]);	// close the circle
    return new GPolyline(points);
}
*/
// input:
//  center - GLatLng object
//  radius - radius in meters
function createCirclePolyline(center, radius) {
	//Function created by Chris Haas
	var circleQuality = 30;			//1 is best but more points, 5 looks pretty good, too
	var M = Math.PI / 180;			//Create Radian conversion constant
	var L = map.getBounds();		//Holds copy of map bounds for use below
	var sw = L.getSouthWest();
	var ne = L.getNorthEast();
    var r = radius*METERS_TO_DEGREES;

	//The map is not completely square so this calculates the lat/lon ratio
	// this works because we create a square map
	var circleSquish = (ne.lng() - sw.lng()) / (ne.lat() - sw.lat());

	var points = [];    //Init Point Array
	//Loop through all degrees from 0 to 360
	for(var i=0; i<360; i+=circleQuality){
		var P = new GLatLng(
			center.lat() + (r * Math.sin(i * M)),
			center.lng() + (r * Math.cos(i * M)) * circleSquish
			);
		points.push(P);
	}
	points.push(points[0]);	// close the circle
    return new GPolyline(points);
}

// input:
//  load clusters from XML file
function loadClusters(file) {
    clear_clusters();
    var file_arg = file + '.xml';
    //map.clearOverlays();
    clusters=[];
    dbg_clear('dbg_txt');
    dbg_clear('info');
    dbg_clear('show');
    dbg('loading XML clusters from: '+ file_arg + '<br>', "loading", false);
    var request = GXmlHttp.create();
    request.open('GET', file_arg, true);
    request.onreadystatechange = function() {
        if (request.readyState == 4) {
            var xmlDoc = request.responseXML;
            var xml_clusters = xmlDoc.documentElement.getElementsByTagName("Cluster");
            var meta = xmlDoc.documentElement.getElementsByTagName("Data");
            var r = parseFloat(meta[0].getAttribute("radius"));
            totalPoints = parseInt(meta[0].getAttribute("total_points"));
            dbg("radius = "+ r + " miles","info");
            dbg("xml_clusters: " + xml_clusters.length + " over " + totalPoints + " datapoints","info");
            xml_parse_cluster(xml_clusters, r*1600);
        }
    }
    request.send(null);
}

function xml_parse_cluster(clusters_xml, r) {
    for (var i in clusters_xml) {
        var lat = clusters_xml[i].getElementsByTagName("Latitude");
        var lng = clusters_xml[i].getElementsByTagName("Longitude");
        var size = clusters_xml[i].getElementsByTagName("Size");
        
        var total_dwell = clusters_xml[i].getElementsByTagName("TotalDwell");
        var avg_dwell = clusters_xml[i].getElementsByTagName("AvgDwell");
        var visits = clusters_xml[i].getElementsByTagName("Visits");
        var id = clusters_xml[i].getElementsByTagName("Id");

        size = parseInt(size[0].firstChild.nodeValue);
        total_dwell = parseInt(total_dwell[0].firstChild.nodeValue);
        avg_dwell = parseInt(avg_dwell[0].firstChild.nodeValue);
        id = id[0].firstChild.nodeValue;
        visits = parseInt(visits[0].firstChild.nodeValue);

        lat = lat[0].firstChild.nodeValue;
        lng = lng[0].firstChild.nodeValue;
        var center = new GLatLng(parseFloat(lat),parseFloat(lng));
        var the_cluster = new Cluster(center, r, size, total_dwell, avg_dwell, visits, id);
        clusters.push(the_cluster);


        if (i == clusters_xml.length-1) {
            // sort descending
            sortClusters(sortType.TOTAL_DWELL);
            dbg('unique sizes: ' + uniqueSizes.length, "loading");
            if (DBG_FLAG) {
                for (var j in uniqueSizes) {
                    var val = uniqueSizes[j];
                    dbg(''+ val + ', i = ' + uniqueSizes.indexOf(val), "loading");
                } 
            } 
            dbg('*** DONE ***', "loading"); 
        } 
    } 
} 


// called by sortClusters()
// args:    
//   metric_ - one sort metric in sortType
// effects:
//   sets global sortMetric to be metric_
// returns:
//   comparator function for metric_
function _getSortCmpFunction(metric_) {
    sortMetric = (metric_== null) ? SORT_SIZE : metric_;
    var f;
    switch(sortMetric) {
        case sortType.SIZE:    
            f = function(a,b) { return b.size - a.size; }
            break;
        case sortType.TOTAL_DWELL:
            f = function(a,b) { return b.total - a.total; }
            break;
        case sortType.AVG_DWELL:
            f = function(a,b) { return b.avg_dwell - a.avg_dwell; } 
            break;
        case sortType.VISITS:
            f = function(a,b) { return b.nvisits - a.nvisits; }
            break;
        default: 
            alert('should never execute this line!!! :P');
    }
    return f;
}

function _getClusterMetric(elt_) {
        var s; 
        switch(sortMetric) {
            case sortType.SIZE:         s = elt_.size;
                                        break;
            case sortType.TOTAL_DWELL:  s = elt_.total;
                                        break;
            case sortType.AVG_DWELL:    s = elt_.avg_dwell;
                                        break;
            case sortType.VISITS:       s = elt_.nvisits; 
                                        break;
            default:    alert('should never execute this line #2!!! :P');
        }
        return s;
}

function _getSortType() {
    return sortTypeStr[sortMetric];
}


function sortClusters(metric_) {
    var f = _getSortCmpFunction(metric_);
    clusters.sort(f);
    seen = [];
    uniqueSizes = [];

    for (var j in clusters) {
        var elt = clusters[j]
        var attr = _getClusterMetric(elt);

        if (seen[attr] != 100) {
            seen[attr] = 100;
            uniqueSizes.push(attr);
        }
    }
    uniqueSizes.sort(function(a,b) { return a-b; });
}


function clear_clusters() { 
    for (var i in clusters) { 
        Cluster_erase(clusters[i]); 
    } 
} 

function clear_all() { 
    map.clearOverlays();
} 

function locateCluster(id) {
    for (var i in clusters) { 
        var c = clusters[i];
        if (c.id == id) {
            map.panTo(c.center);
            c.cmarker.openInfoWindowHtml(c.info_contents);
            return; 
        }
    }
    alert('Could not find cluster: "'+id+'"');
}
function draw_clusters() { 
    clear_clusters();
    dbg_clear();
    var count = 0; var avg = totalPoints/clusters.length;
    var icon_interval = Math.ceil(uniqueSizes.length/icons.length);
    var s_type = _getSortType();
    
    for (var i in clusters) { 
        var c = clusters[i];
        var metric = _getClusterMetric(c);
        icon_index = Math.ceil(uniqueSizes.indexOf(metric) / icon_interval);
        icon_index = (icon_index > 7) ? 7 : icon_index;
        dbg(s_type + ": " + metric + ", icon_index=" + icon_index);
        c.cmarker = createClusterMarker(c, icon_index);
        Cluster_draw(c);
    }
    //dbg("Avg. size: " + avg, "show", false);
}

function draw_top10_clusters() {
    clear_clusters();
    //map.clearOverlays();
    dbg_clear();
    var limit = (clusters.length > 10) ? 10 : clusters.length;
    var icon_interval = Math.ceil(uniqueSizes.length/icons.length);
    var s_type = _getSortType();
    dbg('icon interval: ' + icon_interval);
    dbg('sort('+ s_type +')', 'show', false);

    for (var i=0; i < limit; i++) { 
        var c = clusters[i];
        var metric = _getClusterMetric(c);
        icon_index = Math.ceil(uniqueSizes.indexOf(metric) / icon_interval);
        icon_index = (icon_index > 7) ? 7 : icon_index;
        c.cmarker = createClusterMarker(c, icon_index);
        dbg(s_type + ": " + metric + ", icon_index=" + icon_index);
        Cluster_draw(c);
    }

    dbg('-----');
    var s = _getClusterMetric(clusters[limit-1]);
    var count = 0;
    for (var i=limit; i < clusters.length; i++) {
        if (icon_index > 0 && i>0 && i % icon_interval == 0) {
            icon_index--;
        }
        var c = clusters[i];
        if (_getClusterMetric(c) < s) {
            break;
        }
        //c.cmarker = createClusterMarker(c, icon_index);
        //Cluster_draw(c);
        //dbg(''+c.size);
        count++;
    }
    dbg(''+ count +' additional clusters (size='+s+')');
    
}


function initPage() {
	mapDiv = document.getElementById("map");
	initMap();
    initIcons();
    initGSMIcons();
    loadGSMCellData();
};

function displayFSM(file) {
    var fn = file + '.png'
    var debugText = document.getElementById ? 
    document.getElementById('fsm') :
        document.all.dbg_txt;
    debugText.innerHTML = '<img src="'+fn+'">';

};

function dbg(text, id, replace) {
    var id = (id==null) ? "dbg_txt" : id;
    var replace = (replace==null) ? true : replace;
    var debugText = document.getElementById ? 
    document.getElementById(id) :
        document.all.dbg_txt;
    if (replace) {    
        debugText.innerHTML += text + "<br>";
    } 
    else {
        debugText.innerHTML = text;
    }

};

function dbg_clear(id_) {
    var id = (id_ == null) ? "dbg_txt" : id_;
    var debugText = document.getElementById ? 
        document.getElementById(id) :
        document.all.dbg_txt;

    debugText.innerHTML = '';
}

function set_dbg(val) {
    DBG_FLAG = val;
}
