/*global window, alert, document, moveDiv, rwDiv, showDiv, resizeDiv, hideDiv,
         chColorDiv, createAjaxPack, setTimeout */

var get_holidays_path = "/cgi/wl/fxmarkethours/get_market_holidays.pl";
var closed_market_color = '#9F9F9F'; //greyish
var timeZoneOffset = (new Date()).getTimezoneOffset(); //default timeZone
var cellWidth, cellHeight, tableWidth, statTableWidth;
var showClocks = 0;

var fxmh_style = "font-size:10pt;";

var market;

//****************************************************//
//get URL parameters (src: Netlobo.com)
function gup(name) {
    name = name.replace(/\[/, "\\[").replace(/\]/, "\\]");
    var regexS = "[\\?&]" + name + "=([^&#]*)";
    var regex = new RegExp(regexS);
    var results = regex.exec(window.location.href);
    if (!results) {
        return "";
    } else {
        return results[1];
    }
}
//****************************************************//
var compressedMode = gup('compressedMode');
if (compressedMode == "true" || compressedMode == "1") {
    //alert(compressedMode);
    compressedMode = 1;
    cellWidth = 8;
    cellHeight = 20;
    tableWidth = 24 * cellWidth;
    statTableWidth = 195;
    fxmh_style = "font-size:8pt;";
} else {
    compressedMode = 0;
    cellWidth = 20;
    cellHeight = 20;
    tableWidth = 24 * cellWidth;
    //statTableWidth = 380;
}
//****************************************************//
function TimeZone(offset, title, text) {
    this.offset = offset;
    this.title = title;
    this.text = text;
}
//****************************************************//
function dateToStr(mydate) {
    // output example: "02.03.2006", used as input to market[x].isClosed
    var d = mydate.getDate();
    if (d < 10) {
        d = '0' + d;
    } //make a 2-digit day
    var m = mydate.getMonth() + 1;
    if (m < 10) {
        m = '0' + m;
    } //make a 2-digit month
    var str = d + "." + m + "." + mydate.getFullYear();
    return str;
}
//****************************************************//
function isMarketClosed(date) {
    //if market is closed, returns the holiday description, else returns nothing
    //assuming all markets are closed on Saturdays and Sundays (FIX?)
    if (date.getDay() === 0) {
        return 'Weekend(Sunday)';
    }
    if (date.getDay() == 6) {
        return 'Weekend(Saturday)';
    }

    var holidays = this.holidays;
    var dateStr = dateToStr(date);
    var ix = holidays.indexOf(dateStr);
    if (ix == -1) {
        return ''; //there is no local holiday for the specified market (it is not closed)
    } else {
        ix = holidays.indexOf("~", ix); //holiday's description begins with ~
        if (ix == -1) {
            //alert('Holiday description not found!');
            return 'unknown';
        }
        var end_ix = holidays.indexOf("~", ix + 1); //the description ends with ~
        if (end_ix == -1) {
            //alert('Wrong format for holiday description!');
            return 'unknown';
        }
        //alert ( this.name + " is closed on " + date + " for: " + holidays.substring(ix+1,end_ix) );
        return holidays.substring(ix + 1, end_ix);
    }
}
//****************************************************//
function modifyMarketLayer(divID, left, top, width, height, name) {
    //IE wont resize a layer smaller than its text contents area, if this
    //happens, we should empty the layer contents before resizing and later
    //rewrite its contents back
    moveDiv(divID, left, top);

    if (width <= 4 * cellWidth) {
        rwDiv(divID, "\u00AD");
    } //div conetents should not be empty
    else {
        var content = '<div style="' + fxmh_style + '">' + name + '</div>';
        rwDiv(divID, content);
    }

    resizeDiv(divID, width, height);
}
//****************************************************//
function adjustMarketLayers() {
    var num_rows = market.length;
    for (var row = 0; row < num_rows; row++) {
        var openPos = tableWidth * market[row].openTime / (24 * 60),
        closePos = tableWidth * market[row].closeTime / (24 * 60),
        top = (row + 1 + 0.1) * cellHeight,
        //to add some space between each layer (row)
        height = 0.9 * cellHeight,
        //same as above
        marketLayer_a = market[row].layerA_id,
        marketLayer_b = market[row].layerB_id,
        marketName = (compressedMode) ? market[row].shortName : market[row].name;

        //for drawing purposes, closetime of 00:00 is equal to 24:00 so adjust closePos
        if (closePos === 0) {
            closePos = tableWidth;
        }

        //open times are always represented in layer_a
        if (closePos > openPos) {
            showDiv(marketLayer_a);
            hideDiv(marketLayer_b);
            modifyMarketLayer(marketLayer_a, openPos, top, closePos - openPos, height, marketName);
        } else {
            showDiv(marketLayer_a);
            modifyMarketLayer(marketLayer_a, openPos, top, tableWidth - openPos, height, marketName);

            showDiv(marketLayer_b);
            modifyMarketLayer(marketLayer_b, 0, top, closePos, height, marketName);
        }

        //change color of closed layers to grey
        if (market[row].layerA_closed == 1) {
            chColorDiv(marketLayer_a, closed_market_color);
        } else {
            chColorDiv(marketLayer_a, market[row].color);
        }

        if (market[row].layerB_closed == 1) {
            chColorDiv(marketLayer_b, closed_market_color);
        } else {
            chColorDiv(marketLayer_b, market[row].color);
        }
    }
}
//****************************************************//
function processHolidays(current_date) {
    // (current_date is the local time for the seleted time-zone)
    // shadow market layers a or b which are closed because of a holiday or weekend
    // (based on the market's local open date)
    var time_tmp;
    var num_rows = market.length;
    for (var row = 0; row < num_rows; row++) {
        var opday = new Date();
        opday.setTime(opday.getTime());
        opday.setHours(0);
        opday.setMinutes(0);
        opday.setSeconds(0);

        if (current_date > opday && current_date.getDay() != opday.getDay()) {
            //alert("open date is the next day. readjust by +24 hours");
            opday.setTime(opday.getTime() + 24 * 60 * 60 * 1000);
        }

        //compute local openning date of market (in minutes from 12am)
        time_tmp = market[row].openTime - market[row].timeZoneOffset - (-timeZoneOffset); // extra '-' to convert from string to integer
        opday.setTime(opday.getTime() + time_tmp * 60 * 1000);
        //used setTime instead of setMinutes because Safari's setMinutes only work with 0 <= x <= 59
        //alert(market[row].name + ' opens on ' + opday);
        if (market[row].isClosed(opday)) {
            //alert('A:'+market[row].name + ' CLOSED on ' + opday+ 'cur='+current_date);
            market[row].layerA_closed = 1;
        }
        else {
            //alert('A:'+market[row].name + ' NOT CLOSED on ' + opday+ 'cur='+current_date);
            market[row].layerA_closed = 0;
        }

        //find the next day on which the market opens
        //for example on Friday of a long weekend, market will open on Tuesday
        var next_opday = new Date(opday);
        do {
            next_opday.setTime(next_opday.getTime() + 24 * 3600 * 1000); //in miliseconds
        } while (market[row].isClosed(next_opday));

        //convert next open date back to the selected time-zone
        time_tmp = market[row].timeZoneOffset - timeZoneOffset;
        next_opday.setTime(next_opday.getTime() + time_tmp * 60 * 1000);
        market[row].nextOpenDay = next_opday;

        //check market closure on the previous day
        opday.setTime(opday.getTime() - 24 * 3600 * 1000);
        if (market[row].isClosed(opday)) {
            //alert('B:'+market[row].name + ' CLOSED on ' + opday+ 'cur='+current_date);
            market[row].layerB_closed = 1;
        }
        else {
            //alert('B:'+market[row].name + ' NOT CLOSED on ' + opday+ 'cur='+current_date);
            market[row].layerB_closed = 0;
        }

    }
}
//****************************************************//
function datetimeToStr(mydate) {
    // output example: "Tue Feb 21, 2006 10:08 PM"
    var month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
    var weekday = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
    var w = mydate.getDay();
    var d = mydate.getDate();
    var m = mydate.getMonth();
    var y = mydate.getFullYear();
    var dateStr = weekday[w] + ' ' + month[m] + ' ' + d + ', ' + y;

    // create time string (e.g. '11:02 AM'), alternative: current_date.toLocaleTimeString();
    var hours = mydate.getHours();
    var PM_AM;
    if (hours >= 12) {
        hours -= 12;
        PM_AM = 'PM';
    }
    else {
        PM_AM = 'AM';
    }
    var minutes = mydate.getMinutes();
    if (minutes < 10) {
        minutes = '0' + minutes;
    }
    var timeStr = hours + ':' + minutes + ' ' + PM_AM;

    return dateStr + ' ' + timeStr;
}
//****************************************************//
function genStatus(current_date) {
    //generate html code for the status section (current time + markets' status)
    //current_date is the local time for the seleted time-zone
    var htmlCode = '<div style="' + fxmh_style + '">';
    htmlCode += 'FX markets on ';
    if (compressedMode) {
        htmlCode += '<br>';
    }
    htmlCode += datetimeToStr(current_date) + '<br>';
    //FIX: below table attributes should be moved to css file when they are supported
    htmlCode += '<table class="statTable" width="' + statTableWidth + '">';

    var current = current_date.getHours() * 60 + current_date.getMinutes();
    var num_rows = market.length;

    for (var row = 0; row < num_rows; row++) {
        var opn = market[row].openTime;
        var cls = market[row].closeTime;
        var status = 'closed';

        htmlCode += '<tr>';
        htmlCode += '<td class="statTable" style="' + fxmh_style + '">' + market[row].name + '</td>';
        //'<td class="fxmh">'+ market[row].name +'</td>';
        var time_remained = -1;
        if ((current < cls) && ((current >= opn && !market[row].layerA_closed) || (opn > cls && !market[row].layerB_closed))) {
            status = 'open';
            time_remained = cls - current;
        }
        else if (current >= opn && opn > cls && !market[row].layerA_closed) {
            status = 'open';
            time_remained = 24 * 60 + cls - current;
        }
        else {
            status = 'closed';
            time_remained = (24 * 60 + opn - current) % (24 * 60);
        }

        var minutes = time_remained % 60;
        var hours = (time_remained - minutes) / 60;

        if (!compressedMode) {
            hours = (hours == '1') ? '1 hour ' : hours + ' hours ';
            minutes = (minutes == '1') ? '1 minute ' : minutes + ' minutes ';
        } else {
            hours = '<br>' + hours + 'h ';
            minutes = minutes + 'm ';
        }

        if (status == 'open') {
            htmlCode += '<td style="' + fxmh_style + '" class="statTable statOpen">open</td>';
            htmlCode += '<td style="' + fxmh_style + '" class="statTable statTxt">closes in ' + hours + minutes + '</td>';
        }
        else if (status == 'closed') {
            htmlCode += '<td style="' + fxmh_style + '" class="statTable statClose">closed</td>';
            if (current < opn && !market[row].layerA_closed) {
                htmlCode += '<td style="' + fxmh_style + '" class="statTable statTxt">opens in ' + hours + minutes + '</td>';
            } else {
                htmlCode += '<td style="' + fxmh_style + '" class="statTable statTxt">opens on ' + datetimeToStr(market[row].nextOpenDay) + '</td>';
            }
        }
        htmlCode += '</tr>';
    }
    htmlCode += '</table></div>';
    return htmlCode;
}
//****************************************************//
function refreshStat() {
    var current_date = new Date();
    //convert time for the selected timeZone
    var time_tmp = current_date.getTimezoneOffset() - timeZoneOffset;
    current_date.setTime(current_date.getTime() + time_tmp * 60 * 1000);

    //tags markets closed because of a holiday or weekend
    processHolidays(current_date);

    if (showClocks == 1) {
        //createFXMarketClocks();
    } else {
        adjustMarketLayers(current_date);
        //compute and adjust new location of the timeline
        var tm = current_date.getHours() * 60 + current_date.getMinutes(); //time in minutes
        moveDiv('line', tableWidth * tm / (24 * 60) - 3, 0);

        //update markets status section
        rwDiv('statusDiv', genStatus(current_date));
    }
}
//****************************************************//
function FXMarket(id, marketName, color, openTime, closeTime, timeZoneOffset, observeDST, currencyName, marketShortName) {
    this.id = id; //should be unique for each market
    this.name = marketName;
    this.shortName = marketShortName;
    this.color = color;
    this.GMTopenTime = openTime; //in minutes (from 12am)
    this.GMTcloseTime = closeTime;
    this.openTime = 0; //open time in the selected time zone
    this.closeTime = 0;
    this.timeZoneOffset = timeZoneOffset;
    this.observeDST = observeDST;
    this.layerA_id = 'm' + id + 'a'; //string id for market's graphical layers
    this.layerB_id = 'm' + id + 'b';
    this.layerA_closed = 0; // close or open status of market at layerA
    this.layerB_closed = 0; // close or open status of market at layerB
    this.isClosed = isMarketClosed; //function
    //local holidays (except weekends) during which market is closed.
    //data is acquired through an AJAX call to get_market_holidays.pl  
    this.holidays = ''; //e.g.'01.01.2006 ~New Year~ 25.12.2006 ~Christmas~ ';
    var mk = this;
    this.initHolidays = function initMarketHolidays() {
        //process AJAX response and init market holidays
        var myajax = mk.ajaxpack.ajaxobj;
        var myfiletype = mk.ajaxpack.filetype;
        if (myajax.readyState == 4) { //request of file completed
            if (myajax.status == 200 || window.location.href.indexOf("http") == -1) {
                //request was successful or running script locally
                if (myfiletype == "xml") {
                    var xmlDoc = myajax.responseXML;
                    var holidays = xmlDoc.getElementsByTagName(currencyName).item(0).firstChild;
                    if (holidays) { mk.holidays += holidays.data; }
                    //alert(mk.holidays); 
                    refreshStat();
                }
                else { //should never happen
                    alert(mk.name + ": Holiday data has the wrong format."); //myajax.responseText
                }
            }
            else {
                //AJAX request failed
                //alert("Could not acquire list of bank holidays for "+mk.name+ " market.");
            }
        }
    }; //end function initMarketHolidays
    this.ajaxpack = createAjaxPack();

    var d = new Date();
    var day = d.getDate();
    var month = d.getMonth() + 1;
    var year = d.getFullYear();
    var fromDate = day + "." + month + "." + year;

    var postRequest = "currencyName=" + currencyName + "&fromDate=" + fromDate;
    try {
        this.ajaxpack.postAjaxRequest("http:" + this.ajaxpack.basedomain + get_holidays_path, postRequest, this.initHolidays, "xml");
    } catch(e) {
        //alert("Could not acquire list of bank holidays for "+this.name+ " market.");
    }
}
//****************************************************//
market = [
    //do NOT use string colors (e.g 'yellow'). Problematic with IE.
    new FXMarket(0, "Sydney", '#009900', 22 * 60 + 0, 7 * 60 + 0, -10 * 60, 1, 'AUD', 'SYD'),
    new FXMarket(1, "Tokyo", '#FFBE0F', 0 * 60 + 0, 9 * 60 + 0, -9 * 60, 0, 'JPY', 'TOK'),
    new FXMarket(2, "London", '#996600', 8 * 60 + 0, 17 * 60 + 0, 0, 1, 'GBP', 'LON'),
    new FXMarket(3, "New York", '#6699CC', 13 * 60 + 0, 22 * 60 + 0, 5 * 60, 2, 'USD', 'NYC')
];
//****************************************************//
var tZone = [
    new TimeZone(10 * 60, "Hawaii-Aleutian Standard Time", "(GMT-10) HST"),
    new TimeZone(9 * 60, "Alaska Standard Time, Hawaii-Aleutian Daylight Time", "(GMT-9) AKST,HADT"),
    new TimeZone(8 * 60, "Pacific Standard Time, Alaska Daylight Time", "(GMT-8) PST,AKDT"),
    new TimeZone(7 * 60, "Mountain Standard Time, Pacific Daylight Time", "(GMT-7) MST,PDT"),
    new TimeZone(6 * 60, "Central Standard Time, Mountain Daylight Time", "(GMT-6) CST,MDT"),
    new TimeZone(5 * 60, "Eastern Standard Time, Central Daylight Time", "(GMT-5) EST,CDT"),
    new TimeZone(4 * 60, "Atlantic Standard Time, Eastern Daylight Time", "(GMT-4) AST,EDT"),
    new TimeZone(3 * 60 + 30, "Newfoundland Standard Time", "(GMT-3:30) NST"),
    new TimeZone(3 * 60, "Atlantic Daylight Time", "(GMT-3) ADT"),
    new TimeZone(2 * 60 + 30, "Newfoundland Daylight Time", "(GMT-2:30) NDT"),
    new TimeZone(2 * 60, "", "(GMT-2)"),
    new TimeZone(1 * 60, "", "(GMT-1)"),
    new TimeZone(0, "Greenwich Mean Time, Western European Time", "GMT,WET"),
    new TimeZone(-1 * 60, "British Summer Time, Central European Time, Western European Daylight Time", "(GMT+1) BST,CET,WEDT"),
    new TimeZone(-2 * 60, "Eastern European/South African Standard Time, Central European Daylight Time", "(GMT+2) EET,SAST,CEDT"),
    new TimeZone(-3 * 60, "Moscow Time, Eastern European Daylight Time", "(GMT+3) MSK,EEDT"),
    new TimeZone(-4 * 60, "", "(GMT+4)"),
    new TimeZone(-5 * 60, "", "(GMT+5)"),
    new TimeZone(-(5 * 60 + 30), "Indian Standard Time", "(GMT+5:30) IST"),
    new TimeZone(-6 * 60, "", "(GMT+6)"),
    new TimeZone(-7 * 60, "Christmas Island Time", "(GMT+7) CXT"),
    new TimeZone(-8 * 60, "Singapore/Hong Kong/Chinese/Australian Western Standard Time", "(GMT+8) SGT,HKT,AWST"),
    new TimeZone(-9 * 60, "Japan/Korea Standard Time", "(GMT+9) JST,KST"),
    new TimeZone(-(9 * 60 + 30), "Australian Central Standard Time", "(GMT+9:30) ACST"),
    new TimeZone(-10 * 60, "Australian Eastern Standard Time", "(GMT+10) AEST"),
    new TimeZone(-(10 * 60 + 30), "Australian Central Daylight Time", "(GMT+10:30) ACDT"),
    new TimeZone(-11 * 60, "Australian Eastern Daylight Time", "(GMT+11) AEDT"),
    new TimeZone(-12 * 60, "New Zealand Standard Time", "(GMT+12) NZST"),
    new TimeZone(-13 * 60, "New Zealand Daylight Time", "(GMT+13) NZDT")
];

