/* -*- coding: utf-8 -*-
 * CSV to Google Maps Renderer (Google Maps APIv2)
 * Author: Nao Iizuka <s03048ni@sfc.keio.ac.jp>
 *
 * CSV Parser by http://tiki.is.os-omicron.org/tiki.cgi?c=v&p=JavaScript%2Fcsv
 */
/*
 * TODO:
 *  o 2,4-7,9
 *  o 1,3-
 *  o -4,8
 */
//Debug mode: true/false
var _debug = false;
var _mapObj;
var _lastOpts;
var _lastCSV;
function unload() {
    _mapObj = null;
    _lastOpts = null;
    _lastCSV = null;
    GUnload();
}
function cloneOpts(opts) {
    var newOpts = {
	'latCol': opts.latCol,
	'lngCol': opts.lngCol,
	'icon'  : opts.icon,
	'iconFunc': opts.iconFunc,
	'format': opts.format,
	'mapObj': opts.mapObj,
	'lineFilter': opts.lineFilter
    };
    return newOpts;
}
function resetMarkers() {
    if (!_mapObj || !_lastOpts || !_lastCSV) {
	alert('Some of GMap2 object, opts argument, and CSV content are not ready.');
	return;
    }
    _mapObj.clearOverlays();
    renderCSVContent(_lastCSV, _lastOpts, true);
}
/**
 * Filter markers by a query string.
 */
function searchMarkers(query, col) {
    //Is required variables ready?
    if (!_mapObj || !_lastOpts || !_lastCSV) {
	alert('Some of GMap2 object, opts argument, and CSV content are not ready.');
	return;
    }
    //col is not specified
    if (col == null) {
	alert('searchMarkers(): Please specify at least one column number to be searched.');
	return;
    }
    _mapObj.clearOverlays();
    //A single number is specified as col
    if (typeof col == 'number') {
	col = col.toString();
    }
    var cols = col.split(',');
    var nCols = cols.length;
    var newOpts = cloneOpts(_lastOpts);
    var bindLineFilter = _lastOpts.lineFilter;
    newOpts.lineFilter = function(params) {
	var nParams = params.length;
	for (var i = 0; i < nCols; i++) {
	    if (cols[i] == 'all') {
		for (var i = 0; i < nParams; i++) {
		    if (params[i].indexOf(query) != -1) {
			return true;
		    }
		}
	    } else if (cols[i] == 'all_except_latlng') {
		for (var i = 0; i < nParams; i++) {
		    if (i == newOpts.latCol
			|| i == newOpts.lngCol) {
			continue;
		    }
		    if (params[i].indexOf(query) != -1) {
			return true;
		    }
		}
	    }
	    var colnum = parseInt(cols[i]);
	    //Find query string within the col
	    if (!isNaN(colnum)
		&& nParams > colnum
		&& colnum >= 0
		&& params[colnum].indexOf(query) != -1) {
		return true;
	    }
	}
	//If another lineFilter does already exist, call that.
	if (bindLineFilter) {
	    return bindLineFilter.apply(null, new Array(params));
	}
	return false;
    };
    //Render CSV with opts, not saving states.
    renderCSVContent(_lastCSV, newOpts, true);
}
function renderCSVMarkers(url, opts) {
    requestByAjax(url, function(req){renderCSVContent(req.responseText,opts,false)},
		  null, null, null);
}
/**
 * Retrieve CSV contents by Ajax, then render it to the map.
 */
function renderCSV(url, opts) {
    requestByAjax(url, function(req){renderCSVContent(req.responseText,opts,false)},
		  null, null, null);
}
/**
 * Render CSV contents to the map
 * @params csv   CSV contents
 * @params opts  Options
 * @params notSaveState  If true, remember current opts and CSV content.  If false, does not.
 */
