//
// Copyright (C) 2004 Center of Air Pollution Impact and Trend Analysis 
//
// package: datafed.js
//

function ver(condition, message)
{
    if (!condition)
        panic(message);
}

function ver_st(st, message)
{
    if (typeof(st) != "string" || st == "")
        panic(message);
    return st;
}

function ver_num(number)
{
    if (typeof(number) != "number" || isNaN(number))
        panic("NaN found, check your code.");
    return number;
}

function ver_positive(number)
{
    if (typeof(number) != "number" || isNaN(number))
        panic("NaN found, check your code.");
    ver(number > 0, "positive required");
    return number;
}

function ver_ref(obj)
{
    if(typeof(obj) == "undefined")
        panic("object cannot be null, check your code");
}

function panic(message)
{
    alert("assert failure: " + message);
    debug();
}

var debug_is_called = false;

function debug()
{
    if (!debug_is_called)
    {
        debug_is_called = true;
        enter_debugger(); // this usually let's you debug the script.
    }
}

function parse_cgi(href, initial)
{
    var params = typeof(initial) == "undefined" ? {} : initial;
    var param_names = new Array();
        
    var idxq = href.indexOf("?");
    if (idxq >= 0)
    {
        var query = href.substring(idxq+1, href.length);
        var namevals;
        if (query.indexOf("&") == -1) // opening a file://C:\\ results like this
            namevals = query.split("%26");
        else
            namevals = query.split("&");
        for (var inv = 0; inv < namevals.length; ++inv)
        {
            var par = namevals[inv].split("=");
            ver(par.length == 2, "bad cgi url:" + href);
            param_names[param_names.length] = par[0];
            params[par[0]] = par[1];
        }
    }    
    return {names:param_names, params:params};
}

function find_any_opt(doc, id)
{
    var arrays = Array(doc.anchors, doc.embeds, doc.images, doc.links);
    if (typeof(doc.forms) != "undefined")
    {
        for (var ifrm = 0; ifrm < doc.forms.length; ++ifrm)
        {
            arrays[arrays.length] = doc.forms[ifrm].elements;
            if (doc.forms[ifrm].all != "undefined")
                arrays[arrays.length] = doc.forms[ifrm].all;
        }
    }
    for (var ia = 0; ia < arrays.length; ++ia)
    {
        var elem = find_by_id_opt(arrays[ia], id);
        if (elem != null)
            return elem;
    }
    return null;
}

function find_any(doc, id)
{
    var elem = find_any_opt(doc, id);
    ver(typeof(elem) == "object" && elem != null, "could not find elem " + id);
    return elem;
}

function remove_newlines(text)
{
    text = strrepl(text, "\r", "");
    text = strrepl(text, "\n", " ");
    return text
}

function float_to_string(num)
{
    return truncate_decimals(num, 3).toString();
}

function truncate_decimals(num, max_dec_count)
{
    var text = num.toString();
    var dot = text.indexOf(".");
    return (dot >= 0) ? text.substring(0, dot+1+max_dec_count) : num;
}

function to_quoted_string(val)
{
    return "\"" + val.toString() + "\"";
}

function remove_item(arr, item)
{
    for (var ii = 0; ii < arr.length; ++ii)
    {
        if (arr[ii] == item)
        {
            arr[ii] = arr[arr.length-1];
            arr.length--;
            return;
        }
    }
    panic("could not find item " + item.toString());
}

function strrepl(body, pattern, replval)
{
    // netscape does not work here.
    //while (body.indexOf(pattern) >= 0)
    //    body = body.replace(pattern, replval);
    
    var patlen = pattern.length;
    var idx = body.indexOf(pattern);
    while (idx >= 0)
    {
        var start = body.substring(0, idx);
        var end = body.substring(idx+patlen, body.length);
        body = start + replval + end;
        idx = body.indexOf(pattern);
    }
    return body;
}

function isnumeric(chr)
{
    return chr >= '0' && chr <= "9";
}

function parse_int(str)
{
    return ver_num(parseInt(str, 10));
}

function parse_float(str)
{
    return ver_num(parseFloat(str));
}

