function InputField(elm) {
    // Set attributes
    this.inputElement = elm;
    this.onBlurCallback = null;
    this.onFocusCallback = null;
    this.onKeypressCallback = null;

    // Attach events to element
    if (elm) {
        var self = this;
        elm.onblur = function(event) { return self.onBlur(self.getEvent(event)); };
        elm.onfocus = function(event) { return self.onFocus(self.getEvent(event)); };
        elm.onkeypress = function(event) { return self.onKeypress(self.getEvent(event)); };
    }
}

InputField.prototype.filter = function() {
    // Empty filter function
}

InputField.prototype.getCodeFromKeyEvent = function(event) {
    var keyCode;
    if (event.which) {
        keyCode = event.which;
    } else {
        keyCode = event.keyCode;
    }
    return keyCode;
}

InputField.prototype.getEvent = function(event) {
    if ((event == undefined) && window.event) {
        return window.event;
    }
    return event;
}

InputField.prototype.onBlur = function(event) {
    if (this.onBlurCallback) {
        this.onBlurCallback();
    }
    this.filter();
}

InputField.prototype.onFocus = function(event) {
    if (this.onFocusCallback) {
        this.onFocusCallback();
    }
}

InputField.prototype.onKeypress = function(event) {
    if (this.onKeypressCallback) {
        this.onKeypressCallback();
    }

    // Allow if the CTRL-key is pressed (special action)
    if (event.ctrlKey && !event.altKey && !event.shiftKey) {
        return true;
    }

    // Skip if this browser doesn't support catching basic controls
    // Skip if the character is not empty (which means it's not a control event)
    if ((event.charCode == undefined) || (event.charCode > 0)) {
        return false;
    }

    // Get the key code
    var code = this.getCodeFromKeyEvent(event);

    // Allow standard editing controls (movement, delete, etc)
    if ((code >= 33) && (code <= 40)) {
        return true;
    }

    // Allow backspace, tab, enter and delete
    if ((code == 8) || (code == 9) || (code == 13) || (code == 46)) {
        return true;
    }

    // Deny this action
    return false;
}

InputField.prototype.setOnBlurCallback = function(func) {
    this.onBlurCallback = func;
}

InputField.prototype.setOnFocusCallback = function(func) {
    this.onFocusCallback = func;
}

InputField.prototype.setOnKeypressCallback = function(func) {
    this.onKeypressCallback = func;
}

function IntegerField(elm, isUnsigned) {
    // Call super constructor
    InputField.call(this, elm);

    // Set attributes
    this.isUnsigned = (isUnsigned ? true : false);
}

IntegerField.extendsFrom(InputField);

IntegerField.prototype.filter = function() {
    var intValue = parseInt(this.inputElement.value, 10);
    if (isNaN(intValue)) {
        intValue = '';
    }
    this.inputElement.value = intValue;
}

IntegerField.prototype.onKeypress = function(event) {
    // Call super method
    if (InputField.prototype.onKeypress.call(this, event)) {
        return true;
    }

    // Get the key character
    var chr = String.fromCharCode(this.getCodeFromKeyEvent(event));

    // Allow 0-9
    if ((chr >= '0') && (chr <= '9')) {
        return true;
    }

    // Plus and minus handling
    if (!this.isUnsigned) {
        var value = this.inputElement.value;
        if (chr == '-') {
            if (value.indexOf('-') == -1) {
                this.inputElement.value = '-' + value;
            }
            return false;
        } else if (chr == '+') {
            if (value.charAt(0) == '-') {
                this.inputElement.value = value.substring(1, value.length);
            }
            return false;
        }
    }

    // Character not allowed
    return false;
}

function DoubleField(elm, decimalSeparator, isUnsigned) {
    // Call super constructor
    IntegerField.call(this, elm, isUnsigned);

    // Set attributes
    this.decimalSeparator = (decimalSeparator ? decimalSeparator : ',');
}

DoubleField.extendsFrom(IntegerField);

DoubleField.prototype.filter = function() {
    var value = this.inputElement.value;
    value = value.replace(this.decimalSeparator.escapeForRegex(), '.');
    value = parseFloat(value);
    if (isNaN(value)) {
        value = '';
    } else {
        value = value.format(null, this.decimalSeparator);
    }
    this.inputElement.value = value;
}

DoubleField.prototype.onKeypress = function(event) {
    // Call parent keypress
    if (IntegerField.prototype.onKeypress.call(this, event)) {
        return true;
    }

    // Get character and key code
    var code = this.getCodeFromKeyEvent(event);
    var chr = String.fromCharCode(code);

    // Accept dots and comma's
    if ((chr == '.') || (chr == ',') || (chr == this.decimalSeparator)) {
        var value = this.inputElement.value;
        if (value.indexOf(this.decimalSeparator) != -1) {
            return false;
        }
        if (chr == this.decimalSeparator) {
            return true;
        }
        document.setSelectionToText(this.inputElement, this.decimalSeparator);
        return false;
    }

    // Character not allowed
    return false;
}

function CurrencyField(elm, decimalSeparator, thousandsSeparator, isUnsigned) {
    // Call super constructor
    DoubleField.call(this, elm, decimalSeparator, isUnsigned);

    // Set attributes
    this.thousandsSeparator = (thousandsSeparator ? thousandsSeparator : '.');
}

CurrencyField.extendsFrom(DoubleField);

