var forms, textFields, radiosFields, checkboxFields, selectFields;

var mapForms = {};
var mapFormsFields = {};
BOX.mapForms = mapForms;
BOX.mapFormsFields = mapFormsFields;

var reFieldName = /[a-zA-Z0-9][a-zA-Z0-9_-]+$/;

var mapFormsTypes = {
    'checkbox': 'checkbox',
    'hidden': 'text',
    'password': 'text',
    'radio': 'radio',
    'select-one': 'select',
    'text': 'text'
};

var formPatterns = {
    empty: /^\s*$/,
    email: /^\s*[\w-]+(\.[\w-]+)*@([\w-]+\.)+[A-Za-z]{2,7}\s*$/
};

var formRules = {
    empty: function(value) {
        return formPatterns.empty.test(value);
    },
    email: function(value) {
        return formPatterns.email.test(value);
    }
};

BOX.addFormRule = function(rule, pattern) {
    formPatterns[rule] = pattern;
    formRules[rule] = function(value) {
        return formPatterns[rule].test(value);
    };
};

var getForm = function(f) {
    var type = typeof f;
    if (type == 'string') {
        return D.getElementById(f) || D.forms[f];
    } else if (type == 'number') {
        return D.forms[f];
    } else if (f.nodeName) {
        return f;
    }
};

var hasField = function(form, fieldName) {
    return !!mapFormsFields[form.id].names[fieldName] || false;
};

var getFieldType = function(field) {
    return mapFormsTypes[field.type];
};

var getFieldEventName = function(type) {
    var evt;
    switch (type) {
        case 'text':
        case 'password':
            evt = 'blur.validation';
            break;
        case 'select-one':
            evt = 'change.validation';
            break;
        case 'radio':
        case 'checkbox':
            evt = 'click.validation';
            break;
    }
    return evt;
};

var getFieldLabel = function(f) {
    var label = $(f).next('label');
    if (!label.length) {
        label = $(f).prev('label');
        if (!label.length && f.parentNode) {
            label = getFieldLabel(f.parentNode);
        }
    }
    return label;
};

var getFieldFromEvent = function(form, name) {
    if (typeof name == 'object') {
        name = name[1];
    }
    return (mapFormsFields[form.id] && mapFormsFields[form.id].fields[name]) ? mapFormsFields[form.id].fields[name] : null;
};
BOX.getFieldFromEvent = getFieldFromEvent;

var validateField = function(e) {
    var formId = getStoreId(this, 'form');
    var name = this.name.match(reFieldName)[0];
    if (mapFormsFields[formId] && mapFormsFields[formId].fields[name]) {
        var field = mapFormsFields[formId].fields[name];
        field.setError(field.rule(field));
    }
};

var bindFieldRule = function(field) {
    var evt = getFieldEventName((field[0] || field).type);
    $(field).bind(evt, validateField);
};

var unbindFieldRule = function(field) {
    var evt = getFieldEventName((field[0] || field).type);
    $(field).unbind(evt);
};

var validateForm = function(e) {
    var id = getStoreId(this, 'form') || this.id;
    if (mapForms[id]) {
        mapForms[id].validate();
        if (!mapForms[id].isValid(true)) {
            mapForms[id].fireEvent('formError.' + id, e, mapForms[id].error);
            e.preventDefault();
        } else {
            mapForms[id].fireEvent('formValid.' + id, e);
        }
    }
};

var bindFormSubmit = function(form, submitSelector) {
    if (typeof submitSelector == 'string') {
        var el = $(submitSelector, form).addClass('box:form:' + form.id).bind('click.validation', validateForm);
        if (el[0]) {
            if (el[0].id && el[0].href && el[0].href.indexOf('__doPostBack') > -1) {
                mapForms[form.id].submitName = el[0].id.replace(/_/g, '$');
            } else if (el[0].innerHTML.indexOf('__doPostBack') > -1) {
                mapForms[form.id].submitName = el[0].innerHTML.match(/doPostBack\('([^']+)/)[1];
            }
        }
    } else {
        $(form).bind('submit.validation', validateForm);
    }
};

var unbindFormSubmit = function(form, submitSelector) {
    if (typeof submitSelector == 'string') {
        $(submitSelector, form).removeClass('box:form:' + form.id).unbind('click.validation');
    } else {
        $(form).unbind('submit.validation', validateForm);
    }
};

