/*
 * @(#) Clock.js
 *
 * Clock class
 * Copyright (c) 2010, 2012 Peter Wall
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

var net;
if (!net)
    net = {};
else if (typeof net != 'object')
    throw new Error('net prefix exists but is not an object');
if (!net.pwall)
    net.pwall = {};
else if (typeof net.pwall != 'object')
    throw new Error('net.pwall prefix exists but is not an object');

/**
 * Create a Clock object.  Each Clock object relates to a set of HTML elements containing the
 * different components of a date, all contained within an outer element.  The individual
 * elements are identified by class name.  The display method will update all the date elements,
 * and the refreshEachMinute and refreshEachSecond methods will repeatedly update all the Clock
 * objects in the clocks array.
 *
 * <p>This class requires the package {@code net.pwall.Date}</p>
 *
 * @constructor
 * @param {Element|String} elem     the container element for the clock (or its id)
 * @param {String}         [city]   the city name, in Region/City form, e.g. Australia/Sydney
 */
net.pwall.Clock = function(elem, city) {
    if (typeof elem == 'string')
        elem = document.getElementById(elem);
    this.elem = elem;
    this.city = city;
    if (!city) {
        elem = net.pwall.Clock.getElementByClassName('pwall-clock-city', this.elem);
        if (elem)
            this.city = elem.innerHTML;
    }
    this.timeZone = this.city ? net.pwall.Clock.timeZones[this.city] : null;
    this.dateSeparator = '-';
    this.timeSeparator = ':';
    this.dayNames = net.pwall.Date.dayNamesEnglish;
    this.monthNames = net.pwall.Date.monthNamesEnglish;
};

net.pwall.Clock.prototype.display = function(date) {
    if (this.timeZone) {
        var zoneName = this.timeZone.standard;
        var offset = this.timeZone.offset;
        if (this.timeZone.daylightTimes) {
            var dateISO8601 = net.pwall.Date.toISO8601DateTimeUTC(date);
            for (var i = 0; i < this.timeZone.daylightTimes.length; ++i) {
                var entry = this.timeZone.daylightTimes[i];
                if (dateISO8601 >= entry.start && dateISO8601 < entry.end) {
                    offset++;
                    zoneName = this.timeZone.daylight;
                    break;
                }
            }
        }
        this.displayZone(zoneName, offset);
        date = new Date(date.valueOf() + (offset * 60 * 60 * 1000));
    }
    this.displayDate(date);
    this.displayTime(date);
    this.displayDay(date);
};

net.pwall.Clock.prototype.displayItem = function(id, content) {
    var elem = net.pwall.Clock.getElementByClassName(id, this.elem);
    if (elem)
        elem.innerHTML = content;
};

net.pwall.Clock.prototype.displayDate = function(date) {
    this.displayItem('pwall-clock-date', this.formatDate(date));
};

net.pwall.Clock.prototype.formatDate = function(date) {
    return String(date.getUTCFullYear()) + this.dateSeparator +
            net.pwall.Date.twoDigit(date.getUTCMonth() + 1) + this.dateSeparator +
            net.pwall.Date.twoDigit(date.getUTCDate());
};

net.pwall.Clock.prototype.displayTime = function(date) {
    this.displayItem('pwall-clock-time', this.formatTime(date));
};

net.pwall.Clock.prototype.formatTime = function(date) {
    return net.pwall.Date.twoDigit(date.getUTCHours()) + this.timeSeparator +
            net.pwall.Date.twoDigit(date.getUTCMinutes());
};

net.pwall.Clock.prototype.displayDay = function(date) {
    this.displayItem('pwall-clock-day', this.formatDay(date));
};

net.pwall.Clock.prototype.formatDay = function(date) {
    return this.dayNames[date.getUTCDay()];
};

net.pwall.Clock.prototype.displayZone = function(name, offset) {
    this.displayItem('pwall-clock-zone', this.formatZone(name, offset));
};