function new_date(year, month, day)
{
    ver(month >= 0 && month <= 11, "bad month.");
    ver(day >= 1 && month <= 31, "bad day.");
    var ret = new Date();
    ret.setTime(Date.UTC(year, month, day));
    return ret;
}

function parse_date_opt(str)
{
    var parts1 = str.split("-");
    if (parts1.length != 3)
        return NaN;
    var year = parse_int(parts1[0]);
    var month = parse_int(parts1[1]);
    isep = 0;
    while (isep < parts1[2].length && isnumeric(parts1[2].substring(isep, isep+1)))
        ++isep;
    var day = parse_int(parts1[2].substring(0, isep));
    var ret = new Date();
    if (isep == parts1[2].length)
        ret.setTime(Date.UTC(year, month-1, day));
    else
    {
        var parts2 = parts1[2].substring(isep+1, parts1[2].length).split(":");
        var hour = parse_int(parts2[0]);
        var minute = parse_int(parts2[1]);
        var second = parse_int(parts2[2]);
        ret.setTime(Date.UTC(year, month-1, day, hour, minute, second));
    }
    return ret;
}

function parse_date(str)
{
    var ret = parse_date_opt(str);
    ver(!isNaN(ret), "date in unrecognized format:" + str);
    return ret;
}

function is_valid_date(str)
{
    return !isNaN(parse_date_opt(str));
}

function is_date(obj)
{
    return typeof(obj) == "object" && typeof(obj.getTime) == "function";
}

function lzero(num)
{
    return (ver_num(num) <= 9 ? "0" : "") + num.toString(); 
}

var month_map = {Jan:0, Feb:1, Mar:2, Apr:3, May:4, Jun:5, Jul:6, Aug:7, Sep:8, Oct:9, Nov:10, Dec:11};
var month_arr = "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec".split(",");

function to_string(obj)
{
    var ret;
    if (is_date(obj))
    {
        var GMTtime = obj.toGMTString(); //Fri, 1 Jan 1999 00:00:00 UTC
        var wo_weekday = GMTtime.split(",")[1]; // 1 Jan 1999 00:00:00 UTC
        var parts = wo_weekday.split(" ");
        var day = parse_int(parts[1]);
        var month = month_map[parts[2]];
        var year = parse_int(parts[3]);
        var times = parts[4];
        ver(parts[5] == "UTC" || parts[5] == "GMT", "strange UTC string " + GMTtime);
        ret = year.toString() + "-" + lzero(1+month) + "-" + lzero(day);
        if (times != "00:00:00")
            ret += "T" + times;
    }
    else if (typeof(obj) == "boolean")
        ret = obj ? "true" : "false";
    else
        ret = obj.toString();
    return ret;
}

function get_year(date)
{
    var str = to_string(date);
    return parse_int(str.substring(0,4));
}

function get_month(date)
{
    var str = to_string(date);
    return parse_int(str.substring(5,7))-1;
}

function get_date(date)
{
    var str = to_string(date);
    return parse_int(str.substring(8,10));
}

function get_hours(date)
{
    var str = to_string(date);
    var ts = str.substring(11,13);
    return (ts == "") ? 0 : parse_int(ts);
}

function get_minutes(date)
{
    var str = to_string(date);
    var ts = str.substring(14,16);
    return (ts == "") ? 0 : parse_int(ts);
}

function get_seconds(date)
{
    var str = to_string(date);
    var ts = str.substring(17,19);
    return (ts == "") ? 0 : parse_int(ts);
}

function str_eq(s1, s2)
{
    return s1.toString().toUpperCase() == s2.toString().toUpperCase();
}

function date_eq(d1, d2)
{
    return d1.getTime() == d2.getTime();
}

function eps_eq(f1, f2)
{
    return Math.abs(f1-f2) < 0.0001;
}

function triml(str)
{
    return str.replace( /^\s*/, "" ); 
}

function trimr(str)
{
    return str.replace( /\s*$/, "" ); 
}

function trim(str)
{
    return trimr(triml(str));
}

function is_empty(str)
{
    return ((str == null) || (trim(str).length == 0)); 
}