var clickToChangeField = function(e) {
    var formId = getStoreId(this, 'form');
    var name = this.name.match(reFieldName)[0];
    if (mapForms[formId] && mapFormsFields[formId] && mapFormsFields[formId].fields[name]) {
        var field = mapFormsFields[formId].fields[name];
        var id = this.id.match(reFieldName)[0];
        var type = field.type;
        var replaced = field.isReplaced();
        if (type == 'checkbox') {
            mapForms[formId].fireEvent('fieldChange.' + type + '.' + name);
        }
        if (field.isChecked()) {
            if (type == 'radio') {
                if (field.idChecked && field.idChecked != id) {
                    if (replaced) {
                        field.getLabel(field.idChecked)
                            .removeClass(config.fauxFields.checked)
                            .removeClass(config.fauxFields.checkedFocus);
                    }
                    mapForms[formId].fireEvent('fieldChange.' + type + '.' + name);
                }
                field.idChecked = id;
            }
            if (replaced) {
                field.getLabel(id)
                    .removeClass(config.fauxFields.focus)
                    .addClass(config.fauxFields.checkedFocus);
            }
        } else if (replaced) {
            field.getLabel()
                .removeClass(config.fauxFields.checked)
                .removeClass(config.fauxFields.checkedFocus)
                .addClass(config.fauxFields.focus);
        }
    }
};

var bindFieldClickToChange = function(field) {
    $(field).bind('click.fieldChange', clickToChangeField);
};

var unbindFieldClickToChange = function(field) {
    $(field).unbind('click.fieldChange');
};

var focusBlurField = function(e) {
    var formId = getStoreId(this, 'form');
    var name = this.name.match(reFieldName)[0];
    if (mapForms[formId] && mapFormsFields[formId] && mapFormsFields[formId].fields[name]) {
        var field = mapFormsFields[formId].fields[name];
        var id = this.id.match(reFieldName)[0];
        var type = field.type;
        var label = field.getLabel(id);
        if (e.type == 'focus') {
            if (type == 'radio' || type == 'checkbox') {
                label.addClass(field.isChecked() ? config.fauxFields.checkedFocus : config.fauxFields.focus);
            } else if (type == 'select') {
                label.addClass(config.fauxFields.focus);
            }
        } else {
            if (type == 'radio' || type == 'checkbox') {
                label.removeClass(config.fauxFields.checkedFocus).removeClass(config.fauxFields.focus);
                if (field.isChecked(id)) {
                    label.addClass(config.fauxFields.checked);
                }
            } else if (type == 'select') {
                label.removeClass(config.fauxFields.focus);
            }
        }
    }
};

var bindFieldFocusBlur = function(field) {
    $(field).bind('focus.fieldState', focusBlurField).bind('blur.fieldState', focusBlurField);
};

var unbindFieldFocusBlur = function(field) {
    $(field).unbind('focus.fieldState', focusBlurField).unbind('blur.fieldState', focusBlurField);
};

var bindFieldKeyNav = function(field, name, formId) {
    $(field)
        .bind('keyup.fieldState', function(e) {mapFormsFields[formId].fields[name].keyUp(e);})
        .bind('keydown.fieldState', function(e) {mapFormsFields[formId].fields[name].keyDown(e);});
};

var unbindFieldKeyNav = function(field, name, formId) {
    $(field).unbind('keyup.fieldState').bind('keydown.fieldState');
};

var fauxSelectRollover = function() {
    $(this).addClass(config.fauxFields.hover);
};
    
var fauxSelectRollout = function() {
    $(this).removeClass(config.fauxFields.hover);
};

var replacedSelectClick = function(e) {
    var formId = getStoreId(this, 'form');
    var name = this.id.replace('REP', '');
    if (mapForms[formId] && mapFormsFields[formId] && mapFormsFields[formId].fields[name]) {
        var field = mapFormsFields[formId].fields[name];
        if (field) {
            field.element.focus();
            if (field.opened) {
                field.closeReplaced();
            } else {
                field.openReplaced();
            }
        }
    }
};

var bindReplacedSelectClick = function(el) {
    el.click(replacedSelectClick);
};

var unbindReplacedSelectClick = function(el) {
    el.unbind('click').remove();
};

var createNewFieldObject = {
    'checkbox': function(datas) {
        return new FormCheckbox(datas);
    },
    'radio': function(datas) {
        return new FormRadios(datas);
    },
    'select': function(datas) {
        return new FormSelect(datas);
    },
    'text': function(datas) {
        return new FormText(datas);
    }
};

