(function($){

    var frs = this;
    
        frs.Form = frs.Node.extend({
            init: function(el, options) {
                this.options = $.extend(true, {}, this.options, options);
                this._super(el);

                this.enabled = true;
                this.isValid = true;
                this.fields = new frs.FieldCollection($(this.options.fieldsSelector, this.$el), frs.Field, this);
                this.submitButton = new frs.Node($('button[type=submit]', this.$el));
                this.loaderInfo = new frs.Node($('#loader-info', this.$el));

                // Kill me now... IE7 needs to be kicked so that it doesn't lose the button elements text.
                if (frs.isIE7) {
                    this.submitButton.$el.children('span').hide().show();
                }
            },
        options: {
            fieldsSelector: 'fieldset > ol > li',
            fieldParent: 'fieldset > ol',
            fieldContainer: 'li',
            errorClass: 'error',
            formMode: 'full',
            compactErrPos: 'left',
            widgets: {}
        },
        events: {
            submit: function(e) {
                this.validate();
                if (!frs.utils.elementSupportsAttribute('input', 'placeholder')) this.clearPlaceHolders();
                return (this.enabled && this.isValid);
            },
            loading: function() {
                this.submitButton = this.submitButton.$el.replaceWith(frs.loader);
                this.loader = $('.actions img', this.$el);
                this.loader.css({
                    'float': 'right',
                    'margin': '-4px 0'
                });
                this.loaderInfo.$el.show();
            }
        },
        validate: function() {
            this.emptyErrors();
            for (var field in this.fields.items) {
                this.fields.items[field].validate();
            }
            if (this.isValid != true) this.renderErrors();
        },
        renderErrors: function() {
            var data = {
                'errorClass': this.options.errorClass,
                'action': 'Korjaa'
            };
            // Form errors removed. Need better placement on compact forms
            // this.$el.before(ich.formError(data));
        },
        emptyErrors: function() {
            this.isValid = true;
            this.$el.prev('.msg').remove();
        },
        enable: function() {
            this.enabled = true;
            $('button[type=submit]', this.$el).attr('disabled', false);
        },
        disable: function() {
            this.enabled = false;
            this.submitButton.$el.attr('disabled', true);
        },
        clearPlaceHolders: function() {
            // Clears placeholder data for browsers that don't support it
            var inputs = $('input', this.$el);
            for (var i = 0, len = inputs.length; i < len; i++) {
                var placeholder = inputs[i].getAttribute('placeholder');
                if (!!placeholder && inputs[i].value === placeholder) {
                    inputs[i].value = '';
                }
            }
        }
    });
    
    frs.FieldCollection = Class.extend({
        init: function(collection, node, form, options) {
            if (!collection || !node || !form) throw new Error('Collection expects parameters collection, node and form.');
            var self = this;
            this.options = $.extend(true, {}, this.options, options);
            
            this.length = 0;
            this.items = {};
            
            // Filter out nested fields
            this.form = form;
            this.selector = collection.selector;
            this.collection = collection.not(collection.has(this.form.options.fieldsSelector).find(this.form.options.fieldsSelector));

            $.each(this.collection, function(key, item) {
                // Nested fieldset
                if ($(item).has(self.form.options.fieldsSelector).length) {
                    self._push(new frs.FieldCollection($(self.form.options.fieldsSelector, $(item)), node, self.form, self.options.nodeOptions || null), item);
                } else {
                    self._push(new node($(item), self.form, self.options.nodeOptions || null));
                }
            });
        },
        _push: function(node, container) {
            if (arguments.length < 1) throw new Error('Collection.add expects argument node, which should of type frs.Node.');
            var index;
            
            if (node instanceof frs.FieldCollection) {
                index = (arguments.length > 1) ? $(container).closest('fieldset').attr('id') || this.length : this.length;
            } else {
                index = (arguments.length > 1) ?
                    arguments[1]
                :
                    node.el.id ||
                    node.widget.el.id ||
                    node.widget.$el.attr('name') ||
                    this.length
                ;
            }
            
            this.items[index] = node;
            this.items[index].uid = index;
            this.length++;
            return this.items[index];
        },
        _refreshCollection: function() {
            this.collection = this.options.container ? $(this.selector).children() : $(this.selector);
        },
        add: function(node) {
            if (arguments.length < 1) throw new Error('Collection.add expects argument node, which should of type frs.Node.');
            var item = this._push(node).$el;
            item.appendTo($(this.form.options.fieldParent, this.form.$el));
            this._refreshCollection();
        },
        del: function(id) {
            var id = arguments.length ? id : this.collection[this.collection.length-1].id || this.collection.length-1;
            this.items[id].$el.remove();
            delete this.items[id];
            this.length--;
            this._refreshCollection();
        },
        empty: function() {
            for (var key in this.items) {
                this.del(key);
            }
        },
        validate: function() {
            for (var field in this.items) {
                this.items[field].validate();
            }
        }
    });
        
    frs.Field = frs.Node.extend({
        init: function(el, form, options) {
            this.options = $.extend(true, this.options, options);
            this._super(el);
            
            this.errors = [];

            this.form = form;
            var wgt = this.getWidgetType();
            this.label = new frs.Node(this.$el.children('label').first());
            this.widget = new wgt.type(wgt.selector, this.form, this);
        },
        validate: function() {
            // Empty errors
            this.errors = [];

            this.$el.children('.msg').remove();
            // Check for empty val
            if (this.widget.required && !!!(this.widget.val())) {
                this.errors.push('Tämä kenttä vaaditaan');
            }
            // Validate pattern
            if (!!this.widget.val() && !!this.widget.pattern && !this.widget.pattern.test(this.widget.val())) {
                this.errors.push('Tarkista syöttämäsi tieto.');
            }
            
            if (this.errors.length) {
                this.form.isValid = false;
                this.renderErrors();
            }
        },
        renderErrors: function() {
            var data = {
                'errorClass': this.form.options.errorClass,
                'compactErrPos': this.form.options.compactErrPos,
                'errors': this.errors
            };
            this.errorDisplay = ich.fieldError(data);
            this.$el.prepend(this.errorDisplay);
            if (this.form.options.formMode == 'compact') {
                var left = parseInt(this.$el.position().left, 10) - parseInt(this.errorDisplay.outerWidth(), 10) - 10;
                if (this.form.options.compactErrPos == 'right') {
                    left = parseInt(this.$el.position().left, 10) + parseInt(this.$el.outerWidth(), 10) + 10;
                }
                this.errorDisplay.css({
                    display: 'none',
                    left: left,
                    top: this.$el.position().top
                });
            }
            this.errorDisplay.show('fast');
        },
        getWidgetType: function() {
            var type = this.options.widget || frs.Widget,
                widget = this.$el.find('select, a.select, input, textarea').first(),
                selector = widget,
                key = widget[0].id || widget.attr('name');

            if (widget[0].getAttribute('type') === "checkbox") type = frs.CheckBox;
            if (widget[0].getAttribute('type') === "radio") type = frs.Radio;

            // Select fields
            if (widget[0].tagName.toLowerCase() == 'select' || (widget[0].tagName.toLowerCase() == 'a' && widget[0].getAttribute('class') == 'select')) {
                type = frs.Dropdown;
                selector = this.$el.children('a.select')
            }

            // Date fields
            if (widget[0].getAttribute('data-widget') == 'datepicker') type = frs.DatePicker;
            
            // Autocomplete
            if (widget[0].getAttribute('data-widget') == 'autocomplete') type = frs.AutoComplete;


            // Widget overrides
            if (this.form.options.widgets[key]) {
                type = this.form.options.widgets[key];
//                selector = this.$el.children(this.form.options.widgets[key].selector)
            }

            return {
                type: type,
                selector: selector
            };
        },
        // Convenience method for calling this.widget.val()
        val: function(val) {
            return this.widget.val(val);
        }
    });
    
    frs.Widget = frs.Node.extend({
        init: function(el, form, field, options) {
            this.options = $.extend(true, this.options, options);
            this._super(el);
            
            this.form = form;
            this.field = field;
            this.type = this.el.getAttribute('type');
            
            this.required = !!this.el.getAttribute('required');
            this.hasCustomValidity = frs.utils.isMethodSupported(this.el, 'setCustomValidity');

            var pattern = this.$el.attr('pattern');
            this.pattern = new RegExp((!!pattern) ? pattern : '', 'i');
            
            this.placeholderSupported = frs.utils.elementSupportsAttribute(this.el.tagName, 'placeholder');
            this.placeholder = this.$el.attr('placeholder');
            
            if (!this.placeholderSupported && !!this.placeholder) {
                var value = this.$el.attr('value');
                if (value) {
                    this.$el.val(value);
                } else {
                    this.$el.val(this.$el.attr('placeholder')).css('color', '#aaa');
                }
            }
        },
        events: {
            blur: function() {
                // this.field.validate(); // Removed validation on blur. Too aggressive
                if (this.hasCustomValidity) this.validate();

                if (!this.placeholderSupported && !!this.placeholder) {
                    if ($.trim(this.$el.val()) == '') this.$el.val(this.placeholder).css('color', '#aaa');
                }
            },
            focus: function() {
                if (this.hasCustomValidity) this.validate();
                if (!this.placeholderSupported && !!this.placeholder) {
                    if ($.trim(this.$el.val()) == this.placeholder) this.$el.val('').css('color', '#333');
                }
            },
            input: function() { if (this.hasCustomValidity) this.validate(); },
            invalid: function() {
                if (this.el.validity.valueMissing) this.el.setCustomValidity('Tämä kenttä vaaditaan');
                if (this.el.validity.typeMismatch && this.type === "email") this.el.setCustomValidity('Tarkista sähköpostiosoite');
                if (this.el.validity.typeMismatch && this.type === "url") this.el.setCustomValidity('Tarkista URL');
                if (this.el.validity.patternMismatch) this.el.setCustomValidity('Tarkista syöte');
           }
        },
        validate: function() {
            this.el.setCustomValidity('');
            this.el.checkValidity();
        },
        val: function(val) {
            var v = $.trim(val) || null;
            if (v) { this.$el.val(v); }
            var ret = $.trim(this.$el.val());
            return (!this.placeholderSupported && this.placeholder) ? (ret == this.placeholder) ? '' : ret : ret;
        }
    });

    frs.CheckBox = frs.Widget.extend({
        init: function(el, form, field, options) {
            this.options = $.extend(true, {}, this.options, options);
            this._super(el, form, field, options);

            if (frs.isIE8) {
                var self = this;
                this.pseudoEl = this.field.$el.children('b');
                this.pseudoEl.click(function() {
                    self.el.checked = !self.el.checked;
                    self.$el.trigger('change');
                });
            }
        },
        events: {
            change: function() {
                this.field.$el[(this.el.checked) ? 'addClass' : 'removeClass']('checked');
                if (frs.isIE8) {
                    if (this.el.checked) {
                        this.pseudoEl.css('background-position', '-167px -11px');
                    } else {
                        this.pseudoEl.css('background-position', '-199px -11px');
                    }
                }
            },
            focus: function() { this.field.$el.addClass('active'); },
            blur: function() { this.field.$el.removeClass('active'); }
        }
    });

    frs.Radio = frs.Widget.extend({
        init: function(el, form, field, options) {
            this.options = $.extend(true, {}, this.options, options);
            this._super(el, form, field, options);

            this.opts = $('input[name='+this.el.getAttribute('name')+']').parent();
        },
        events: {
            change: function() {
                this.opts.removeClass('checked');
                if (frs.isIE7) this.opts.children('b').css('background-position', '-201px -27px');
                if (this.el.checked) this.field.$el.addClass('checked');
                if (frs.isIE7 && this.el.checked) this.field.$el.children('b').css('background-position', '-184px -27px');
            },
            focus: function() {
                this.field.$el.addClass('active');
                if (frs.isIE7) this.field.$el.children('b').css('background-position', (this.el.checked) ? '-184px -27px' : '-218px -27px');
                this.$el.trigger('change'); // Safari doesn't trigger change correctly, patching it here
            },
            blur: function() {
                this.field.$el.removeClass('active');
                if (frs.isIE7) this.field.$el.children('b').css('background-position', (this.el.checked) ? '-167px -27px' : '-201px -27px');
            }
        }
    });

    frs.DatePicker = frs.Widget.extend({
        init: function(el, form, field, options) {
            this.options = $.extend(true, this.options, options);
            this._super(el, form, field);
            
            this.options.buttonImage = this.form.options.mediaDir+'/img/v3_sprite.png';
            if (this.$el.val()) this.options.defaultDate = this.$el.val();
            if (this.$el.attr('min')) this.options.minDate = this.$el.attr('min');
            if (this.$el.attr('max')) this.options.maxDate = this.$el.attr('max');
            
            this.$el.datepicker(this.options);
        },
        options: {
            showWeek: true,
            firstDay: 1,
            showOn: 'both',
            constrainInput: true,
            dateFormat: 'dd.mm.yy',
            hideIfNoPrevNext: true,
            showOtherMonths: true,
            selectOtherMonths: true,
            onSelect: function() { $(this).data('set', true); }
        }
    });
    
    frs.AutoComplete = frs.Widget.extend({
        init: function(el, form, field, options) {
            this.options = $.extend(true, {}, this.options, options);
            this._super(el, form, field);

            this.options.target = $('#' + this.$el.attr('data-target'));
            this.options.self = this;
            this.$el.autocomplete(this.options);
        },
        options: {
            focus: function(e, ui) {
                $(e.target).val(ui.item.label);
                return false;
            },
            select: function(e, ui) {
                var valField = $('#' + $(e.target).attr('data-target'));
                $(e.target).val(ui.item.label);
                valField.val(ui.item.value);
                valField.data('set', true);
                return false;
            },
            source: function(request, response) {
                var self = this.options.self;
                var re = $.ui.autocomplete.escapeRegex(request.term);
                var matcher = new RegExp(re, "i");
                var src = frs[self.$el.attr('data-src')];

                response($.grep(src, function(value, index) {
                  value = value.label || value.value || value;
                  return matcher.test(value) || matcher.test(self.normalize(value));
                }));
            }
        },
        events: {
            'keypress.frs': function(e) {
                if (!!!this.options.target.data('set') && (e.keyCode > 47 || e.keyCode == 46 || e.keyCode == 8)) this.options.target.val('');
            }
        },
        accentMap: {
            "â": "a",
            "ã": "a",
            "á": "a",
            "à": "a",
            "æ": "ae",
            "ç": "c",
            "é": "e",
            "è": "e",
            "ê": "e",
            "ë": "e",
            "ì": "i",
            "í": "i",
            "î": "i",
            "ï": "i",
            "ñ": "n",
            "ò": "o",
            "ó": "o",
            "ô": "o",
            "õ": "o",
            "ø": "o",
            "š": "s",
            "ù": "u",
            "ú": "u",
            "û": "u",
            "ü": "u",
            "ý": "y",
            "ÿ": "y",
            "ž": "z",
            "'": " "
        },
        normalize: function(term) {
            var ret = "";
            for (var i = 0; i < term.length; i++) {
                ret += this.accentMap[term.charAt(i)] || term.charAt(i);
            }
            return ret;
        }
    });

    frs.Dropdown = frs.Widget.extend({
        init: function(el, form, field, options) {
            this.options = $.extend(true, {}, this.options, options);
            this._super(el, form, field, this.options);

            this.selectedIndex = 0;
            this.opened = false;

            this.enabled = true;

            this.t = ''; // Type timer
            this.c = ''; // Typed character

            this.$el.css('display', 'block');

            // Remove unnecessary select
            this.field.$el.children('select').remove();

            this.required = !!this.el.getAttribute('data-required');

            this.input = new frs.Node(this.field.$el.children('input[data-value-field]'));
            this.input.$el.attr('name', this.input.$el.attr('data-name'));

            var listType = (this.options.listType) ? this.options.listType : frs.DropdownList,
                listItemType = (this.options.listItemType) ? this.options.listItemType : frs.DropdownListItem;

            this.list = new listType(this.field.$el.children('ul.select-list'), this, { listItemType: listItemType });

            if (frs.isIE7 && this.$el.children('b').length == 0) this.$el.prepend('<b />');
        },
        val: function(val, data) {
            if (!!val && !!data) {
                this.input.$el.val(val);
                this.$el.html(data);
                if (frs.isIE7 && this.$el.children('b').length == 0) this.$el.prepend('<b />');
                this.$el.change();
            }
            return this.input.$el.val();
        },
        setByVal: function(val) {
            if (!!val) {
                for (var i in this.list.items.items) {
                    if (this.list.items.items[i].val() == val) {
                        this.list.items.items[i].$el.trigger('click');
                        break;
                    }
                }
            }
        },
        events: {
            blur: function() {
                if (this.field.errors.length > 0) this.field.validate();
            },
            click: function(e) {
                e.preventDefault();
                e.stopPropagation();
                if (this.enabled) this.list[(this.list.opened) ? 'hide' : 'show']();
            },
            keydown: function(e) {
                var selectedIndex = this.selectedIndex;

				switch( e.keyCode ) {
                    case 9: // tab
                        break;
					/*case 8: // backspace
                        break;*/
					case 27: // esc
                        this.$el.click(); // Closes options
                        break;
					case 13: // enter
                        if (this.list.opened) {
                            this.list.items.items[selectedIndex].$el.trigger('click', true);
                        }
                        break;
					case 37: // left
					case 38: // up
                        e.preventDefault();
                        e.stopPropagation();
                        if(selectedIndex > 0) {
                            --selectedIndex;
                            if (this.list.opened) {
                                this.list.items.collection.removeClass('selected');
                                this.list.items.items[selectedIndex].$el.addClass('selected');
                                this.list.items.items[selectedIndex].$el.trigger('click', false);
                                this.list.$el.scrollTop(selectedIndex * 23);
                            } else {
                                this.list.items.items[selectedIndex].$el.click();
                            }
                        }
                        break;
					case 39: // right
					case 40: // down
                        e.preventDefault();
                        e.stopPropagation();
                        ++selectedIndex;
                        if(selectedIndex < this.list.items.length) {
                            if (this.list.opened) {
                                this.list.items.collection.removeClass('selected');
                                this.list.items.items[selectedIndex].$el.addClass('selected');
                                this.list.items.items[selectedIndex].$el.trigger('click', false);
                                this.list.$el.scrollTop(selectedIndex * 23);
                            } else {
                                this.list.items.items[selectedIndex].$el.click();
                            }
                        }
                        break;
				}
            },
            keypress: function(e) {

                switch(e.keyCode) {

					case 9: // tab
					case 27: // esc
					case 13: // enter
					case 38: // up
					case 37: // left
					case 40: // down
					case 39: // right
						break;

					default: // Type to find
                        var self = this;
                        e.preventDefault();
                        e.stopPropagation();

                        // if( !control.hasClass('selectBox-menuShowing') ) showMenu(select);

                        clearTimeout(this.t);

                        this.c += String.fromCharCode(e.charCode || e.keyCode);

                        for (var i = 0; i < this.list.items.length; ++i) {
                            if (this.list.items.items[i].$el.text().substr(0, this.c.length).toLowerCase() === this.c.toLowerCase() ) {
                                this.list.items.collection.removeClass('selected');
                                this.list.items.items[i].$el.addClass('selected');
                                this.list.items.items[i].$el.trigger('click', false);
                                this.list.$el.scrollTop(i * 23);
                                break;
                            }
                        }

                        this.t = setTimeout(function() { self.c = ''; }, 1000);
                }
            }
        },
        fill: function(itemList) {
            this.list.$el.empty();
            delete this.list.items;
            for (var key in itemList) {
                this.list.$el.append(ich.dropdownOption({value: key, name: itemList[key].name}));
            }
            this.list.items = new frs.Collection(this.list.$el.children(), frs.DropdownListItem, { nodeOptions: { dropdown: this } });
        },
        enable: function() {
            this.enabled = true;
            this.$el.removeAttr('disabled');
            for (var item in this.list.items.items) {
                this.list.items.items[item].enable();
            }
        },
        disable: function() {
            this.enabled = false;
            this.$el.attr('disabled', 'disabled');
            for (var item in this.list.items.items) {
                this.list.items.items[item].disable();
            }
        },
        getItemByVal: function(val) {
            for (var i in this.list.items.items) {
                if (this.list.items.items[i].val() === val) return this.list.items.items[i];
            }
            return false;
        }
    });
    frs.DropdownList = frs.Node.extend({
        init: function(el, dropdown, options) {
            this.options = $.extend(true, {}, this.options, options);
            this._super(el, this.options);

            this.dropdown = dropdown;
            this.items = new frs.Collection(this.$el.children(), this.options.listItemType, { nodeOptions: { dropdown: this.dropdown } });

            this.$el.css('width', this.dropdown.$el.width()+'px');

            for (var i in this.items.items) {
                if (this.items.items[i].val() == this.dropdown.input.$el.val()) this.dropdown.selectedIndex = parseInt(i, 10);
            }
        },
        options: {
            scrollToSelected: true
        },
        events: {
            mousedown: function(e) {
                e.stopPropagation();
                if (!$(e.target).parents().andSelf().hasClass('select-list')) e.data.self.hide();
            }
        },
        show: function() {
            $(document).bind('mousedown.dropdown', {self:this}, this.events.mousedown);

            this.$el.appendTo($('body'));

            this.$el.css({
                'width': (this.dropdown.$el.outerWidth()-4)+'px',
                'height': (frs.isMobile && !frs.isiOS5) ? 'auto' : (this.items.length > 15) ? (23 * 15)+'px' : 'auto',
                'top': this.dropdown.$el.offset().top+this.dropdown.$el.outerHeight()+'px',
                'left': this.dropdown.$el.offset().left+'px'
            });

            this.items.collection.removeClass('selected');
            this.items.items[this.dropdown.selectedIndex].$el.addClass('selected');

            this.$el.show();

            if (this.items.length > 15 && this.options.scrollToSelected) this.$el.scrollTop(this.dropdown.selectedIndex * 23);

            this.opened = true;
        },
        hide: function() {
            $(document).unbind('mousedown.dropdown');

            this.$el.hide();
            this.opened = false;
        }
    });
    frs.DropdownListItem = frs.Node.extend({
        init: function(el, options) {
            this.options = $.extend(true, {}, this.options, options);
            this._super(el, this.options);
            this.disabled = false;
        },
        events: {
            click: function(e) {
                e.stopPropagation();

                if (this.disabled) return false;

                if (this.val() != this.options.dropdown.val()) {
                    this.options.dropdown.selectedIndex = parseInt(this.index, 10);
                    this.options.dropdown.val(this.val(), this.$el.html());
                }
                if (arguments.length < 2 || (arguments.length > 1 && arguments[1] === true)) {
                    this.options.dropdown.list.hide();
                }
            },
            focus: function () {
                this.$el.trigger('mouseenter');
            },
            blur: function () {
                this.$el.trigger('mouseleave');
            }
        },
        val: function() {
            return this.$el.attr('data-value');
        },
        enable: function() {
            this.$el.removeAttr('disabled');
            this.disabled = false;
        },
        disable: function() {
            this.$el.attr('disabled', 'disabled');
            this.disabled = true;
        }
    });
    
}).call(frs, jQuery);