function trim_array(aa)
{
    for (var ii = 0; ii < aa.length; ++ ii)
    {
        aa[ii] = trim(aa[ii]);
    }
}

function shallow_array_copy(arr)
{
    var ret = Array();
    for (var ii = 0, len = arr.length; ii < len; ++ii)
    {
        ret[ii] = arr[ii];
    }
    return ret;
}

function concat_arrays(target, arr1)
{
    for (var iapp = 0; iapp < arr1.length; ++iapp)
        target[target.length] = arr1[iapp];
}

function array_has_elem(arr, elem)
{
    return array_indexof_opt(arr, elem) >= 0;
}

function array_indexof_opt(arr, elem)
{
    for (var iord = 0; iord < arr.length; ++iord)
    {
        if (elem == arr[iord])
            return iord;
    }
    return -1;
}

function array_indexof(arr, elem)
{
    var ret = array_indexof_opt(arr, elem);
    ver(ret >= 0);
    return ret;
}

function is_array_a_set(arr)
{
    var is_set = true;
    for (var ii = 0; ii < arr.length; ++ii)
    {
        for (var tail = ii+1; tail < arr.length; ++tail)
        {
            if (arr[ii] == arr[tail])
                is_set = false;
        }
    }
    return is_set;
}

function find_by_id_opt(arr, id)
{
    if (typeof(arr) != "undefined")
    {
        for (var ii = 0; ii < arr.length; ++ii)
        {
            var elem = arr[ii];
            if (typeof(elem) == "object" && 
                typeof(elem.name) == "string" && 
                elem.name == id)
                return elem;
        }
    }
    return null;
}

function parse_csv(csv_text)
{
    var ret = {fields:new Array(), rows:new Array()};
    var csv_lines = csv_text.replace("\r", "").split("\n");
    trim_array(csv_lines);
    var irow = 0; 
    while (irow < csv_lines.length && csv_lines[irow] == "")
    {
        ++irow;
    }
    if (irow < csv_lines.length)
    {
        ret.rows = Array(csv_lines.length-irow-1);
        ret.fields = csv_lines[irow].split(",");
        var ifinal = 0;
        ++irow;
        while (irow < csv_lines.length)
        {
            var parts = csv_lines[irow].split(",");
            var row = {};
            for (var ifld = 0; ifld < ret.fields.length; ++ifld)
            {
                row[ret.fields[ifld]] = parts[ifld];
            }
            ret.rows[ifinal] = row;
            ++ifinal;
            ++irow;
        }
    }
    return ret;
}

function time_dimension(
    current_time, current_index, 
    sample_periodicity_unit, sample_periodicity_multiplier, time_min, time_max)
{
    ver(typeof(current_time) == "string", "time_dimension:current_time must be date formatted as string, 'unknown' or empty"); 
    ver(typeof(current_index) == "string", "time_dimension:current_index must be string, not " + typeof(current_index)); 
    ver_num(sample_periodicity_multiplier);
    ver(typeof(time_min.getTime) == "function", "time_dimension:time_min must be Date"); 
    ver(typeof(time_max) == "string", "time_dimension:time_max must be 'now' or date formatted as string"); 
    
    this.current_time = current_time;
    this.current_index = current_index;
    this.sample_periodicity_unit = sample_periodicity_unit;
    this.sample_periodicity_multiplier = sample_periodicity_multiplier;
    this.time_min = time_min;
    this.time_max = time_max;
    
    this.ensure_and_get_current_time();
    this.default_time = this.current_time;
}

time_dimension.prototype.goto_first = function()
{
    this.current_time = "unknown";
    this.current_index = 1;
    this.ensure_and_get_current_time();
}

time_dimension.prototype.goto_prev = function()
{
    if (this.current_index > 1)
    {
        --this.current_index;
        this.current_time = "unknown";
        this.ensure_and_get_current_time();
    }
}

time_dimension.prototype.goto_next = function()
{
    if (this.current_index < this.last_index())
    {
        ++this.current_index;
        this.current_time = "unknown";
        this.ensure_and_get_current_time();
    }
}