function renderCSVContent(csv, opts, notSaveState) {
    //Check parameters
    var errMsg;
    if (typeof opts.mapObj == 'undefined'
	&& typeof map == 'undefined') {
	errMsg = 'Please specify GMap2 object opts.mapObj or variable named "map"';
    } else if (!opts) {
	errMsg = 'Please specify opts argument';
    } else if (typeof opts.latCol == 'undefined') {
	errMsg = 'Please specify opts.latCol';
    } else if (typeof opts.lngCol == 'undefined') {
	errMsg = 'Please specify opts.lngCol';
    }
    if (errMsg) {
	alert(errMsg);
	return;
    }
    if (typeof opts.mapObj != 'undefined') {
	_mapObj = opts.mapObj;
    } else {
	_mapObj = map;
    }
    if (!notSaveState) {
	_lastOpts = opts;
	_lastCSV = csv;
    }
    var time;
    if (_debug) {
	alert('start parse csv');
	time = new Date().getTime();
    }
    var matrix = csvParseAll(csv);
    if (_debug) {
	alert('parse end: took ' + (new Date().getTime() - time) + ' ms');
	time = new Date().getTime();
    }
    var nRows = matrix.length;
    for (var i = 0; i < nRows; i++) {
	parseCSVLine(matrix[i], opts);
    }
    if (_debug) {
	alert('matrix end: took ' + (new Date().getTime() - time) + ' ms');
	time = new Date().getTime();
    }
}
//Leading pattern to be replaced
var pat_leading = "\\[%\\s*";
//Trailing pattern to be replaced
var pat_trailing = "\\s*%\\]";
//RegExp of \n
var regex_return = new RegExp("\n", 'g');
//RegExp of comment line
var regex_comment = new RegExp("^\s*#");
/**
 * Parse one CSV line.
 */
function parseCSVLine(params, opts) {
    if (params[0].match(regex_comment)) {
	return;
    } else if (opts.lineFilter
	       && !opts.lineFilter.apply(null, new Array(params))) {
	return;
    }
    var nParams = params.length;
    var lat;
    var lng;
    var html = opts.format;
    for (var i = 0; i < nParams; i++) {
	if (i == opts.latCol) {
	    lat = params[i];
	} else if (i == opts.lngCol) {
	    lng = params[i];
	}
	if (html) {
	    html = html.replace(new RegExp([pat_leading,i,pat_trailing]
					   .join(''), 'g'), params[i]);
	}
    }
    if (html) {
	html = html.replace(regex_return, '<br/>');
    }
    _mapObj.addOverlay(createMarker(new GLatLng(lat, lng),
				    opts.iconFunc
				    ? opts.iconFunc.apply(null, new Array(params))
				    : opts.icon,
				    html));
}
/**
 * Create a marker and return it.
 */
function createMarker(point, icon, html) {
    var marker;
    if (typeof GxMarker != 'undefined') {
	if (html) {
	    marker = new GxMarker(point, icon, html);
	} else {
	    marker = new GxMarker(point, icon);
	}
    } else {
	marker = new GMarker(point, icon);
    }
    //Open blow off when the marker is clicked
    if (html) {
	GEvent.addListener(marker, 'click', function() {
			       marker.openInfoWindowHtml(html);
			   });
    }
    return marker;
}
/**
 * Request a URL by Ajax.
 */
