////////////////////////////////////////////////////////////////////////////////
// Constants
////////////////////////////////////////////////////////////////////////////////

var __CFDATE_ERROR_DIALOG_BOX_ID = "invalid CFDateDialogBox id";
var __CFDATE_ERROR_EDITOR_ID = "invalid CFDateEditor id";
var __CFDATE_ERROR_EMPTY_INDEX = "no select box option selected";
var __CFDATE_ERROR_FORMAT = "date isn't formatted correctly";
var __CFDATE_ERROR_FROM_DATE_TIME = "empty from date/time";
var __CFDATE_ERROR_RANGE = "from date/time is greater than to date/time";
var __CFDATE_ERROR_TIME_DIALOG_BOX_ID = "invalid CFDateTimeDialogBox id";
var __CFDATE_ERROR_TIME_EDITOR_ID = "invalid CFDateTimeEditor id";
var __CFDATE_ERROR_TIME_RANGE_LIST_ID = "invalid CFDateTimeRangeList id";
var __CFDATE_ERROR_TO_DATE_TIME = "empty to date/time";

var __CFDATE_RANGE_DELIMITER = " to ";

////////////////////////////////////////////////////////////////////////////////
// Static Variables
////////////////////////////////////////////////////////////////////////////////

var __cfDateDialogBoxMap = {};
var __cfDateEditorMap = {};
var __cfDateTimeDialogBoxMap = {};
var __cfDateTimeEditorMap = {};
var __cfDateTimeRangeListMap = {};

////////////////////////////////////////////////////////////////////////////////
// Classes
////////////////////////////////////////////////////////////////////////////////

// CFDateDialogBox

function CFDateDialogBox(id, titleBoxId, contentBoxId, closeButtonId,
                         calendarId, cancelButtonId, confirmButtonId, value)
{
    CFDialogBox.call(this, id, titleBoxId, contentBoxId, closeButtonId);
    var calendar = cfCalendarGet(calendarId);
    var cancelButton = cfElementGet(cancelButtonId);
    var confirmButton = cfElementGet(confirmButtonId);
    this.__calendar = calendar;
    this.setValue(value);
    __cfDateDialogBoxMap[id] = this;
    f = cfEventHandlerCreate(this.__handleCalendarUpdate.bind(this));
    calendar.addEventHandler(CFWIDGET_EVENT_USER_UPDATE, f);
    f = cfEventHandlerCreate(this.__handleCancel.bind(this));
    cancelButton.onclick = f;
    f = cfEventHandlerCreate(this.__handleConfirm.bind(this));
    confirmButton.onclick = f;
}

CFDateDialogBox.extendClasses(CFDialogBox);

CFDateDialogBox.prototype.__handleCalendarUpdate = function()
{
    this.__triggerEvent(CFWIDGET_EVENT_USER_UPDATE);
}

CFDateDialogBox.prototype.__handleCancel = function()
{
    this.__setResult(undefined);
    this.hide();
}

CFDateDialogBox.prototype.__handleConfirm = function()
{
    this.__setResult(this.getValue());
    this.hide();
}

CFDateDialogBox.prototype.getValue = function()
{
    return this.__calendar.getValue();
}

CFDateDialogBox.prototype.refresh = function()
{
    CFDialogBox.prototype.refresh.call(this);
    this.__calendar.refresh();
}

CFDateDialogBox.prototype.setValue = function(value)
{
    this.__calendar.setValue(value);
}

// CFDateEditor

function CFDateEditor(id, textBoxId, dialogBoxId, changeButtonId, formatString,
                      value)
{
    CFWidget.call(this, id);
    var changeButton = cfElementGet(changeButtonId);
    var dialogBox = cfDateDialogBoxGet(dialogBoxId);
    var textBox = cfElementGet(textBoxId);
    this.__changeButton = changeButton;
    this.__dialogBox = dialogBox;
    this.__formatString = formatString;
    this.__textBox = textBox;
    this.setValue(value);
    __cfDateEditorMap[id] = this;
    var f = cfEventHandlerCreate(this.__showDialogBox.bind(this));
    changeButton.onclick = f;
    var f = cfEventHandlerCreate(this.__getDialogBoxResult.bind(this));
    dialogBox.addEventHandler(CFWIDGET_EVENT_HIDE, f);
    var f = cfEventHandlerCreate(this.__handleTextBoxUpdate.bind(this));
    textBox.onblur = f;
}

CFDateEditor.extendClasses(CFWidget);

CFDateEditor.prototype.__getDialogBoxResult = function()
{
    var result = this.__dialogBox.getResult();
    if (typeof(result) != "undefined") {
        this.setValue(result);
        this.__triggerEvent(CFWIDGET_EVENT_USER_UPDATE);
    }
}