time_dimension.prototype.goto_last = function()
{
    this.current_index = "last";
    this.current_time = "unknown";
    this.ensure_and_get_current_time();
}

time_dimension.prototype.change_time = function(new_datetime)
{
    this.current_index = "unknown";
    this.current_time = to_string(new_datetime);
    this.ensure_and_get_current_time();
}

time_dimension.prototype.change_index = function(new_index)
{
    this.current_index = new_index;
    this.current_time = "unknown";
    this.ensure_and_get_current_time();
}

time_dimension.prototype.time_to_index = function(index_time)
{
    var ret;

    if (this.sample_periodicity_unit == "year")
    {
        ret = (get_year(index_time) - get_year(this.time_min)) / this.sample_periodicity_multiplier + 1;
    }
    else if (this.sample_periodicity_unit == "month")
    {
        var years = get_year(index_time) - get_year(this.time_min);
        var months = get_month(index_time) - get_month(this.time_min);
        ret = (years * 12 + months) / this.sample_periodicity_multiplier + 1;
    }
    else 
    {
        var span = index_time.getTime() - this.time_min.getTime();
        if (this.sample_periodicity_unit == "week")
            span /= (7*24*3600*1000);
        else if (this.sample_periodicity_unit == "day")
            span /= (24*3600*1000);
        else if (this.sample_periodicity_unit == "hour")
            span /= (3600*1000);
        else if (this.sample_periodicity_unit == "minute")
            span /= (60*1000);
        else if (this.sample_periodicity_unit == "second")
            span /= 1000;
        else
            panic("unknown sample_periodicity_unit " + this.sample_periodicity_unit);
        ret = Math.floor(span / this.sample_periodicity_multiplier) + 1;
    }
    return ret;
}

time_dimension.prototype.index_to_start_time = function(index)
{
    index = (index-1) * this.sample_periodicity_multiplier;
    var ret;
    if (this.sample_periodicity_unit == "year")
        ret = new_date(get_year(this.time_min) + index, 0, 1);
    else if (this.sample_periodicity_unit == "month")
    {
        var month = get_month(this.time_min) + index;
        ret = new_date(get_year(this.time_min) + month / 12, month % 12, 1);
    }
    else if (this.sample_periodicity_unit == "week")
    {
        var mstime = this.time_min.getTime() + index*7*24*3600*1000;
        ret = new Date();
        ret.setTime(mstime);
    }
    else if (this.sample_periodicity_unit == "day")
    {
        var mstime = this.time_min.getTime() + index*24*3600*1000;
        ret = new Date();
        ret.setTime(mstime);
    }
    else if (this.sample_periodicity_unit == "hour")
    {
        var mstime = this.time_min.getTime() + index*3600*1000;
        ret = new Date();
        ret.setTime(mstime);
    }
    else if (this.sample_periodicity_unit == "minute")
    {
        var mstime = this.time_min.getTime() + index*60*1000;
        ret = new Date();
        ret.setTime(mstime);
    }
    else if (this.sample_periodicity_unit == "second")
    {
        var mstime = this.time_min.getTime() + index*1000;
        ret = new Date();
        ret.setTime(mstime);
    }
    else
    {
        ret = DateTime.Now;
        panic("wrong periodicity_unit " + this.sample_periodicity_unit);
    }
    return ret;
}

time_dimension.prototype.get_time_from_text = function(timetext)
{
    if (timetext.indexOf("now") == -1)
        return parse_date(timetext);
    else
    {
        timetext = trim(strrepl(timetext, "now", ""));
        var currtime = new Date();
        var mscurr = currtime.getTime();
        mscurr -= 60.0*1000.0*currtime.getTimezoneOffset();
        currtime.setTime(mscurr);
        var idx = this.time_to_index(currtime);
        if (timetext != "")
            idx += parse_int(timetext);
        return this.index_to_start_time(idx);
    }
}

time_dimension.prototype.last_index = function()
{
    return this.time_to_index(this.get_time_from_text(this.time_max));
}

