APP.zIndex = 100;

SmartSelect = Class.create(Abstract, {
	initialize: function (element, options) {
        this.element    = $(element);

        this.options    = Object.extend({
            className:          'smart-select',
            valueConstraint:    false,
            onSelect:           false,
            onMatch:            false,
            onNoMatch:          false,
            highlightClassName: 'highlight'
        }, options || {});

        this.replacement = null;
        this.values      = this.element.select('option').map(function (option) {
            return {
                value:    option.value,
                text:     option.innerHTML,
                selected: option.selected
            }
        });

        if (!this.options.defaultValue) {
            this.options.defaultValue = this.values.first();
        }

        this.valueItems     = null;
        this.highlightIndex = 0;

        this.replace();

        this.trigger.observe('click', this.toggle.bind(this));

        if (!this.options.valueConstraint) {
            this.text.observe('focus', (function () {
                this.text.select();
                this.show();
            }).bind(this)).observe('keyup', (function (event) {
                event.stop();
                switch (event.keyCode) {
                    case Event.KEY_RETURN:
                        this.selectHighlighted();
                        return;
                    break;

                    case Event.KEY_ESC:
                        this.hide();
                        this.text.blur();
                        return;
                    break;

                    case Event.KEY_UP:
                    case Event.KEY_DOWN:
                    case Event.KEY_LEFT:
                    case Event.KEY_RIGHT:
                    case Event.KEY_HOME:
                    case Event.KEY_END:
                    case Event.KEY_TAB:
                        event.stop();
                        return;
                    break;
                }

                this.search();
            }).bind(this)).observe('keydown', (function (event) {
                switch (event.keyCode) {
                    case Event.KEY_RETURN:
                        event.stop();
                    break;
                    case Event.KEY_UP:
                        event.stop();
                        this.highlightUp();
                        return;
                    break;

                    case Event.KEY_DOWN:
                        event.stop();
                        this.highlightDown();
                        return;
                    break;
                }
            }).bind(this));
        } else {
            this.text.observe('click', this.toggle.bind(this));
        }

        // close on click outside of this.element
        $(document.getElementsByTagName("body").item(0)).observe('click', (function (event) {
            var element = event.element();

            if (element) {
                if (element == this.element || element.descendantOf(this.element)) {
                    event.stop();
                } else {
                    this.hide();
                }
            } else {
                this.hide();
            }
        }).bind(this));
	},

    replace: function () {
        this.replacement = new Element('div', {className: this.options.className});

        this.value = new Element('input', {
            type:       'text',
            name:       this.element.name,
            id:         this.element.id,
            style:      'display: none;',
            value:      0,
            tabIndex:   "999"
        });

        this.text = null;
        var selected  = this.values.find(function (value) { return value.selected == true; });
        if (!selected) {
            selected = this.options.defaultValue;
        }
        if (this.options.valueConstraint) {
            this.text = new Element('input', {readonly: true});
            this.text.value = selected.text;
        } else {
            this.text = new Element('input');
            this.text.value = selected.text;
        }

        this.trigger     = new Element('a', {href: 'javascript:', className: (this.options.className + '-trigger'), tabindex: "999"});
        this.placeholder = new Element('ul');

        [this.value, this.trigger, this.text, this.placeholder].each((function (element) {
            this.replacement.insert(element);
        }).bind(this));
        this.placeholder.hide();

        Element.replace(this.element, this.replacement);

        this.element = this.replacement;

        if (selected) {
            this.selectValue(selected.value, selected.text, true);
        }

        // TODO: pin element to replacement position + decrement z-index gradually
        this.element.setStyle({zIndex: APP.zIndex});
        APP.zIndex = APP.zIndex - 1;
//        this.element.absolutize();
    },

    filterValues: function (match) {
        this.placeholder.update();

        var matches = [];
        if (match && match.length > 0) {
            matches = this.values.findAll(function (value) {
                return value.text.toLowerCase().indexOf(match.toLowerCase()) != -1;
            });
        } else {
            matches = this.values;
        }

        if (matches && matches.size() > 0) {
            var template = new Template('<li><a href="javascript:" title="#{text}" rel="#{value}">#{text}</a></li>');
            matches.each((function (match) {
                this.placeholder.insert(template.evaluate(match));
            }).bind(this));

            // TODO: Excel-like autocomplete
            this.setValue(matches.first().value/*, matches.first().text*/);

            this.valueItems = this.placeholder.select('a');
            this.valueItems.last().addClassName('last');

            this.valueItems.each((function (elm) {
                elm.observe('click', (function (event) {
                    event.stop();
                    this.selectValue(elm.rel, elm.innerHTML);

                // highlight effect on hover
                }).bind(this)).observe('mouseover', (function () { this.valueItems.invoke('removeClassName', this.options.highlightClassName); elm.addClassName(this.options.highlightClassName);
                }).bind(this)).observe('mouseout', (function () { elm.removeClassName(this.options.highlightClassName); }).bind(this));
            }).bind(this));

            this.placeholder.show();
            this.highlightIndex = 0;
            this.highlight();

            // user function
            if (this.options.onMatch) {
                this.options.onMatch(match);
            }
        } else {
            // TODO: remove custom (set -> no value when not matching)
            this.setValue(0);

            // user function
            if (this.options.onNoMatch) {
                this.options.onNoMatch(match);
            }

            this.hide();
        }
    },

    highlightUp: function () {
        this.highlightIndex--;
        if (this.highlightIndex == -1) {
            this.highlightIndex = this.valueItems.size() - 1;
        }
        this.highlight();
    },

    highlightDown: function () {
        this.highlightIndex++;
        if (this.highlightIndex == this.valueItems.size()) {
            this.highlightIndex = 0;
        }
        this.highlight();
    },

    highlight: function () {
        this.valueItems.invoke('removeClassName', this.options.highlightClassName);
        if (this.valueItems[this.highlightIndex]) {
            this.valueItems[this.highlightIndex].addClassName(this.options.highlightClassName);
        }
    },

    selectHighlighted: function () {
        var selected = this.values.find((function (value) {
            return value.value == this.valueItems[this.highlightIndex].rel;
        }).bind(this));

        this.selectValue(selected.value, selected.text);
    },

    selectValue: function (value, text, bypassCallback) {
        if (!value || parseInt(value) == 0) {
            text = this.options.defaultValue.text;
        }

        this.setValue(value, text);

        this.hide();
//        this.text.blur();

        // user function
        if (this.options.onSelect && !bypassCallback) {
            this.options.onSelect(value, text);
        }
    },

    setValue: function (value, text) {
        this.value.value = value;
        if (text) {
//            if (this.options.valueConstraint) {
//                this.text.update(text);
//            } else {
                this.text.value = text;
//            }
        }
    },

    search: function () {
        this.filterValues(this.text.value);
    },

    setValues: function (values) {
        this.values = values;
    },

    toggle: function () {
        if (this.placeholder.visible()) {
            this.hide();
        } else {
            this.show();
        }
    },

    show: function () {
        this.placeholder.show();
        this.filterValues();
    },

    hide: function () {
        this.placeholder.hide().update();
    }
});