// Form class
var Form = function(datas) {
    Form.superclass.constructor.call(this);
    
    this.addEvents(['formError', 'formValid', 'fieldError', 'fieldValid', 'fieldChange', 'fieldReplaced']);
    
    this.initialize(datas);
};
BOX.extend(Form, BOX.Observable, {
    initialize: function(datas) {
        var that = this;
        
        that.element = datas.element;
        that.id = $(datas.element).attr('id');
        
        mapFormsFields[that.id] = {};
        mapFormsFields[that.id].fields = {};
        $('input, select, textarea', this.element).each(function() {
            if (this.name) {
                var name = this.name.match(reFieldName)[0];
                var type = getFieldType(this);
                if (createNewFieldObject[type]) {
                    if (type == 'radio') {
                        if (!mapFormsFields[that.id].fields[name]) {
                            if (that.element.nodeName.toLowerCase() == 'form') {
                                mapFormsFields[that.id].fields[name] = createNewFieldObject[type]({'fields': that.element.elements[this.name], 'name': name, 'formId': that.id});
                            } else {
                                mapFormsFields[that.id].fields[name] = createNewFieldObject[type]({'fields': D.forms[0].elements[this.name], 'name': name, 'formId': that.id});
                            }
                        }
                    } else {
                        mapFormsFields[that.id].fields[name] = createNewFieldObject[type]({'field': this, 'name': name, 'formId': that.id});
                    }
                    if (type == 'radio' || type == 'checkbox') {
                        bindFieldClickToChange(this);
                    }
                    $(this).addClass('box:form:' + that.id);
                }
            }
        });
    },
    
    getElement: function() {
        return this.element;
    },
    
    eachField: function(fn) {
        for (var name in mapFormsFields[this.id].fields) {
            if (isOwnProperty(mapFormsFields[this.id].fields, name)) {
                if (fn(mapFormsFields[this.id].fields[name]) === false) {
                    break;
                }
            }
        }
        return this;
    },
    
    checkbox: function(name) {
        if (mapFormsFields[this.id].fields[name] && mapFormsFields[this.id].fields[name].type == 'checkbox') {
            return mapFormsFields[this.id].fields[name];
        }
        return null;
    },
    
    radio: function(name) {
        if (mapFormsFields[this.id].fields[name] && mapFormsFields[this.id].fields[name].type == 'radio') {
            return mapFormsFields[this.id].fields[name];
        }
        return null;
    },
    
    select: function(name) {
        if (mapFormsFields[this.id].fields[name] && mapFormsFields[this.id].fields[name].type == 'select') {
            return mapFormsFields[this.id].fields[name];
        }
        return null;
    },
    
    text: function(name) {
        if (mapFormsFields[this.id].fields[name] && mapFormsFields[this.id].fields[name].type == 'text') {
            return mapFormsFields[this.id].fields[name];
        }
        return null;
    },
    
    isValid: function(noValidation) {
        if (!noValidation) {
            this.validate(true);
        }
        var valid = true;
        this.eachField(function(field) {
            if (field.error) {
                return (valid = false);
            }
        });
        return valid;
    },
    
    getErrors: function() {
        var i = 0, errors = {};
        this.eachField(function(field) {
            if (field.error) {
                errors[field.name] = field.error;
                ++i;
            }
        });
        return (i ? errors : null);
    },
    
    setErrors: function(errors) {
        if (typeof errors == 'object') {
            for (var name in errors) {
                if (isOwnProperty(errors, name) && mapFormsFields[this.id].fields[name]) {
                    mapFormsFields[this.id].fields[name].setError(errors[name]);
                }
            }
        }
        return this;
    },
    
    addValidation: function(fn, submitSelector) {
        var msg = fn(this);
        if (msg) {
            this.error = msg;
        }
        this.submitSelector = submitSelector;
        bindFormSubmit(this.element, submitSelector);
        return this;
    },
    
    removeValidation: function() {
        this.eachField(function(field) {
            if (field.rule) {
                unbindFieldRule(field.element || field.elements);
            }
        });
        unbindFormSubmit(this.element, this.submitSelector);
        return this;
    },
    
    validate: function(noBroadcast) {
        this.eachField(function(field) {
            if (field.rule !== undefined) {
                field.validate(noBroadcast);
            }
        });
        return this;
    },
    
    addReplacement: function(options) {
        var type;
        this.eachField(function(field) {
            if (field.addReplacement !== undefined) {
                type = field.type;
                field.addReplacement(options);
            }
        });
        return this;
    },
    
    removeReplacement: function() {
        var type;
        this.eachField(function(field) {
            if (field.removeReplacement !== undefined) {
                type = field.type;
                field.removeReplacement();
            }
        });
        return this;
    }
});