time_dimension.prototype.ensure_and_get_current_time = function()
{
    var last_index = this.last_index();
    
    var curr_time = "";
    if (this.current_time != "")
    {
        if (str_eq(this.current_time, "unknown"))
            curr_time = "";
        else
            curr_time = to_string(this.get_time_from_text(this.current_time));
    }
    var curr_idx = "";
    if (this.current_index != "")
    {
        curr_idx = this.current_index;
        if (str_eq(curr_idx, "unknown"))
            curr_idx = "";
        else if (str_eq(curr_idx, "last"))
            curr_idx = last_index.toString();
    }

    var retidx;
    if (curr_time != "")
    {
        var given_time = parse_date(curr_time);
        if (curr_idx != "")
        {
            retidx = parse_int(curr_idx);
            if (retidx != this.time_to_index(given_time))
                panic("time '" + curr_time + "' and index '" + curr_idx + "' are inconsistent. Try to replace other with 'UNKNOWN'.");
        }
        else
        {
            curr_idx = this.time_to_index(given_time).toString();
            retidx = parse_int(curr_idx);
        }
    }
    else
    {
        retidx = curr_idx != "" ? parse_int(curr_idx) : 1;
    }
    ver_num(retidx);
    // don't make this restriction
    //retidx = Math.max(1, Math.min(retidx, last_index));
    this.current_index = retidx.toString();
    var ret = this.index_to_start_time(retidx);
    this.current_time = to_string(ret);
    return ret;
}

function encode_url_parameter(param)
{
    var ret = "";
    for (var ii = 0; ii < param.length; ++ii)
    {
        ret += find_enc(param.substring(ii, ii+1));
    }
    return ret;
}

function decode_url_parameter(param)
{
    var ret = "";
    for (var ii = 0; ii < param.length; ++ii)
    {
        var ch = param.substring(ii, ii+1);
        if (ch == "+")
            ret += " ";
        else if (ch == "%")
        {
            ret += find_dec(param.substring(ii, ii+3));
            ii += 2;
        }
        else
            ret += ch;
    }
    return ret;
}

function find_enc(chr)
{
    for (var ichr = 0; ichr < url_xlate.length; ++ichr)
    {
        if (url_xlate[ichr].chr == chr)
            return url_xlate[ichr].enc;
    }
    return chr;
}

function find_dec(enc)
{
    for (var ichr = 0; ichr < url_xlate.length; ++ichr)
    {
        if (url_xlate[ichr].enc == enc)
            return url_xlate[ichr].chr;
    }
    return enc;
}

var url_xlate = Array(
{idx:9, chr:'\t', enc:'%09'},
{idx:10, chr:'\n', enc:'%0a'},
{idx:13, chr:'\r', enc:'%0d'},
{idx:32, chr:' ', enc:'+'},
{idx:32, chr:' ', enc:'%20'},
{idx:34, chr:'"', enc:'%22'},
{idx:35, chr:'#', enc:'%23'},
{idx:36, chr:'$', enc:'%24'},
{idx:37, chr:'%', enc:'%25'},
{idx:38, chr:'&', enc:'%26'},
{idx:43, chr:'+', enc:'%2b'},
{idx:44, chr:',', enc:'%2c'},
{idx:47, chr:'/', enc:'%2f'},
{idx:58, chr:':', enc:'%3a'},
{idx:59, chr:'', enc:';%3b'},
{idx:60, chr:'<', enc:'%3c'},
{idx:61, chr:'=', enc:'%3d'},
{idx:62, chr:'>', enc:'%3e'},
{idx:63, chr:'?', enc:'%3f'},
{idx:64, chr:'@', enc:'%40'},
{idx:91, chr:'[', enc:'%5b'},
{idx:92, chr:'\\', enc:'%5c'},
{idx:93, chr:']', enc:'%5d'},
{idx:94, chr:'^', enc:'%5e'},
{idx:96, chr:'`', enc:'%60'},
{idx:123, chr:'{', enc:'%7b'},
{idx:124, chr:'|', enc:'%7c'},
{idx:125, chr:'}', enc:'%7d'},
{idx:126, chr:'~', enc:'%7e'});