CFDateEditor.prototype.__handleTextBoxUpdate = function()
{
    var date;
    var s = this.__textBox.value.strip();
    if (! s) {
        date = undefined;
    } else {
        date = Date.fromFormattedString(s, this.__formatString, true);
        if (typeof(date) == "undefined") {
            this.setValue(this.getValue());
            return cfErrorTrigger("CFDateEditor::__handleTextBoxUpdate: '" + s +
                                  "': " + __CFDATE_ERROR_FORMAT);
        }
    }
    this.setValue(date);
    this.__triggerEvent(CFWIDGET_EVENT_USER_UPDATE);
}

CFDateEditor.prototype.__showDialogBox = function()
{
    this.__dialogBox.show();
}

CFDateEditor.prototype.getValue = function()
{
    var value = this.__value;
    return (typeof(value) == "undefined") ? undefined : new Date(value);
}

CFDateEditor.prototype.refresh = function()
{
    CFWidget.prototype.refresh.call(this);
    this.__dialogBox.refresh();
}

CFDateEditor.prototype.setValue = function(value)
{
    if (typeof(value) == "undefined") {
        this.__textBox.value = '';
    } else {
        value = new Date(value);
        this.__dialogBox.setValue(value);
        this.__textBox.value = value.toFormattedString(this.__formatString);
    }
    this.__value = value;
}

// CFDateTimeDialogBox

function CFDateTimeDialogBox(id, titleBoxId, contentBoxId, closeButtonId,
                             calendarId, cancelButtonId, confirmButtonId, 
                             hourSpinnerId, minuteSpinnerId, secondSpinnerId,
                             periodSelectId, value)
{
    var hourSpinner = cfSpinnerGet(hourSpinnerId);
    var minuteSpinner = cfSpinnerGet(minuteSpinnerId);
    var periodSelect = cfElementGet(periodSelectId);
    var secondSpinner = secondSpinnerId.length ? cfSpinnerGet(secondSpinnerId) :
                        undefined;
    this.__hourSpinner = hourSpinner;
    this.__minuteSpinner = minuteSpinner;
    this.__periodSelect = periodSelect;
    this.__secondSpinner = secondSpinner;
    CFDateDialogBox.call(this, id, titleBoxId, contentBoxId, closeButtonId,
                         calendarId, cancelButtonId, confirmButtonId, value);
    __cfDateTimeDialogBoxMap[id] = this;
    f = cfEventHandlerCreate(this.__handleHourUpdate.bind(this));
    hourSpinner.addEventHandler(CFWIDGET_EVENT_USER_UPDATE, f);
    cfSelectSetChangeCallback(periodSelect, f);
    f = cfEventHandlerCreate(this.__handleMinuteUpdate.bind(this));
    minuteSpinner.addEventHandler(CFWIDGET_EVENT_USER_UPDATE, f);
    if (secondSpinner) {
        f = cfEventHandlerCreate(this.__handleSecondUpdate.bind(this));
        secondSpinner.addEventHandler(CFWIDGET_EVENT_USER_UPDATE, f);
    }
}

CFDateTimeDialogBox.extendClasses(CFDateDialogBox);

CFDateTimeDialogBox.prototype.__handleHourUpdate = function()
{
    var date = this.getValue();
    var hour = (this.__hourSpinner.getValue() +
                (cfSelectGetValue(this.__periodSelect) == "PM" ? 12 : 0)) % 24;
    date.setHours(hour);
    this.setValue(date);
    this.__triggerEvent(CFWIDGET_EVENT_USER_UPDATE);
}

CFDateTimeDialogBox.prototype.__handleMinuteUpdate = function()
{
    var date = this.getValue();
    date.setMinutes(this.__minuteSpinner.getValue());
    this.setValue(date);
    this.__triggerEvent(CFWIDGET_EVENT_USER_UPDATE);
}

CFDateTimeDialogBox.prototype.__handleSecondUpdate = function()
{
    var date = this.getValue();
    date.setSeconds(this.__secondSpinner.getValue());
    this.setValue(date);
    this.__triggerEvent(CFWIDGET_EVENT_USER_UPDATE);
}

CFDateTimeDialogBox.prototype.setValue = function(value)
{
    CFDateDialogBox.prototype.setValue.call(this, value);
    value = this.getValue();
    var hour = value.getHours();
    this.__hourSpinner.setValue((hour % 12) || 12);
    this.__minuteSpinner.setValue(value.getMinutes());
    cfSelectSetValue(this.__periodSelect, hour >= 12 ? "PM" : "AM");
    var secondSpinner = this.__secondSpinner;
    if (secondSpinner) {
        secondSpinner.setValue(value.getSeconds());
    }
}

// CFDateTimeEditor

function CFDateTimeEditor(id, textBoxId, dialogBoxId, changeButtonId,
                          formatString, value)
{
    CFDateEditor.call(this, id, textBoxId, dialogBoxId, changeButtonId,
                      formatString, value);
    __cfDateTimeEditorMap[id] = this;
}