BOX.form = function(datas, rm) {
    var id = datas.id || datas;
    if (!rm) {
        if (!mapForms[id]) {
            var form = D.getElementById(id);
            if (form) {
                if (typeof datas == 'string') {
                    datas = {};
                }
                datas.element = form;
                id = $(form).attr('id');
                mapForms[id] = new Form(datas);
                return mapForms[id];
            }
        } else {
            return mapForms[id];
        }
    } else {
        if (mapForms[id]) {
            mapForms[id].removeValidation().removeReplacement();
            delete mapForms[id];
        }
    }
    return null;
};

// Field class
var FormField = function(datas) {};
BOX.extend(FormField, {
    initialize: function(datas) {
        this.element = datas.field;
        this.name = datas.name;
        this.formId = datas.formId;
    },
    
    getElement: function() {
        return (this.element || null);
    },
    
    getLabel: function() {
        return getFieldLabel(this.element);
    },
    
    getValue: function() {
        return (this.element.value ? this.element.value : null);
    },
    
    setValue: function(value) {
        if (value === undefined) {
            return (this.element.value = '');
        } else {
            return (this.element.value = value);
        }
    },
    
    isDefault: function() {
        if (typeof this.element.defaultValue != 'undefined') {
            return this.getValue() == this.element.defaultValue;
        }
        return false;
    },
    
    isDisabled: function() {
        return this.element.disabled;
    },
    
    disable: function() {
        this.element.disabled = true;
        return this;
    },
    
    enable: function() {
        this.element.disabled = false;
        return this;
    },
    
    mustValidate: function(rule) {
        this.rule = rule;
        bindFieldRule(this.element);
        return this;
    },
    
    isValid: function(noValidation) {
        if (!noValidation) {
            this.validate(true);
        }
        return !this.error;
    },
    
    validate: function(noBroadcast) {
        if (this.rule) {
            this.setError(this.rule(this), noBroadcast);
        }
        return this;
    },
    
    getError: function() {
        return (this.error || null);
    },
    
    setError: function(error, noBroadcast) {
        if (error) {
            if (!noBroadcast) {
                mapForms[this.formId].fireEvent('fieldError.' + this.type + '.' + this.name, error);
            }
            this.error = error;
        } else {
            if (!noBroadcast) {
                mapForms[this.formId].fireEvent('fieldValid.' + this.type + '.' + this.name);
            }
            this.error = null;
        }
        return this;
    }
});

// Text class
var FormText = function(datas) {
    FormText.superclass.constructor.call(this);
    
    this.initialize(datas);
};
BOX.extend(FormText, FormField, {
    type: 'text',
    
    isEmpty: function() {
        return formRules.empty(this.element.value);
    },
    
    isEqualTo: function(what) {
        return this.getValue() == what;
    },
    
    isMatching: function(what) {
        return formRules[what] ? formRules[what](this.element.value) : null;
    }
});