net.pwall.Clock.prototype.formatZone = function(name, offset) {
    return name + ' : UTC' + (offset < 0 ? '-' : '+') + net.pwall.Date.twoDigit(offset) +
            ':' + net.pwall.Date.twoDigit((offset * 60) % 60);
};

net.pwall.Clock.formatDateDMY = function(date) {
    return net.pwall.Date.twoDigit(date.getUTCDate()) + this.dateSeparator +
            net.pwall.Date.twoDigit(date.getUTCMonth() + 1) + this.dateSeparator +
            String(date.getUTCFullYear());
};

net.pwall.Clock.formatDateLong = function(date) {
    return String(date.getUTCDate()) + ' ' + this.monthNames[date.getUTCMonth()] + ' ' +
            String(date.getUTCFullYear());
};

net.pwall.Clock.formatDateLongUS = function(date) {
    return this.monthNames[date.getUTCMonth()] + ' ' + String(date.getUTCDate()) + ', ' +
            String(date.getUTCFullYear());
};

net.pwall.Clock.formatDateShortUS = function(date) {
    return String(date.getUTCMonth() + 1) + '/' + String(date.getUTCDate()) + '/' +
            String(date.getUTCFullYear());
};

net.pwall.Clock.formatTimeSeconds = function(date) {
    return net.pwall.Date.twoDigit(date.getUTCHours()) + this.timeSeparator +
            net.pwall.Date.twoDigit(date.getUTCMinutes()) + this.timeSeparator +
            net.pwall.Date.twoDigit(date.getUTCSeconds());
};

net.pwall.Clock.formatTimeAMPM = function(date) {
    var h = date.getUTCHours();
    return String((h + 11) % 12 + 1) + ':' + net.pwall.Date.twoDigit(date.getUTCMinutes()) +
            ' ' + (h < 12 ? 'AM' : 'PM');
};

net.pwall.Clock.displayZoneTooltip = function(name, offset) {
//    var elem = net.pwall.Clock.getElementByClassName('pwall-clock-time', this.elem);
//    if (elem)
//        elem.title = this.formatZone(name, offset);
    this.elem.title = this.formatZone(name, offset);
};

net.pwall.Clock.getElementByClassName = function(id, elem) {
    if (id) {
        if (!elem)
            elem = document.documentElement;
        else if (typeof elem === 'string')
            elem = document.getElementById(elem);
        if (elem) {
            var className = elem.className;
            if (className) {
                if (className == id)
                    return elem;
                if (className.search('\\b' + id + '\\b') >= 0)
                    return elem;
            }
            var children = elem.childNodes;
            if (children) {
                for (var i = 0; i < children.length; ++i) {
                    var result = net.pwall.Clock.getElementByClassName(id, children[i]);
                    if (result)
                        return result;
                }
            }
        }
    }
    return null;
};

net.pwall.Clock.refreshEachMinute = function() {
    if (net.pwall.Clock.timeoutId) {
        window.clearTimeout(net.pwall.Clock.timeoutId);
        net.pwall.Clock.timeoutId = null;
    }
    var date = new Date();
    net.pwall.Clock.displayAll(date);
    net.pwall.Clock.timeoutId = window.setTimeout(net.pwall.Clock.refreshEachMinute,
            (60 - date.getUTCSeconds()) * 1000 - date.getUTCMilliseconds());
};

net.pwall.Clock.refreshEachSecond = function() {
    if (net.pwall.Clock.timeoutId) {
        window.clearTimeout(net.pwall.Clock.timeoutId);
        net.pwall.Clock.timeoutId = null;
    }
    var date = new Date();
    net.pwall.Clock.displayAll(date);
    net.pwall.Clock.timeoutId = window.setTimeout(net.pwall.Clock.refreshEachSecond,
            1000 - date.getUTCMilliseconds());
};