function requestByAjax(url, completeFunc, loadingFunc, errorFunc, body) {
    var req = false;
    if (window.XMLHttpRequest) {
	try {
	    req = new XMLHttpRequest();
	} catch (e) {
	    req = false;
	}
    } else if (window.ActiveXObject) {
	try {
	    req = new ActiveXObject("Msxml2.XMLHTTP");
	} catch (e) {
	    try {
		req = new ActiveXObject("Microsoft.XMLHTTP");
	    } catch (e) {
		req = false;
	    }
	}
    }
    if (req) {
	req.onreadystatechange = function() {
	    if (req.readyState == 4) {
		if (req.status == 200 || req.status == 304) {//succeeded
		    if (completeFunc) {
			completeFunc.apply(this, new Array(req));
		    }
		} else { //error
		    alert('error while Ajax request to: ' + url);
		    if (errorFunc) {
			errorFunc.apply(this, new Array(req));
		    }
		}
	    } else { //loading
		if (loadingFunc) {
		    loadingFunc.apply(this, new Array(req));
		}
	    }
	};
	if (!body) { //GET request
	    req.open('GET', url);
	    req.setRequestHeader('If-Modified-Since', 'Sat, 1 Jan 2000 00:00:00 GMT');
	    req.send(null);
	} else { //POST request
	    req.open('POST', url);
	    req.setRequestHeader('Content-Type',
				 'application/x-www-form-urlencoded');
	    req.setRequestHeader('If-Modified-Since', 'Sat, 1 Jan 2000 00:00:00 GMT');
	    req.send(body);
	}
    }
}

/*
 * Following codes are derived from:
 *  http://tiki.is.os-omicron.org/tiki.cgi?c=v&p=JavaScript%2Fcsv
 */
function debug() {}
function info() {}

var CSV_EOL = "\n";