// Radios class
var FormRadios = function(datas) {
    FormRadios.superclass.constructor.call(this);
    
    this.initialize(datas);
};
BOX.extend(FormRadios, FormField, {
    type: 'radio',
    
    initialize: function(datas) {
        var that = this;
        that.elements = datas.fields;
        that.name = datas.name;
        that.formId = datas.formId;
        that.group = {};
        that.idChecked = null;
        that.number = datas.fields.length;
        this.each(function(field, i) {
            that.group[field.id.match(reFieldName)[0]] = i;
            if (field.checked) {
                that.idChecked = field.id.match(reFieldName)[0];
            }
        });
        return that;
    },
    
    each: function(fn) {
        var i = this.number, l = i - 1;
        if (i) {
            while (i--) {
                if (fn(this.elements[l - i], l - i)) {
                    break;
                }
            }
        }
        return this;
    },
    
    getIndex: function() {
        var index = null;
        this.each(function(field, i) {
            if (field.checked) {
                return (index = i);
            }
        });
        return index;
    },
    
    getElement: function(id) {
        var f = null;
        if (typeof id == 'string') {
            if (this.group[id] !== undefined) {
                return this.elements[this.group[id]];
            }
        } else if (typeof id == 'number') {
            if (id >= 0 && id < this.number) {
                return this.elements[id];
            }
        } else {
            this.each(function(field) {
                if (field.checked) {
                    f = field;
                }
            });
        }
        return f;
    },
    
    getElements: function() {
        return this.elements;
    },
    
    getLabel: function(id) {
        var field = this.getElement(id);
        return (field && getFieldLabel(field));
    },
    
    getLabels: function(id) {
        return getFieldLabel(this.elements);
    },
    
    getValue: function(id) {
        if (id !== undefined) {
            var field = this.getElement(id);
            return ((field && field.value) ? field.value : null);
        } else {
            var value = null;
            this.each(function(field) {
                if (field.checked) {
                    return (value = field.value);
                }
            });
            return value;
        }
    },
    
    setValue: function(value, id) {
        var done = null;
        if (id !== undefined) {
            var field = this.getElement(id);
            if (field) {
                field.value = done = value;
            }
        } else {
            this.each(function(field) {
                if (field.checked) {
                    return (field.value = done = value);
                }
            });
        }
        return done;
    },
    
    isChecked: function(id) {
        var ok = false;
        if (id !== undefined) {
            var field = this.getElement(id);
            if (field) {
                ok = this.getElement(id).checked;
            }
        } else {
            this.each(function(field) {
                if (field.checked) {
                    return (ok = true);
                }
            });
        }
        return ok;
    },
    
    check: function(id) {
        var ok = false;
        if (id !== undefined) {
            var field = this.getElement(id);
            ok = (!!field && (field.checked = true));
            if (ok && this.isReplaced()) {
                if (typeof id == 'number') {
                    id = this.elements[id].id.match(reFieldName)[0];
                }
                if (this.idChecked != id) {
                    this.getLabel(this.idChecked)
                            .removeClass(config.fauxFields.checked)
                            .removeClass(config.fauxFields.checkedFocus);
                }
                this.getLabel(id)
                    .removeClass(config.fauxFields.focus)
                    .removeClass(config.fauxFields.checkedFocus)
                    .addClass(config.fauxFields.checked);
                this.idChecked = id;
                mapForms[this.formId].fireEvent('fieldChange.radio.' + this.name);
            }
        }
        return ok;
    },
    
    uncheck: function(id) {
        var ok = false;
        if (id !== undefined) {
            var field = this.getElement(id);
            ok = (!!field && !(field.checked = false));
        } else {
            this.each(function(field) {
                field.checked = false;
            });
            ok = !!this.number;
        }
        if (ok && this.isReplaced() && this.idChecked) {
            this.getLabel(this.idChecked)
                .removeClass(config.fauxFields.focus)
                .removeClass(config.fauxFields.checkedFocus)
                .removeClass(config.fauxFields.checked);
            this.idChecked = null;
            mapForms[this.formId].fireEvent('fieldChange.radio.' + this.name);
        }
        return ok;
    },
    
    isDisabled: function() {
        return this.elements[0].disabled;
    },
    
    disable: function() {
        this.each(function(field) {
            field.disabled = true;
        });
        return this;
    },
    
    enable: function() {
        this.each(function(field) {
            field.disabled = false;
        });
        return this;
    },
    
    isReplaced: function() {
        return (this.elements[0].className.indexOf('box:mode:replaced') > -1);
    },
    
    addReplacement: function() {
        $(this.elements).addClass('box:mode:replaced');
        if (this.idChecked) {
            this.getLabel(this.idChecked).addClass(config.fauxFields.checked);
        }
        bindFieldFocusBlur(this.elements);
        mapForms[this.formId].fireEvent('fieldReplaced.radio.' + this.name);
    },
    
    removeReplacement: function() {
        $(this.elements).removeClass('box:mode:replaced');
        if (this.idChecked) {
            this.getLabel(this.idChecked)
                .removeClass(config.fauxFields.checked)
                .removeClass(config.fauxFields.checkedFocus)
                .removeClass(config.fauxFields.focus);
        }
        unbindFieldFocusBlur(this.elements);
    },
    
    mustValidate: function(rule) {
        this.rule = rule;
        this.each(function(field) {
            bindFieldRule(field);
        });
        return this;
    }
});

