(function(paw){
    function PawInput($elt, fieldId, data, def, onChange){
        this.$elt = $elt;
        this.fieldId = fieldId;
        this.data = data;

        this.onChange = onChange;
        this.def = def;
        this.isBinded = false;
        this.components = {
            switch: null,
            datepicker: null
        };

        this.inputTypeEvent = {
            'text': paw.utils.value.isSet(def) && paw.utils.value.isSet(def.event)? def.event : 'keyup',
            'radio': 'change',
            'checkbox': 'change',
            'switch': 'change',
            'select': 'change',
            'typeahead': 'change',
            'textarea': 'change',
            'select2withAPI': 'select2:select select2:unselecting',
            "datepicker": 'dp.change'
        };

        this.inputType = this.determineInputType();

        this.produceWrapperUnwrapper();

        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.bootstrapComponent();
        this.bind();
    }

    PawInput.prototype.bind = function(){
        if(this.isBinded){
            return;
        }
        this.refreshValue();
        this.changeFn = this.compileChangeFn(this.inputType, this.onChange);
        this.$elt.on(this.inputTypeEvent[this.inputType] || 'change', this.changeFn);

        if(paw.utils.value.isSet(this.components.datepicker)){
            this.$elt.on(this.components.datepicker.manualInput.id, this.components.datepicker.manualInput.fn);
        }

        this.isBinded = true;
    };

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

        if (paw.utils.value.isSet(this.components.switch)) {
            this.components.switch.destroy();
        }
        if(paw.utils.value.isSet(this.components.datepicker)){
            this.$elt.off(this.components.datepicker.manualInput.id, this.components.datepicker.manualInput.fn);
        }
        if(paw.utils.value.isSet(this.components.pawSelectpicker)){
            this.pawSelectpicker.destroy();
        }
    };

    PawInput.prototype.refreshValue = function(){
        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;
            case 'select2withAPI':
                if(!paw.utils.value.isSet(this.$elt.attr('multiple'))){
                    var wvalue = this.wrap(value);
                    this.$elt.val(wvalue);
                    if(paw.utils.value.isSet(value)){
                        this.$elt.append(new Option(value.text, value.id, true, true))
                    }
                }
                else{
                    this.$elt.val(value);
                    if(paw.utils.value.isSet(value)){
                        paw.utils.iterable.iterate(value, function(val){
                            self.$elt.append(new Option(val, val, true, true))
                        });                        
                    }
                    paw.helper.select2MultiselectHelper.renderSelectedItems(this.$elt);
                }
                
                this.$elt.trigger('change');
                break;
            case 'pawSelectpicker':
            case 'selectpicker':
                this.$elt.val(this.wrap(value));
                this.$elt.selectpicker('refresh');
                break;
            default:
                this.$elt.val(this.wrap(value));
                break;
        }
    };

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

    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';
                }
                if(this.$elt.hasClass('custom-datepicker')){
                    return 'datepicker';
                }
                return this.$elt.attr('type').toLowerCase();
            }
            case 'SELECT': {
                if(this.$elt.hasClass('select2withAPI')){
                    return 'select2withAPI';
                }
                if(this.$elt.hasClass('paw-selectpicker')){
                    return 'pawSelectpicker';
                }
                if(this.$elt.hasClass('selectpicker')){
                    return 'selectpicker';
                }
            }
            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);
                };
            case 'select2withAPI':
                return function (e) {
                    switch(e.type){
                        case 'select2:select':
                            return self.updateValue(
                                paw.utils.value.isSet(self.$elt.attr('multiple'))? self.$elt.val() : e.params.data,
                                onChange, 
                                e);
                        case 'select2:unselecting':
                            var val = self.updateValue(null, onChange, e);
                            self.$elt.select2("val", "");
                            e.preventDefault();
                            return val;
                    }                    
                };
            case 'datepicker':
                return function(e){
                    self.components.datepicker.changeDebouncer(e, onChange);
                }
            default:
                return function (e) {
                    return self.updateValue(self.$elt.val(), onChange, e);
                };
        }
    };

    PawInput.prototype.produceWrapperUnwrapper = function(){
        switch(this.inputType){
            case 'select2withAPI':
                if(!paw.utils.value.isSet(this.$elt.attr('multiple'))){
                    this.wrap = paw.utils.value.isSet(this.def) && paw.utils.value.isFunction(this.def.wrap)? this.def.wrap : function(val){
                        return paw.utils.value.isSet(val)? val.id : '';
                    };
                    this.unwrap = paw.utils.value.isSet(this.def) && paw.utils.value.isFunction(this.def.unwrap)? this.def.unwrap : function(val){
                        return paw.utils.value.isSet(val)? {
                            id: val.id,
                            text: val.text,
                            selected: val.selected
                        } : null;
                    };
                }   
                else{
                    this.wrap = paw.utils.value.isSet(this.def) && paw.utils.value.isFunction(this.def.wrap)? this.def.wrap : function(val){
                        return JSON.stringify(val);
                    };
                    this.unwrap = paw.utils.value.isSet(this.def) && paw.utils.value.isFunction(this.def.unwrap)? this.def.unwrap : function(val){
                        return val;
                    };
                }
                break;             
            default:
                this.wrap = paw.utils.value.isSet(this.def) && paw.utils.value.isFunction(this.def.wrap)? this.def.wrap : function(uval){
                    return uval;
                };
                this.unwrap = paw.utils.value.isSet(this.def) && paw.utils.value.isFunction(this.def.unwrap)? this.def.unwrap : function(wval){
                    return wval;
                };
                break;
        }
    };

    PawInput.prototype.bootstrapComponent = function () {
        var self = this;
        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;
            case 'select2withAPI':
                initializeSelect2WithAPI(
                    this.$elt[0], 
                    this.$elt.data('allow-clear'), 
                    this.$elt.data('tags'), 
                    false);
                var $select2 = this.$elt.data('select2');
                this.$elt.off('select2:unselecting');
                $select2.$dropdown.addClass('paw-select-container').addClass('paw-select2-container');
                break;
            case 'selectpicker':
                paw.com.input.PawInputSelectpickerComponent.initSelectpicker(this.$elt, this.def.settings);
                break;
            case 'pawSelectpicker':
                this.components.pawSelectpicker = new paw.com.input.PawInputSelectpickerComponent(this.$elt, this.def.settings, this.components.selectpicker)

                // delay initialization so bootstrap doesn't override selectpicker plugin...
                paw.utils.async.delay().then(function(){
                    self.components.pawSelectpicker.init();
                });
                break;
            case 'datepicker':
                $(this.def.settings.appendTo).css('position', 'relative');
                this.$elt.datetimepicker({
                    format: this.def.settings.format,
                    locale: this.def.settings.locale,
                    widgetParent: this.def.settings.appendTo,
                    minDate: this.def.settings.minDate,
                    maxDate: this.def.settings.maxDate,
                    keyBinds: {
                        left: null,
                        right: null,
                        up: null,
                        down: null,
                    }
                });
                this.components.datepicker = {
                    manualInput: {
                        id: 'input',
                        fn: function(e){
                            var val = $(this).val();
                            self.components.datepicker.manualInput.rawInput = val;
                            var date = moment(val, self.def.settings.format, true);
                            if (date.isValid() && date.year() > 999) {   
                                self.$elt.datetimepicker('date', date);
                            }
                        },
                        rawInput: null
                    },
                    changeDebouncer: debouncer(function(e, onChange){
                        var val = self.$elt.val();
                        console.log();
                        if(!self.isValueChanged(val)){
                            return;
                        }
                        var date = moment(val, self.def.settings.format, true);
                        if (date.isValid() && date.year() > 999) {   
                            self.$elt.datetimepicker('hide', date);
                            return self.updateValue(val, onChange, e);
                        }
                        e.preventDefault();
                        e.stopImmediatePropagation();
                        
                    }, 500)
                };
                break;
        }
    };

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

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

    PawInput.prototype.isValueChanged = function(value){
        var newValue = this.wrap(value);
        var oldValue = this.wrap(this.data[this.fieldId]);
        return newValue !== oldValue;
    };

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