net.pwall.Clock.displayAll = function(date) {
    for (var i = 0; i < net.pwall.Clock.clocks.length; ++i)
        net.pwall.Clock.clocks[i].display(date);
};

net.pwall.Clock.timeoutId = null;

net.pwall.Clock.clocks = [];

net.pwall.Clock.timeZones = {};

net.pwall.Clock.timeZones['GMT'] = {
    offset: 0,
    standard: 'GMT'
};

net.pwall.Clock.timeZones['Pacific/Auckland'] = {
    offset: +12,
    standard: 'NZST',
    daylight: 'NZDT',
    daylightTimes: [
        { start: '2008-09-27T14:00:00Z', end: '2009-04-04T14:00:00Z' },
        { start: '2009-09-26T14:00:00Z', end: '2010-04-03T14:00:00Z' },
        { start: '2010-09-25T14:00:00Z', end: '2011-04-02T14:00:00Z' },
        { start: '2011-09-24T14:00:00Z', end: '2012-03-31T14:00:00Z' },
        { start: '2012-09-29T14:00:00Z', end: '2013-04-06T14:00:00Z' },
        { start: '2013-09-28T14:00:00Z', end: '2014-04-05T14:00:00Z' },
        { start: '2014-09-27T14:00:00Z', end: '2015-04-04T14:00:00Z' },
        { start: '2015-09-26T14:00:00Z', end: '2016-04-02T14:00:00Z' },
        { start: '2016-09-24T14:00:00Z', end: '2017-04-01T14:00:00Z' }
    ]
};

net.pwall.Clock.timeZones['Australia/Sydney'] = {
    offset: +10,
    standard: 'AEST',
    daylight: 'AEDT',
    daylightTimes: [
        { start: '2008-10-04T16:00:00Z', end: '2009-04-04T16:00:00Z' },
        { start: '2009-10-03T16:00:00Z', end: '2010-04-03T16:00:00Z' },
        { start: '2010-10-02T16:00:00Z', end: '2011-04-02T16:00:00Z' },
        { start: '2011-10-01T16:00:00Z', end: '2012-03-31T16:00:00Z' },
        { start: '2012-10-06T16:00:00Z', end: '2013-04-06T16:00:00Z' },
        { start: '2013-10-05T16:00:00Z', end: '2014-04-05T16:00:00Z' },
        { start: '2014-10-04T16:00:00Z', end: '2015-04-04T16:00:00Z' },
        { start: '2015-10-03T16:00:00Z', end: '2016-04-02T16:00:00Z' },
        { start: '2016-10-01T16:00:00Z', end: '2017-04-01T16:00:00Z' }
    ]
};

net.pwall.Clock.timeZones['Australia/Melbourne'] =
        net.pwall.Clock.timeZones['Australia/Sydney'];

net.pwall.Clock.timeZones['Australia/Hobart'] = net.pwall.Clock.timeZones['Australia/Sydney'];

net.pwall.Clock.timeZones['Australia/Brisbane'] = {
    offset: +10,
    standard: 'AEST'
};

net.pwall.Clock.timeZones['Australia/Adelaide'] = {
    offset: +9.5,
    standard: 'ACST',
    daylight: 'ACDT',
    daylightTimes: [
        { start: '2008-10-04T16:30:00Z', end: '2009-04-04T16:30:00Z' },
        { start: '2009-10-03T16:30:00Z', end: '2010-04-03T16:30:00Z' },
        { start: '2010-10-02T16:30:00Z', end: '2011-04-02T16:30:00Z' },
        { start: '2011-10-01T16:30:00Z', end: '2012-03-31T16:30:00Z' },
        { start: '2012-10-06T16:30:00Z', end: '2013-04-06T16:30:00Z' },
        { start: '2013-10-05T16:30:00Z', end: '2014-04-05T16:30:00Z' },
        { start: '2014-10-04T16:30:00Z', end: '2015-04-04T16:30:00Z' },
        { start: '2015-10-03T16:30:00Z', end: '2016-04-02T16:30:00Z' },
        { start: '2016-10-01T16:30:00Z', end: '2017-04-01T16:30:00Z' }
    ]
};