// Checkbox class
var FormCheckbox = function(datas) {
    FormCheckbox.superclass.constructor.call(this);
    
    this.initialize(datas);
};
BOX.extend(FormCheckbox, FormField, {
    type: 'checkbox',
    
    isChecked: function() {
        return this.element.checked;
    },
    
    check: function() {
        this.element.checked = true;
        if (this.isReplaced()) {
            this.getLabel()
                .removeClass(config.fauxFields.focus)
                .removeClass(config.fauxFields.checkedFocus)
                .addClass(config.fauxFields.checked);
            mapForms[this.formId].fireEvent('fieldChange.checkbox.' + this.name);
        }
        return true;
    },
    
    uncheck: function() {
        this.element.checked = false;
        if (this.isReplaced()) {
            this.getLabel()
                .removeClass(config.fauxFields.focus)
                .removeClass(config.fauxFields.checkedFocus)
                .removeClass(config.fauxFields.checked);
            mapForms[this.formId].fireEvent('fieldChange.checkbox.' + this.name);
        }
        return true;
    },
    
    isReplaced: function() {
        return (this.element.className.indexOf('box:mode:replaced') > -1);
    },
    
    addReplacement: function() {
        $(this.element).addClass('box:mode:replaced');
        if (this.isChecked()) {
            this.getLabel().addClass(config.fauxFields.checked);
        }
        bindFieldFocusBlur(this.element);
        mapForms[this.formId].fireEvent('fieldReplaced.checkbox.' + this.name);
        return this;
    },
    
    removeReplacement: function() {
        $(this.element).removeClass('box:mode:replaced');
        if (this.isChecked()) {
            this.getLabel()
                .removeClass(config.fauxFields.checked)
                .removeClass(config.fauxFields.checkedFocus)
                .removeClass(config.fauxFields.focus);
        }
        unbindFieldFocusBlur(this.element);
        return this;
    }
});

var fauxSelect, fauxSelectScroll, fauxSelectMask;
$(D).ready(function() {
    fauxSelect = $(config.fauxSelect.container).appendTo('body');
    fauxSelectScroll = new BOX.SimpleScroll({target: fauxSelect[0]});
    fauxSelectMask = new Mask({html: config.fauxSelect.mask});
});