var the_timeout;
//****************************************************//
function computeDST_diff(observeDST) {
    //difference between current time and std time. If it is Daylight Saving Time (DST),
    //and the market observes it, the difference is 60 minutes otherwise 0.
    if (!observeDST) {
        return 0;
    } //when market dosen't observe DST
    var dst_start = new Date();
    var dst_end = new Date();

    var wday;
    if (observeDST == 2) {

        //compute 2nd Sunday of March (for NYC in 2007)
        dst_start.setMonth(2); // Set from month 0-11
        dst_start.setDate(1);
        wday = dst_start.getDay(); // what weekday is 31st?
        if (wday === 0
        /* as in sunday */
        ) {
            wday = 7;
        }
        dst_start.setDate(15 - wday); // subtract from the highest possible value of the second sunday (+1 because of array issues)
        //compute first Sunday of November
        dst_end.setMonth(10); // pick November
        dst_end.setDate(1);
        wday = dst_end.getDay();
        if (wday === 0
        /* as in sunday */
        ) {
            wday = 7;
        }
        dst_end.setDate(8 - wday);

    } else {
        //compute last Sunday of March
        dst_start.setMonth(2);
        dst_start.setDate(31);
        wday = dst_start.getDay(); // what weekday is 31st?
        dst_start.setDate(31 - wday); // go back until last Sunday
        //compute last Sunday of October
        dst_end.setMonth(9);
        dst_end.setDate(31);
        wday = dst_end.getDay();
        dst_end.setDate(31 - wday);
    }

    var current = new Date();
    if (current >= dst_start && current < dst_end) {
        return 60;
    } else {
        return 0;
    }
}
//****************************************************//
function adjustMarketHours() {
    //adjust market hours by converting GMT times to the timeZone time
    for (var row = 0; row < market.length; row++) {
        //current difference with standard time (in minutes)
        var DST_diff = computeDST_diff(market[row].observeDST);
        market[row].openTime = (market[row].GMTopenTime - timeZoneOffset - DST_diff + 24 * 60) % (24 * 60);
        //alert("opentime for "+ market[row].name + " is " + market[row].openTime);
        market[row].closeTime = (market[row].GMTcloseTime - timeZoneOffset - DST_diff + 24 * 60) % (24 * 60);
    }
}
//****************************************************//
var z_index = 100;
function mkLayer(id, left, top, width, height, color, text) { //generates the HTML code for a market layer
    var code = '<div class="mLayer" id="' + id + '" style="left:' + left + 'px;top:' + top + 'px;';
    code += 'height:' + height + 'px;width:' + width + 'px;';
    code += 'background-color:' + color + ';z-index:' + z_index + ';" >';
    code += text + '</div>';
    z_index++;
    return code;
}