net.pwall.Clock.timeZones['Australia/Darwin'] = {
    offset: +9.5,
    standard: 'ACST'
};

net.pwall.Clock.timeZones['Australia/Perth'] = {
    offset: +8,
    standard: 'AWST'
};

net.pwall.Clock.timeZones['Asia/Singapore'] = {
    offset: +8,
    standard: 'SGT'
};

net.pwall.Clock.timeZones['Asia/Tokyo'] = {
    offset: +9,
    standard: 'JST'
};

net.pwall.Clock.timeZones['Asia/Shanghai'] = {
    offset: +8,
    standard: 'CST'
};

net.pwall.Clock.timeZones['Asia/Kolkata'] = {
    offset: +5.5,
    standard: 'IST'
};

net.pwall.Clock.timeZones['Europe/London'] = {
    offset: 0,
    standard: 'GMT',
    daylight: 'BST',
    daylightTimes: [
        { start: '2009-03-29T01:00:00Z', end: '2009-10-25T01:00:00Z' },
        { start: '2010-03-28T01:00:00Z', end: '2010-10-31T01:00:00Z' },
        { start: '2011-03-27T01:00:00Z', end: '2011-10-30T01:00:00Z' },
        { start: '2012-03-25T01:00:00Z', end: '2012-10-28T01:00:00Z' },
        { start: '2013-03-31T01:00:00Z', end: '2013-10-27T01:00:00Z' },
        { start: '2014-03-30T01:00:00Z', end: '2014-10-26T01:00:00Z' },
        { start: '2015-03-29T01:00:00Z', end: '2015-10-25T01:00:00Z' },
        { start: '2016-03-27T01:00:00Z', end: '2016-10-30T01:00:00Z' },
        { start: '2017-03-26T01:00:00Z', end: '2017-10-29T01:00:00Z' }
    ]
};

net.pwall.Clock.timeZones['Europe/Paris'] = {
    offset: +1,
    standard: 'CET',
    daylight: 'CEST',
    daylightTimes: [
        { start: '2009-03-29T01:00:00Z', end: '2009-10-25T01:00:00Z' },
        { start: '2010-03-28T01:00:00Z', end: '2010-10-31T01:00:00Z' },
        { start: '2011-03-27T01:00:00Z', end: '2011-10-30T01:00:00Z' },
        { start: '2012-03-25T01:00:00Z', end: '2012-10-28T01:00:00Z' },
        { start: '2013-03-31T01:00:00Z', end: '2013-10-27T01:00:00Z' },
        { start: '2014-03-30T01:00:00Z', end: '2014-10-26T01:00:00Z' },
        { start: '2015-03-29T01:00:00Z', end: '2015-10-25T01:00:00Z' },
        { start: '2016-03-27T01:00:00Z', end: '2016-10-30T01:00:00Z' },
        { start: '2017-03-26T01:00:00Z', end: '2017-10-29T01:00:00Z' }
    ]
};

net.pwall.Clock.timeZones['Europe/Berlin'] = net.pwall.Clock.timeZones['Europe/Paris'];

net.pwall.Clock.timeZones['Europe/Moscow'] = {
    // Moscow switched to permanent summer time in March 2011
    offset: +4,
    standard: 'MSK'
};

