(function(paw){
    function PawInput($elt, fieldId, data, def, onChange){
        this.$elt = $elt;
        this.fieldId = fieldId;
        this.data = data;
        this.wrap = paw.utils.value.isSet(def) && paw.utils.value.isFunction(def.wrap)? def.wrap : function(uval){
            return uval;
        };
        this.unwrap = paw.utils.value.isSet(def) && paw.utils.value.isFunction(def.unwrap)? def.unwrap : function(wval){
            return wval;
        };
        this.mappedValue = paw.utils.value.isSet(def) && paw.utils.value.isFunction(def.mappedValue)? def.mappedValue : function(val){
            return val;
        };
        this.mappedLabel = paw.utils.value.isSet(def) && paw.utils.value.isFunction(def.mappedLabel)? def.mappedLabel : function(val){
            return val;
        };
        this.onChange = onChange;
        this.def = def;
        this.isBinded = false;
        this.components = {
            switch: null
        };

        this.inputTypeEvent = {
            'text': 'keyup',
            'radio': 'change',
            'checkbox': 'change',
            'switch': 'change',
            'select': 'change',
            'typeahead': 'change',
            'textarea': 'change'
        };
        this.inputType = this.determineInputType();

        this.bootstrapComponent();
        this.bind();
    }

    PawInput.prototype.bind = function(){
        if(this.isBinded){
            return;
        }
        var self = this;
        var value = this.data[this.fieldId];
        switch(this.inputType){
            case 'radio':
                if(this.unwrap(this.$elt.val()) === value){
                    this.$elt.prop('checked', true);
                }
                else{
                    this.$elt.prop('checked', false);
                }
                break;
            case 'checkbox':
            case 'switch':
                this.$elt.prop('checked', value);
                break;
            case 'select':
                if(paw.utils.value.isSet(this.def) && paw.utils.value.isSet(this.def.options)){
                    if(paw.utils.value.isDefined(this.def.defaultValue) && 
                        paw.utils.value.isDefined(this.def.defaultLabel)){
                        self.$elt.append('<option value="' + this.def.defaultValue + '">' + this.def.defaultLabel + '</option>');
                    }
                    paw.utils.iterable.iterate(this.def.options, function(option){
                        var display = self.mappedLabel(option);
                        var value = self.mappedValue(option);
                        self.$elt.append('<option value="' + value + '">' + display + '</option>');
                    });
                    break;
                }
                console.warn("Can't bind select, no options given.");
                break;
            default:
                this.$elt.val(this.wrap(value));
                break;
        }
        this.changeFn = this.compileChangeFn(this.inputType, this.onChange);
        this.$elt.on(this.inputTypeEvent[this.inputType], this.changeFn);
        this.isBinded = true;
    };

    PawInput.prototype.unbind = function(){
        this.$elt.off(this.inputTypeEvent[this.inputType], this.changeFn);

        if (paw.utils.value.isSet(this.components.switch)) {
            this.components.switch.destroy();
        }
    };

    PawInput.prototype.determineInputType = function(){
        switch(this.$elt.prop('tagName')){
            case 'INPUT': {
                if (this.$elt.hasClass('js-switch')) {
                    return 'switch';
                }
                if (this.$elt.hasClass('typeahead_3')) {
                    return 'typeahead';
                }
                return this.$elt.attr('type').toLowerCase();
            }
            default: {
                return this.$elt.prop('tagName').toLowerCase();
            }
        }
    };

    PawInput.prototype.compileChangeFn = function(type, onChange){
        var self = this;
        switch(type){
            case 'checkbox':
            case 'switch':
                return function (e) {
                    return self.updateValue(self.$elt.is(':checked'), onChange, e);
                };
            default:
                return function (e) {
                    return self.updateValue(self.$elt.val(), onChange, e);
                };
        }
    };

    PawInput.prototype.bootstrapComponent = function () {
        switch (this.inputType) {
            case 'switch':
                this.components.switch = new Switchery(this.$elt[0], { size: 'small' });
                break;
            case 'typeahead':
                initializePartnerTypeahead(this.def.url, this.$elt, this.$elt, this.def.onSelected);
                break;
        }
    };

    PawInput.prototype.updateValue = function (value, onChange, e) {
        var newValue = this.unwrap(value);
        var oldValue = this.data[this.fieldId];
        if (newValue === oldValue) {
            return;
        }
        this.data[this.fieldId] = newValue;
        if (paw.utils.value.isFunction(onChange)) {
            return onChange(e, this.data[this.fieldId]);
        }
    };

    PawInput.prototype.focus = function () {
        this.$elt.focus();
    };

    paw.com.Input = PawInput;
})(paw);