// Select class
var FormSelect = function(datas) {
    FormSelect.superclass.constructor.call(this);
    
    this.initialize(datas);
};
BOX.extend(FormSelect, FormField, {
    type: 'select',
    
    initialize: function(datas) {
        this.element = datas.field;
        this.name = datas.name;
        this.formId = datas.formId;
        this.number = (datas.field && datas.field.options) ? datas.field.options.length : null;
        this.scrollTo = null;
        this.options = '';
        this.currentIndex = datas.field.selectedIndex;
        this.opened = false;
    },
    
    hasIndex: function(i) {
        return !isNaN(i) && i >= 0 && i < this.number;
    },
    
    getValue: function(i) {
        i = i || this.getIndex();
        if (this.hasIndex(i)) {
            return this.element.options[i].value ? this.element.options[i].value : null;
        }
        return null;
    },
    
    setValue: function(value, i) {
        i = i || this.getIndex();
        if (this.hasIndex(i)) {
            this.element.options[i].value = value;
            return value;
        }
        return null;
    },
    
    getIndex: function() {
        return this.element.selectedIndex;
    },
    
    setIndex: function(i) {
        if (this.hasIndex(i)) {
            this.element.selectedIndex = i;
            if (this.isReplaced()) {
                this.getReplaced('span')[0].innerHTML = this.getText(i);
                $('#' + this.name + 'Option' + this.currentIndex).removeClass(config.fauxFields.selected);
                if (this.opened) {
                    this.setReplacedOptionOnView($('#' + this.name + 'Option' + i).addClass(config.fauxFields.selected));
                }
            }
            // fireEvent change ???
            // how to detect change when no replacement ???
            // bind a classic change event and unbind on replacing ???
            if (i != this.currentIndex) {
                this.currentIndex = i;
                mapForms[this.formId].fireEvent('fieldChange.select.' + this.name);
            }
            return i;
        }
        return null;
    },
    
    getText: function(i) {
        i = i || this.getIndex();
        if (this.hasIndex(i)) {
            return this.element.options[i].text;
        }
        return null;
    },
    
    setText: function(t, i) {
        i = i || this.getIndex();
        if (this.hasIndex(i)) {
            this.element.options[i].innerHTML = t;
            if (this.isReplaced()) {
                // do stuff
            }
            return t;
        }
        return null;
    },
    
    getOption: function(i) {
        i = i || this.getIndex();
        if (this.hasIndex(i)) {
            return {'text': this.getText(i), 'value': this.getValue(i), 'selected': i == this.getIndex()};
        }
        return null;
    },
    
    setOption: function(option, i) {
        i = i || this.getIndex();
        if (this.hasIndex(i)) {
            if (typeof option == 'object') {
                this.options = this.options.replace(this.getText(i), option.text);
                this.element.option[i].value = option.value;
                this.element.option[i].text = option.text;
                if (option.selected) {
                    this.setIndex(i);
                }
            } else {
                this.options = this.options.replace('<li id="' + that.name + 'Option' + i + '">' + this.getText(i) + '</li>', '');
                this.element.option[i] = null;
            }
            return option;
        }
        return null;
    },
    
    getOptions: function() {
        var options = [], i = this.number, l = this.number - 1;
        while (i--) {
            options[l - i] = this.getOption(i);
        }
        return options;
    },
    
    setOptions: function(options, clear) {
        var that = this;
        if (typeof options == 'object') {
            if (clear) {
                that.element.options.length = 0;
                that.options = '';
            }
            var i = options.length, l = options.length - 1, opt;
            while (i--) {
                opt = options[l - i];
                if (opt.selected) {
                    this.currentIndex = i;
                }
                that.element.options[that.element.options.length] = new Option(opt.text, opt.value, opt.selected);
                that.options += '<li id="' + that.name + 'Option' + (l - i) + '">' + opt.text + '</li>';
            }
        } else {
            var sI = that.getIndex();
            $(that.element).children().each(function(i, opt) {
                if (sI == i) {
                    that.currentIndex = i;
                }
                that.options += '<li id="' + that.name + 'Option' + i + '">' + opt.text + '</li>';
            });
        }
    },
    
    addReplacement: function(options) {
        bindReplacedSelectClick($('<div id="' + this.name + 'REP" class="fauxSelect box:form:' + this.formId + '"><div><span id="' + this.name + 'REPInner">' + this.element.options[this.getIndex()].text + '</span></div></div>').insertAfter(this.element));
        $(this.element).addClass('box:mode:replaced');
        this.scrollTo = 0;
        this.options = '';
        this.setOptions();
        if (typeof options == 'object' && typeof options.maxHeight == 'number') {
            this.maxHeight = options.maxHeight;
        }
        bindFieldFocusBlur(this.element);
        bindFieldKeyNav(this.element, this.name, this.formId);
        if (BOX.ieOld) {
            $(this.element).bind('mousewheel', function(e) {
                fauxSelectScroll.wheel(e);
            });
        }
        return this;
    },
    
    removeReplacement: function() {
        $(this.element).removeClass('box:mode:replaced');
        unbindReplacedSelectClick($('#' + this.name + 'REP'));
        unbindFieldFocusBlur(this.element);
        unbindFieldKeyNav(this.element, this.name, this.formId);
        if (BOX.ieOld) {
            $(this.element).unbind('mousewheel');
        }
        return this;
    },
    
    isReplaced: function() {
        return (this.element.className.indexOf('box:mode:replaced') > -1);
    },
    
    getReplaced: function(selector) {
        return $('#' + this.name + 'REP ' + (selector || ''));
    },
    
    getReplacedOpeningPosition: function(page) {
        var el = this.getReplaced();
        var offset = el.offset();
        var totalHeight = el[0].offsetHeight;
        var realHeight = el.height();
        var borderCorrection = totalHeight - realHeight;
        // var width = el[0].offsetWidth;
        var maxHeight = 'auto';
        if (this.maxHeight) {
            maxHeight = fauxSelectScroll.sContent.outerHeight();
            if (maxHeight > this.maxHeight) {
                maxHeight = this.maxHeight;
            }
        }
        var top = offset.top + totalHeight - (borderCorrection / 2);
        if (top + maxHeight > page.viewportH + page.scrollH) {
            top = offset.top - maxHeight - borderCorrection + (borderCorrection / 2);
        }
        el = null;
        return {
            'top': top + 'px',
            'left': offset.left + 'px',
            //'width': width + 'px',
            'height': maxHeight + (maxHeight == 'auto' ? '' : 'px')
        };
    },
    
    setReplacedOptionOnView: function(item) {
        if (item && item.length && !fauxSelectScroll.disabled && item[0].offsetParent) {
            var maxHeight = fauxSelect.height();
            var fauxTop = fauxSelectScroll.getContentOffset();
            var itemTop = item[0].offsetTop;
            var itemHeight = item[0].offsetHeight;
            var itemMax = itemTop + itemHeight;
            if (itemMax > maxHeight && this.scrollTo > (maxHeight - itemMax)) {
                this.scrollTo = maxHeight - itemMax;
                fauxSelectScroll.moveContentTo(this.scrollTo);
            } else if (itemTop < - fauxTop) {
                this.scrollTo = - itemTop;
                fauxSelectScroll.moveContentTo(this.scrollTo);
            }
        }
    },
    
    clickReplaced: function() {
        if (this.isReplaced()) {
            this.element.focus();
            if (fauxSelect.hasClass(config.fauxFields.focus)) {
                this.closeReplaced();
            } else {
                this.openReplaced();
            }
        }
    },
    
    clickList: function(e, trigger) {
        var t = e.target;
        while (t != trigger) {
            if (t.nodeName.toLowerCase() == 'li') {
                var i = Number(t.id.match(/\d+$/)[0]);
                this.setIndex(i);
                this.closeReplaced();
                this.element.focus();
            }
            t = t.parentNode;
        }
    },
    
    openReplaced: function() {
        var that = this;
        if (that.isReplaced()) {
            var page = getDimensions();
            fauxSelectMask.show(null, {
                width: Math.min(page.totalW, page.viewportW) + 'px',
                height: Math.min(page.totalH, page.viewportH) + 'px'
            });
            fauxSelect.width(this.getReplaced()[0].offsetWidth); // set width before computing height
            fauxSelectScroll.sContent[0].innerHTML = '<ul>' + that.options + '</ul>';
            var current = $('#' + that.name + 'Option' + that.currentIndex).addClass(config.fauxFields.selected);
            fauxSelect.click(function(e) {that.clickList(e, this);});
            var positions = that.getReplacedOpeningPosition(page);
            fauxSelect.css(positions).addClass(config.fauxFields.focus);
            fauxSelectScroll.compute();
            if (BOX.ieOld) {
                $('li', fauxSelect).bind('mouseover.selectReplaced', fauxSelectRollover).bind('mouseout.selectReplaced', fauxSelectRollout);
            }
            that.setReplacedOptionOnView(current);
            that.opened = true;
            mapForms[that.formId].fireEvent('open.select.' + that.name);
            $(D).bind('keydown.selectReplaced', function(e) {
                if (e.which == 27) {
                    that.closeReplaced();
                }
            }).bind('mousedown.selectReplaced', function(e) {
                var t = e.target;
                if (t == fauxSelectMask.dom[0]) {
                    that.closeReplaced();
                }
            });
            current = null;
        }
    },
    
    closeReplaced: function() {
        if (this.isReplaced()) {
            fauxSelect.css('left', '-10000px').removeClass(config.fauxFields.focus);
            fauxSelect.unbind('click');
            fauxSelectMask.hide();
            this.opened = false;
            mapForms[this.formId].fireEvent('close.select.' + this.name);
            $(D).unbind('keydown.fauxSelect').unbind('mousedown.selectReplaced');
            if (BOX.ieOld) {
                $('li', fauxSelect).unbind('mouseover.selectReplaced').unbind('mouseout.selectReplaced');
            }
        }
    },
    
    keyDown: function(e) {
        if(e.which == 9) {
            this.closeReplaced();
        }
    },
    
    keyUp: function(e) {
        var k = e.which;
        if(e.altKey && (k == 38 || k == 40)) {
            this.clickReplaced();
            return;
        }
        var i = this.getIndex();
        switch (k) {
            case 13:
            case 27:
                this.setIndex(i);
                this.closeReplaced();
                break;
            case 34:
            case 35:
                this.setIndex(this.element.options.length - 1);
                break;
            case 33:
            case 36:
                this.setIndex(0);
                break;
            case 37:
            case 38:
                i = (i == this.currentIndex) ? --i : i;
                if (i < 0) {i = 0;}
                this.setIndex(i);
                break;
            case 39:
            case 40:
                i = (i == this.currentIndex) ? ++i : i;
                if (i >= this.element.options.length) {i = this.element.options.length - 1;}
                this.setIndex(i);
                break;
            default:
                this.setIndex(i);
        }
    }
});