net.pwall.Clock.timeZones['America/New_York'] = {
    offset: -5,
    standard: 'EST',
    daylight: 'EDT',
    daylightTimes: [
        { start: '2009-03-08T07:00:00Z', end: '2009-11-01T07:00:00Z' },
        { start: '2010-03-14T07:00:00Z', end: '2010-11-07T07:00:00Z' },
        { start: '2011-03-13T07:00:00Z', end: '2011-11-06T07:00:00Z' },
        { start: '2012-03-11T07:00:00Z', end: '2012-11-04T07:00:00Z' },
        { start: '2013-03-10T07:00:00Z', end: '2013-11-03T07:00:00Z' },
        { start: '2014-03-09T07:00:00Z', end: '2014-11-02T07:00:00Z' },
        { start: '2015-03-08T07:00:00Z', end: '2015-11-01T07:00:00Z' },
        { start: '2016-03-13T07:00:00Z', end: '2016-11-06T07:00:00Z' },
        { start: '2017-03-12T07:00:00Z', end: '2017-11-05T07:00:00Z' }
    ]
};

net.pwall.Clock.timeZones['America/Chicago'] = {
    offset: -6,
    standard: 'CST',
    daylight: 'CDT',
    daylightTimes: [
        { start: '2009-03-08T08:00:00Z', end: '2009-11-01T08:00:00Z' },
        { start: '2010-03-14T08:00:00Z', end: '2010-11-07T08:00:00Z' },
        { start: '2011-03-13T08:00:00Z', end: '2011-11-06T08:00:00Z' },
        { start: '2012-03-11T08:00:00Z', end: '2012-11-04T08:00:00Z' },
        { start: '2013-03-10T08:00:00Z', end: '2013-11-03T08:00:00Z' },
        { start: '2014-03-09T08:00:00Z', end: '2014-11-02T08:00:00Z' },
        { start: '2015-03-08T08:00:00Z', end: '2015-11-01T08:00:00Z' },
        { start: '2016-03-13T08:00:00Z', end: '2016-11-06T08:00:00Z' },
        { start: '2017-03-12T08:00:00Z', end: '2017-11-05T08:00:00Z' }
    ]
};

net.pwall.Clock.timeZones['America/Denver'] = {
    offset: -7,
    standard: 'MST',
    daylight: 'MDT',
    daylightTimes: [
        { start: '2009-03-08T09:00:00Z', end: '2009-11-01T09:00:00Z' },
        { start: '2010-03-14T09:00:00Z', end: '2010-11-07T09:00:00Z' },
        { start: '2011-03-13T09:00:00Z', end: '2011-11-06T09:00:00Z' },
        { start: '2012-03-11T09:00:00Z', end: '2012-11-04T09:00:00Z' },
        { start: '2013-03-10T09:00:00Z', end: '2013-11-03T09:00:00Z' },
        { start: '2014-03-09T09:00:00Z', end: '2014-11-02T09:00:00Z' },
        { start: '2015-03-08T09:00:00Z', end: '2015-11-01T09:00:00Z' },
        { start: '2016-03-13T09:00:00Z', end: '2016-11-06T09:00:00Z' },
        { start: '2017-03-12T09:00:00Z', end: '2017-11-05T09:00:00Z' }
    ]
};

net.pwall.Clock.timeZones['America/Los_Angeles'] = {
    offset: -8,
    standard: 'PST',
    daylight: 'PDT',
    daylightTimes: [
        { start: '2009-03-08T10:00:00Z', end: '2009-11-01T10:00:00Z' },
        { start: '2010-03-14T10:00:00Z', end: '2010-11-07T10:00:00Z' },
        { start: '2011-03-13T10:00:00Z', end: '2011-11-06T10:00:00Z' },
        { start: '2012-03-11T10:00:00Z', end: '2012-11-04T10:00:00Z' },
        { start: '2013-03-10T10:00:00Z', end: '2013-11-03T10:00:00Z' },
        { start: '2014-03-09T10:00:00Z', end: '2014-11-02T10:00:00Z' },
        { start: '2015-03-08T10:00:00Z', end: '2015-11-01T10:00:00Z' },
        { start: '2016-03-13T10:00:00Z', end: '2016-11-06T10:00:00Z' },
        { start: '2017-03-12T10:00:00Z', end: '2017-11-05T10:00:00Z' }
    ]
};