//****************************************************//
function setTimer() {
    //refresh stats every minute
    if ((new Date()).getSeconds() === 0) {
        refreshStat();
    }

    //set timer for 1 sec
    the_timeout = setTimeout("setTimer();", 1 * 1000);
}
//****************************************************//
function initMarket() {
    adjustMarketHours();
    refreshStat();
}
//****************************************************//
function printMarketLayers() {
    var row, col;
    for (row = 0; row < market.length; row++) {
        document.write(mkLayer(market[row].layerA_id, 0, cellHeight * (row + 1), 100, cellHeight, market[row].color, market[row].name));
        document.write(mkLayer(market[row].layerB_id, 200, cellHeight * (row + 1), 100, cellHeight, market[row].color, market[row].name));
    }

    //FXMarkets' table
    document.write('<table class="marketLayersTable" id="FXMarkets_table"');
    document.write('width="' + tableWidth + '">');
    //header:
    document.write('<thead><tr>');

    var step = 1;
    var clWidth = cellWidth;
    if (compressedMode) {
        step = 2;
        clWidth = 2 * cellWidth;
    }

    var i;
    for (col = 0, i = 0; col < 24; i++, col += step) {
        var color = (i % 2 == 1) ? '#999999' : '#FFFFFF';
        var bcolor = 'background-color:' + color + ';';
        document.write('<td class="marketLayersTable" style="border-bottom:1px solid #808080;' + bcolor + fxmh_style + '" height="' + cellHeight + '" width="' + clWidth + '" align="center" colspan="' + step + '"');
        document.write('>' + col + '</td>');
    }
    document.write("</tr></thead>");
    //body: empty rows, market layers will appear here
    for (row = 0; row < market.length; row++) {
        document.write("<tr>");
        for (col = 0; col < 24; col++) {
            document.write('<td class="marketLayersTable" style="' + fxmh_style + '" height="' + cellHeight + '" width="' + cellWidth + '" align="left"> </td>');
        }
        document.write('</tr>');
    }
    document.write('</table>');
}
//****************************************************//
function printTimeZoneSelector() {
    var tmpStr = '<div style="' + fxmh_style + '">';

    if (compressedMode) {
        tmpStr += 'TZ: <select name="time_zone" style="font-size:7pt"';
    }
    else {
        tmpStr += 'Time Zone: <select name="time_zone" ';
    }

    tmpStr += ' onChange="timeZoneOffset=this.options[selectedIndex].value;initMarket();" >';
    document.write(tmpStr);

    for (var i = 0; i < tZone.length; i++) {
        document.write('<option value="' + tZone[i].offset + '" ');
        if (tZone[i].title !== "") {
            document.write('title="' + tZone[i].title + '"');
        }
        if (tZone[i].offset == timeZoneOffset) {
            document.write(' selected ');
        }
        document.write('>' + tZone[i].text);
    }
    document.write('</select></div><br>');
}
//****************************************************//

