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

var __CFSPINNER_ERROR_ID = "invalid CFSpinner id";
var __CFSPINNER_ERROR_MIN_MAX = "minValue is greater than maxValue";
var __CFSPINNER_ERROR_REQUIRED = "a value is required";
var __CFSPINNER_ERROR_VALUE = "invalid CFSpinner value";

var __CFSPINNER_STATE_IDLE = 0;
var __CFSPINNER_STATE_MOUSE_DECREMENT = 1;
var __CFSPINNER_STATE_MOUSE_INCREMENT = 2;

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

var __cfSpinnerMap = {};

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

function CFSpinner(id, textBoxId, decrementImageId, incrementImageId, value,
                   minValue, maxValue, required, addPadding, blankValue)
{
    if (minValue > maxValue) {
        return cfErrorTrigger("CFSpinner: " + __CFSPINNER_ERROR_MIN_MAX);
    }
    CFWidget.call(this, id);
    var decrementImage = cfElementGet(decrementImageId);
    var incrementImage = cfElementGet(incrementImageId);
    var textBox = cfElementGet(textBoxId);
    this.__addPadding = addPadding;
    this.__blankValue = blankValue;
    this.__maxLength = textBox.maxLength;
    this.__maxValue = maxValue;
    this.__minValue = minValue;
    this.__required = required;
    this.__state = __CFSPINNER_STATE_IDLE;
    this.__textBox = textBox;
    this.setValue(value);
    __cfSpinnerMap[id] = this;
    var f = cfEventHandlerCreate(this.__spin.bind(this));
    cfMouseAddOnMouseHoldCallback(f);
    f = cfEventHandlerCreate(this.__stopSpinMouse.bind(this));
    cfMouseAddOnMouseUpCallback(f);
    f = cfEventHandlerCreate(this.__handleBlur.bind(this));
    textBox.onblur = f;
    f = cfEventHandlerCreate(this.__startSpinMouseIncrement.bind(this));
    incrementImage.onmousedown = f;
    f = cfEventHandlerCreate(this.__startSpinMouseDecrement.bind(this));
    decrementImage.onmousedown = f;
    cfElementRemoveClass(decrementImage, CFELEMENT_HIDDEN_CLASS);
    cfElementRemoveClass(incrementImage, CFELEMENT_HIDDEN_CLASS);
}

CFSpinner.extendClasses(CFWidget);

CFSpinner.prototype.__handleBlur = function()
{
    var textBox = this.__textBox;
    var textValue = textBox.value.toString().strip();
    var value = this.__value;
    if (! textValue) {
        if (this.__required) {
            this.setValue(value);
            return cfErrorTrigger("CFSpinner::__handleBlur: " +
                                  __CFSPINNER_ERROR_REQUIRED);
        }
        this.setValue(undefined);
    } else {
        var n = new Number(textValue);
        if (isNaN(n)) {
            if ((! this.__required) &&
                (textValue == this.__blankValue)) {
                this.setValue(undefined);
            } else {
                this.setValue(value);
                return cfErrorTrigger("CFSpinner::__handleBlur: '" +
                                      textValue + "': " +
                                      __CFSPINNER_ERROR_VALUE);
            }
        } else {
            this.setValue(parseInt(n.toString()));
        }
    }
    this.__triggerEvent(CFWIDGET_EVENT_USER_UPDATE);
    return true;
}

CFSpinner.prototype.__handleSpin = function()
{
    var minValue = this.__minValue;
    var value = this.__value;
    switch (this.__state) {
    case __CFSPINNER_STATE_MOUSE_DECREMENT:
        if ((typeof(value) == "undefined") || (value <= minValue)) {
            value = this.__required ? minValue : undefined;
        } else {
            value--;
        }
        break;
    case __CFSPINNER_STATE_MOUSE_INCREMENT:
        if (typeof(value) == "undefined") {
            value = minValue;
        } else {
            value++;
        }
        break;
    default:
        return;
    }
    this.setValue(value);
    this.__triggerEvent(CFWIDGET_EVENT_USER_UPDATE);
}

CFSpinner.prototype.__spin = function()
{
    if (this.__state != __CFSPINNER_STATE_IDLE) {
        this.__handleSpin();
        return false;
    }
}

CFSpinner.prototype.__startSpinMouseDecrement = function()
{
    this.__state = __CFSPINNER_STATE_MOUSE_DECREMENT;
    this.__handleSpin();
    return false;
}

CFSpinner.prototype.__startSpinMouseIncrement = function()
{
    this.__state = __CFSPINNER_STATE_MOUSE_INCREMENT;
    this.__handleSpin();
    return false;
}

CFSpinner.prototype.__stopSpinMouse = function()
{
    var state = this.__state;
    if ((state == __CFSPINNER_STATE_MOUSE_DECREMENT) ||
        (state == __CFSPINNER_STATE_MOUSE_INCREMENT)) {
        this.__state = __CFSPINNER_STATE_IDLE;
        return false;
    }
}

CFSpinner.prototype.disable = function()
{
    var textBox = this.__textBox;
    textBox.disabled = true;
    textBox.readOnly = true;
    CFWidget.prototype.disable.call(this);
}

CFSpinner.prototype.enable = function()
{
    var textBox = this.__textBox;
    textBox.disabled = false;
    textBox.readOnly = false;
    CFWidget.prototype.enable.call(this);
}

CFSpinner.prototype.getName = function()
{
    return this.__textBox.name;
}

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

CFSpinner.prototype.setName = function(name)
{
    this.__textBox.name = name;
}

CFSpinner.prototype.setValue = function(value)
{
    var textBox = this.__textBox;
    if ((! value) && (value != 0)) {
        if (this.__required) {
            return cfErrorTrigger("CFSpinner::setValue: " +
                                  __CFSPINNER_ERROR_REQUIRED);
        }
        this.__value = undefined;
        textBox.value = this.__blankValue;
    } else {
        var n = new Number(value);
        if (n === Number.NaN) {
            return cfErrorTrigger("CFSpinner::setValue: '" + value.toString() +
                                  "': " + __CFSPINNER_ERROR_VALUE);
        }
        value = parseInt(n.toString());
        value = Math.min(Math.max(this.__minValue, value), this.__maxValue);
        this.__value = value;
        value = value.toString();
        if (this.__addPadding) {
            value = value.padLeft('0', this.__maxLength);
        }
        textBox.value = value;
    }
}

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

function cfSpinnerCreate(arg)
{
    return new CFSpinner(arg.id, arg.textBoxId, arg.decrementImageId,
                         arg.incrementImageId, arg.value, arg.minValue,
                         arg.maxValue, arg.required, arg.addPadding,
                         arg.blankValue);
}

function cfSpinnerGet(id)
{
    var spinner = __cfSpinnerMap[id];
    if (! spinner) {
        return cfErrorTrigger("cfSpinnerGet: '" + id + "': " +
                              __CFSPINNER_ERROR_ID);
    }
    return spinner;
}