var CSV_TOKEN_ITEM = 0;
var CSV_TOKEN_DQUOTE = 1;
var CSV_TOKEN_EOL = 2;
var CSV_TOKEN_EOD = 3; // End of Data, like EOF
var CSV_TOKEN_COMMA = 4;
/*
	type must be one of 
		CSV_TOKEN_ITEM
		CSV_TOKEN_DQUOTE
		CSV_TOKEN_EOL
		CSV_TOKEN_EOD
		CSV_TOKEN_COMMA
	@return {done: <bool>, rest: <string>, type: <int>, val: <string>
*/
function csvParseToken(line)
{
	var comma = line.indexOf(",");
	var crPos = line.indexOf(CSV_EOL);
	var dquot = line.indexOf('"'); 

	function endWithPos(pos)
	{
		return {type: CSV_TOKEN_ITEM, rest: line.substring(pos), val: line.substring(0, pos)};
	}
	function endWithEOL()
	{
		return endWithPos(crPos);
	}
	function endWithDquot()
	{
		return endWithPos(dquot);
	}
	function endWithComma()
	{
		return endWithPos(comma);
	}

	if (crPos == 0)
		return {type: CSV_TOKEN_EOL, rest: line.substring(CSV_EOL.length), val: CSV_EOL};

	if (dquot == 0)
		return {type: CSV_TOKEN_DQUOTE, rest: line.substring(1), val: '"'};

	if (comma == 0)
		return {type: CSV_TOKEN_COMMA, rest: line.substring(1), val: ','};
	


	if (comma == -1 && crPos == -1 && dquot == -1)
	{
		if (line == "" || line == null)
			return {type: CSV_TOKEN_EOD, rest: "", val: ""};
		return {type: CSV_TOKEN_ITEM, rest: "", val: line};
	}

	if (comma == -1) {
		if (crPos == -1){
			return endWithDquot();
		} else {
			if (dquot == -1)
				return endWithEOL();
			if (crPos < dquot)	
				return endWithEOL();
			return endWithDquot();
		}
	}

	if (crPos == -1)
	{
		if (dquot == -1){
			return endWithComma();
		} else {
			if (dquot < comma) 
				return endWithDquot();
			return endWithComma();
		}
	}

	if (dquot == -1)
	{
		if (crPos < comma) {
			return endWithEOL();
		} else {
			return endWithComma();
		}
	}

	if (dquot < crPos) {
		if (dquot < comma) {
			return endWithDquot();
		} else {
			return endWithComma();
		}
	}

	/* dquot > crPos */
	if (crPos < comma) {
		return endWithEOL();
	} else {
		return endWithComma();
	}
	throw "csvParseToken: never reached here";
}
function csvParseAll(lines)
{
	var res = new Array();
	var oneRes = new Array();
	
	var token;
	var current = "";
	var rest = lines;
	var PARSE_NORMAL = 0;
	var PARSE_DQUOTE = 1;
	var PARSE_DQUOTE_CLOSE_CANDIDATE = 2;

	function parseDquoteCloseCandidate()
	{
		switch(token.type)
		{
			case CSV_TOKEN_ITEM:
				debug("item3");
				info("invalid format");
				current += token.val;
				return PARSE_NORMAL;
				break;
			case CSV_TOKEN_EOL:
				debug("eol3");
				if (current != "")
					oneRes.push(current);
				res.push(oneRes);
				current = "";
				oneRes = new Array();
				return PARSE_NORMAL;
				break;
			case CSV_TOKEN_DQUOTE:
				current += '"';
				return PARSE_DQUOTE;
				break;
			case CSV_TOKEN_COMMA:
				debug("comma3");
				oneRes.push(current);
				current = "";
				return PARSE_NORMAL;
				break;
			case CSV_TOKEN_EOD:
				debug("eod3");
				if (current != "")
					oneRes.push(current);
				if( oneRes.length != 0 )
					res.push(oneRes);
				return PARSE_NORMAL;
				break;
			default:
				throw "cvsParseAll: never reached here4";
				break;
		}
		return null;
	}

	function parseDquote()
	{
		switch(token.type)
		{
			case CSV_TOKEN_ITEM:
				debug("item2");
				current += token.val;
				return PARSE_DQUOTE;
				break;
			case CSV_TOKEN_EOL:
				debug("eol2");
				current += token.val;
				return PARSE_DQUOTE;
				break;
			case CSV_TOKEN_DQUOTE:
				return PARSE_DQUOTE_CLOSE_CANDIDATE;
				break;
			case CSV_TOKEN_COMMA:
				debug("comma2");
				current += ",";
				return PARSE_DQUOTE;
				break;
			case CSV_TOKEN_EOD:
				debug("eod2");
				info("invalid format");
				if (current != "")
					oneRes.push(current);
				if( oneRes.length != 0 )
					res.push(oneRes);
				return PARSE_NORMAL;
				break;
			default:
				throw "cvsParseAll: never reached here3";
				break;
		}
		return null;
	}

	function parseNormal()
	{
		switch(token.type)
		{
			case CSV_TOKEN_ITEM:
				current += token.val;
				debug("item");
				return PARSE_NORMAL;
				break;
			case CSV_TOKEN_EOL:
				if (current != "")
					oneRes.push(current);
				res.push(oneRes);
				current = "";
				oneRes = new Array();
				return PARSE_NORMAL;
				break;
			case CSV_TOKEN_DQUOTE:
				if (current == "")
					return PARSE_DQUOTE;
				current += '"';
				return PARSE_NORMAL;
				break;
			case CSV_TOKEN_EOD:
				debug("eod");
				if (current != "")
					oneRes.push(current);
				if( oneRes.length != 0 )
					res.push(oneRes);
				return PARSE_NORMAL;
				break;
			case CSV_TOKEN_COMMA:
				debug("comma");
				oneRes.push(current);
				current = "";
				return PARSE_NORMAL;
				break;
			default:
				throw "cvsParseAll: never reached here";
				break;
		}
		return null;
	}

	var state = PARSE_NORMAL;
	do {
		token = csvParseToken(rest);
		debug(token.rest);
		switch(state) {
			case PARSE_NORMAL:
				state = parseNormal();
				break;
			case PARSE_DQUOTE:
				state = parseDquote();
				break;
			case PARSE_DQUOTE_CLOSE_CANDIDATE:
				state = parseDquoteCloseCandidate();
				break;
			default:
				alert(state);
				throw "cvsParseAll: never reached here2";
				break;
		}
		rest = token.rest;
	} while(token.type != CSV_TOKEN_EOD);
	return res;
}