CurrencyField.prototype.filter = function() {
    var value = this.inputElement.value;
    value = value.replace(this.decimalSeparator.escapeForRegex(), '.');
    value = parseFloat(value);
    if (isNaN(value)) {
        value = '';
    } else {
        value = value.format(2, this.decimalSeparator, this.thousandsSeparator);
    }
    this.inputElement.value = value;
}

CurrencyField.prototype.onFocus = function(event) {
    // Call super method
    InputField.prototype.onFocus.call(this, event);

    // Remove thousands separator
    this.inputElement.value = this.inputElement.value.replace(new RegExp(this.thousandsSeparator.escapeForRegex(), 'g'), '');
}

function DateField(elm, separator) {
    // Call super constructor
    InputField.call(this, elm);

    // Set attributes
    this.separator = (separator ? separator : '-');
}

DateField.extendsFrom(InputField);

DateField.prototype.filter = function() {
    var value = this.inputElement.value;

    // Bail out if empty
    if (value == '') {
        return;
    }

    // Get the value and split it
    value = value.split(this.separator);

    // Determine day, month and year
    var day, month, year;
    day = parseInt(value[0], 10);
    month = parseInt(value[1], 10);
    year = parseInt(value[2], 10);

    // In the absence of the year and/or month, we take the first day/month
    if (!year) {
        if (!month) {
            year = day;
            month = 1;
        } else {
            year = month;
            month = day;
        }
        day = 1;
    }
    if (year < 100) {
        year += (year < 38) ? 2000 : 1900;
    }

    // Format day, month and year
    var dDate = new Date();
    dDate.setFullYear(year, month - 1, day);
    if (dDate.valueOf()) {
        day = dDate.getDate();
        month = dDate.getMonth() + 1;
        year = dDate.getFullYear();
    }
    if (day < 10) {
        day = '0' + day;
    }
    if (month < 10) {
        month = '0' + month;
    }

    // Compose year
    this.inputElement.value = day + this.separator + month + this.separator + year;
}

DateField.prototype.onKeypress = function(event) {
    // Call super method
    if (InputField.prototype.onKeypress.call(this, event)) {
        return true;
    }

    // Get key character
    var chr = String.fromCharCode(this.getCodeFromKeyEvent(event));

    // Allow 0-9
    if ((chr >= '0') && (chr <= '9')) {
        return true;
    }

    // Allow -, / and separator.
    if ((chr == '-') || (chr == '/') || (chr == this.separator)) {
        // Count the number of occurences
        var value = this.inputElement.value;
        if (value.countOccurrences(this.separator) >= 2) {
            return false;
        }
        document.setSelectionToText(this.inputElement, this.separator);
        return false;
    }

    // Character not allowed
    return false;
}

function TimeField(elm, hourSeparator, minuteSeparator) {
    // Call super constructor
    InputField.call(this, elm);

    // Set attributes
    this.hourSeparator = (hourSeparator ? hourSeparator : ':');
    this.minuteSeparator = (minuteSeparator ? minuteSeparator : '.');
}

TimeField.extendsFrom(InputField);

TimeField.prototype.filter = function() {
    var value = this.inputElement.value;
    if (value == '') {
        return;
    }

    // Determine hours, minutes and seconds
    value = value.replace(new RegExp(this.minuteSeparator.escapeForRegex(), 'g'), this.hourSeparator);
    var tmp = value.split(this.hourSeparator);
    var hours = parseInt(tmp[0], 10);
    var minutes = parseInt(tmp[1], 10);
    var seconds = parseInt(tmp[2], 10);

    // Normalize
    minutes = (minutes ? minutes : 0);
    seconds = (seconds ? seconds : 0);
    if (seconds >= 60) {
        minutes += Math.floor(seconds / 60);
        seconds %= 60;
    }
    if (minutes >= 60) {
        hours += Math.floor(minutes / 60);
        minutes %= 60;
    }

    // Format
    if (minutes < 10) {
        minutes = '0' + minutes;
    }
    if (seconds < 10) {
        seconds = '0' + seconds;
    }
    this.inputElement.value = hours + this.hourSeparator + minutes + this.minuteSeparator + seconds;
}

TimeField.prototype.onKeypress = function(event) {
    // Call super method
    if (InputField.prototype.onKeypress.call(this, event)) {
        return true;
    }

    // Get key character
    var chr = String.fromCharCode(this.getCodeFromKeyEvent(event));

    // Allow 0-9
    if ((chr >= '0') && (chr <= '9')) {
        return true;
    }

    // Allow the separators once
    var value = this.inputElement.value;
    var numSeps = value.countOccurrences(this.hourSeparator);
    if (this.minuteSeparator != this.hourSeparator) {
        numSeps += value.countOccurrences(this.minuteSeparator);
    }
    if ((chr == ':') || (chr == this.hourSeparator)) {
        if (numSeps >= 2) {
            return false;
        }
        document.setSelectionToText(this.inputElement, this.hourSeparator);
        return false;
    }
    if ((chr == '.') || (chr == this.minuteSeparator)) {
        if (numSeps >= 2) {
            return false;
        }
        document.setSelectionToText(this.inputElement, this.minuteSeparator);
        return false;
    }

    // Character not allowed
    return false;
}

function IntervalField(elm) {
    // Call super constructor
    TimeField.call(this, elm);
}

IntervalField.extendsFrom(TimeField);

function TextField(elm) {
    // Call super constructor
    InputField.call(this, elm);
}

TextField.extendsFrom(InputField);

TextField.prototype.onKeypress = function(event) {
    // Call super method (we don't need to check the return value, see below)
    InputField.prototype.onKeypress.call(this, event);

    // Because this is a text field, we allow all characters
    return true;
}