CFDateTimeEditor.extendClasses(CFDateEditor);

// CFDateTimeRange

function CFDateTimeRange(fromDateTime, toDateTime)
{
    if (typeof(fromDateTime) == "undefined") {
        return cfErrorTrigger("CFDateTimeRange: " +
                              __CFDATE_ERROR_FROM_DATE_TIME);
    }
    if (typeof(toDateTime) == "undefined") {
        return cfErrorTrigger("CFDateTimeRange: " +
                              __CFDATE_ERROR_TO_DATE_TIME);
    }
    if (fromDateTime.isGreaterThan(toDateTime)) {
        return cfErrorTrigger("CFDateTimeRange: " + __CFDATE_ERROR_RANGE);
    }
    this.__fromDateTime = fromDateTime;
    this.__toDateTime = toDateTime;
}

CFDateTimeRange.prototype.getFromDateTime = function()
{
    return new Date(this.__fromDateTime);
}

CFDateTimeRange.prototype.getToDateTime = function()
{
    return new Date(this.__toDateTime);
}

CFDateTimeRange.prototype.toFormattedString = function(s)
{
    return this.__fromDateTime.toFormattedString(s) + __CFDATE_RANGE_DELIMITER +
           this.__toDateTime.toFormattedString(s);
}

// CFDateTimeRangeList

function CFDateTimeRangeList(id, selectBoxId, textAreaId, fromDateTimeEditorId,
                             fromDateTimeEditorWrapperId, toDateTimeEditorId,
                             toDateTimeEditorWrapperId, addButtonId,
                             removeButtonId, formatString, value)
{
    CFWidget.call(this, id);
    var addButton = cfElementGet(addButtonId);
    var fromDateTimeEditor = cfDateTimeEditorGet(fromDateTimeEditorId);
    var removeButton = cfElementGet(removeButtonId);
    var selectBox = cfElementGet(selectBoxId);
    var textArea = cfElementGet(textAreaId);
    var toDateTimeEditor = cfDateTimeEditorGet(toDateTimeEditorId);
    this.__formatString = formatString;
    this.__fromDateTimeEditor = fromDateTimeEditor;
    this.__selectBox = selectBox;
    this.__textArea = textArea;
    this.__toDateTimeEditor = toDateTimeEditor;
    this.setValue(value);
    cfElementAddClass(textArea, CFELEMENT_HIDDEN_CLASS);
    cfElementRemoveClass(addButton, CFELEMENT_HIDDEN_CLASS);
    cfElementRemoveClass(removeButton, CFELEMENT_HIDDEN_CLASS);
    cfElementRemoveClass(selectBox, CFELEMENT_HIDDEN_CLASS);
    cfElementRemoveClass(cfElementGet(fromDateTimeEditorWrapperId),
                         CFELEMENT_HIDDEN_CLASS);
    cfElementRemoveClass(cfElementGet(toDateTimeEditorWrapperId),
                         CFELEMENT_HIDDEN_CLASS);
    __cfDateTimeRangeListMap[id] = this;
    var f = cfEventHandlerCreate(this.__handleAdd.bind(this));
    addButton.onclick = f;
    f = cfEventHandlerCreate(this.__handleFromDateTimeUpdate.bind(this));
    fromDateTimeEditor.addEventHandler(CFWIDGET_EVENT_USER_UPDATE, f);
    f = cfEventHandlerCreate(this.__handleRemove.bind(this));
    removeButton.onclick = f;
    f = cfEventHandlerCreate(this.__handleToDateTimeUpdate.bind(this));
    toDateTimeEditor.addEventHandler(CFWIDGET_EVENT_USER_UPDATE, f);
}

CFDateTimeRangeList.extendClasses(CFWidget);

CFDateTimeRangeList.prototype.__handleAdd = function()
{
    var fromEditor = this.__fromDateTimeEditor;
    var toEditor = this.__toDateTimeEditor;
    var value = this.getValue();
    value.push(new CFDateTimeRange(fromEditor.getValue(), toEditor.getValue()));
    this.setValue(value);
    fromEditor.setValue(undefined);
    toEditor.setValue(undefined);
    this.__triggerEvent(CFWIDGET_EVENT_USER_UPDATE);
}

CFDateTimeRangeList.prototype.__handleFromDateTimeUpdate = function()
{
    var fromValue = this.__fromDateTimeEditor.getValue();
    if (typeof(fromValue) != "undefined") {
        var toEditor = this.__toDateTimeEditor;
        var toValue = toEditor.getValue();
        if ((typeof(toValue) == "undefined") ||
            (fromValue.isGreaterThan(toValue))) {
            toEditor.setValue(fromValue);
        }
    }
}

CFDateTimeRangeList.prototype.__handleRemove = function()
{
    var index = this.__selectBox.selectedIndex;
    if (index == -1) {
        return cfErrorTrigger("CFDateTimeRangeList::__handleRemove: " +
                              __CFDATE_ERROR_EMPTY_INDEX);
    }
    value = this.getValue();
    value.splice(index, 1);
    this.setValue(value);
    this.__triggerEvent(CFWIDGET_EVENT_USER_UPDATE);
}

CFDateTimeRangeList.prototype.__handleToDateTimeUpdate = function()
{
    var toValue = this.__toDateTimeEditor.getValue();
    if (typeof(toValue) != "undefined") {
        var fromEditor = this.__fromDateTimeEditor;
        var fromValue = fromEditor.getValue();
        if ((typeof(fromValue) == "undefined") ||
            (toValue.isLessThan(fromValue))) {
            fromEditor.setValue(toValue);
        }
    }
}

CFDateTimeRangeList.prototype.getValue = function()
{
    return this.__value.copy();
}

CFDateTimeRangeList.prototype.setValue = function(value)
{
    var formatString = this.__formatString;
    var lines = new Array();
    for (var i = 0; i < value.length; i++) {
        lines.push(value[i].toFormattedString(formatString));
    }
    var selectBox = this.__selectBox;
    cfSelectClearOptions(selectBox);
    for (var i = 0; i < lines.length; i++) {
        var line = lines[i];
        selectBox.add(new Option(line, line), undefined);
    }
    this.__textArea.value = lines.join('\n');
    this.__value = value;
}

////////////////////////////////////////////////////////////////////////////////
// Public API
////////////////////////////////////////////////////////////////////////////////

function cfDateDialogBoxCreate(arg)
{
    return new CFDateDialogBox(arg.id, arg.titleBoxId, arg.contentPaneId,
                               arg.closeButtonId, arg.calendarId,
                               arg.cancelButtonId, arg.confirmButtonId,
                               arg.value);
}

function cfDateDialogBoxGet(id)
{
    var box = __cfDateDialogBoxMap[id];
    if (! box) {
        return cfErrorTrigger("cfDateDialogBoxGet: '" + id + "': " +
                              __CFDATE_ERROR_DIALOG_BOX_ID);
    }
    return box;
}

function cfDateEditorCreate(arg)
{
    return new CFDateEditor(arg.id, arg.textBoxId, arg.dialogBoxId,
                            arg.changeButtonId, arg.formatString, arg.value);
}

function cfDateEditorGet(id)
{
    var editor = __cfDateEditorMap[id];
    if (! editor) {
        return cfErrorTrigger("cfDateEditorGet: '" + id + "': " +
                              __CFDATE_ERROR_EDITOR_ID);
    }
    return editor;
}

function cfDateTimeDialogBoxCreate(arg)
{
    return new CFDateTimeDialogBox(arg.id, arg.titleBoxId, arg.contentPaneId,
                                   arg.closeButtonId, arg.calendarId,
                                   arg.cancelButtonId, arg.confirmButtonId, 
                                   arg.hourSpinnerId, arg.minuteSpinnerId,
                                   arg.secondSpinnerId, arg.periodSelectId,
                                   arg.value);
}

function cfDateTimeDialogBoxGet(id)
{
    var box = __cfDateTimeDialogBoxMap[id];
    if (! box) {
        return cfErrorTrigger("cfDateTimeDialogBoxGet: '" + id + "': " +
                              __CFDATE_ERROR_TIME_DIALOG_BOX_ID);
    }
    return box;
}

function cfDateTimeEditorCreate(arg)
{
    return new CFDateTimeEditor(arg.id, arg.textBoxId, arg.dialogBoxId,
                                arg.changeButtonId, arg.formatString,
                                arg.value);
}

function cfDateTimeEditorGet(id)
{
    var editor = __cfDateTimeEditorMap[id];
    if (! editor) {
        return cfErrorTrigger("cfDateTimeEditorGet: '" + id + "': " +
                              __CFDATE_ERROR_TIME_EDITOR_ID);
    }
    return editor;
}

function cfDateTimeRangeListCreate(arg)
{
    return new CFDateTimeRangeList(arg.id, arg.selectBoxId, arg.textAreaId,
                                   arg.fromDateTimeEditorId,
                                   arg.fromDateTimeEditorWrapperId,
                                   arg.toDateTimeEditorId,
                                   arg.toDateTimeEditorWrapperId,
                                   arg.addButtonId, arg.removeButtonId,
                                   arg.formatString, arg.value);
}

function cfDateTimeRangeListGet(id)
{
    var list = __cfDateTimeRangeListMap[id];
    if (! list) {
        return cfErrorTrigger("cfDateTimeRangeListGet: '" + id + "': " +
                              __CFDATE_ERROR_TIME_RANGE_LIST_ID);
    }
    return list;
}
