var OU = OU || {};

OU.input = (function() {
    var groups = {},
        types = {},
        aliases = {
            checkbox: 'cb',
            textarea: 'ta',
            text: 'tx',
            radio: 'ra',
            select: 'st'
        };

    /**
     * @param {object||object[]} data
     * @param {string} data.type
     * @param {object[]} data.labels [optional]
     */
    var create = function(data) {
        createNamespace(data);

        if (Array.isArray(data)) {
            return createGroup(data);
        }
        else {
            data = Array.isArray(data) ? data[0] : data;

            if (data.type == 'radio' && !data.name) {
                throw new Error('Name required with type radio.');
            }

            return createSingle.call(types[data.type], data);
        }
    };

    var createNamespace = function(data) {
        data = Array.isArray(data) ? data : [data];

        data.forEach(function(data) {
            //check alias exists and throw error if not
            if (typeof aliases[data.type] == 'undefined') {
                throw new Error('Unsupported input type: ' + data.type);
            }

            //create namespace for type if undefined
            if (typeof types[data.type] == 'undefined') {
                types[data.type] = {
                    index: 0
                };
            }
        });
    };

    /**
     * @param {object} data
     * @param {string} data.type
     * @param {string|object|Element} data.label
     * @param {string} [data.name]
     * @param {string} [data.value]
     * @param {boolean} [data.disabled]
     * @param {boolean} [data.hidden]
     * @param {function} [data.change]
     * @param {object[]} [data.classes]
     * @param {Element} data.el
     * @param {boolean} group
     */
    var createSingle = function(data, group) {
        var tag = getTagName(data.type),
            input = document.createElement(tag),
            label = createLabel.call(this, input, data);

        this.index++;
        input.className = 'ou-input';

        if (group) {
            groups[data.name] = ++groups[data.name] || 0;
            input.setAttribute('data-input-group', data.group || (Object.keys(groups).length - 1).toString());
            input.setAttribute('data-input-group-index', groups[data.name]);
            input.setAttribute('data-input-type', data.type);
        }

        if (tag == 'input') {
            input.type = data.type;
        }

        if (data.value) {
            input.value = data.value;
        }

        if (data.name) {
            input.name = data.name;
        }

        if (data.disabled) {
            input.disabled = true;
        }

        if (data.hidden) {
            (data.label instanceof Element ? input : label).style.display = 'none';
        }

        if (Array.isArray(data.classes)) {
            (data.label instanceof Element ? input : label).className = data.classes.join(' ');
        }

        if (data.change) {
            input.addEventListener('change', data.change);
        }

        if (initSpecific[data.type]) {
            initSpecific[data.type](data, input);
        }

        data.el = input;

        //return label if newly created, otherwise the input
        return (data.label instanceof Element) ? input : label;
    };

    var initSpecific = {
        /**
         *
         * @param data
         * @param input
         * @param {boolean} [data.checked]
         */
        checkbox: function(data, input) {
            if (data.checked) {
                input.checked = data.checked;
            }
        },

        radio: function(data, input) {
            if (data.checked) {
                input.checked = data.checked;
            }
        },

        /**
         *
         * @param data
         * @param input
         * @param {object[]} [data.options]
         * @param {object[]} [data.value]
         * @param {boolean} [data.shuffle]
         * @param {boolean} [data.placeholder]
         */
        select: function(data, input) {
            var tmp = [],
                option;

            data.placeholder = (data.placeholder !== false);

            data.options.forEach(function(opt, i) {
                if (i === 0 && data.placeholder) {
                    option = document.createElement('option');
                    option.textContent = 'Select...';
                    option.selected = true;
                    option.disabled = true;
                    option.hidden = true;
                    input.appendChild(option);
                }

                option = document.createElement('option');
                tmp.push(option);

                if (opt.label) {
                    option.textContent = opt.label;
                }

                option.value = opt.value || i;

                if (opt.disabled) {
                    option.disabled = true;
                }

                if (opt.selected) {
                    option.selected = true;
                }

                if (data.shuffle) {
                    OU.utils.shuffle(tmp);
                }

                tmp.forEach(function(option) {
                    input.appendChild(option);
                });
            });

            if (data.shuffle) {
                OU.utils.shuffle(tmp);
            }

            tmp.forEach(function(option) {
                input.appendChild(option);
            });
        },

        text: function(data, input) {
            var min = (typeof data.min == 'number') ? data.min : null,
                max = (typeof data.max == 'number') ? data.max : null;

            if (data.valid == 'number') {
                input.addEventListener('input', function() {
                    this.value = this.value.replace(/[^\d]+/g, '');
                });
            }

            if (min || max) {
                data.change = function(change, e) {
                    var val = parseInt(e.target.value, 10);

                    switch (true) {
                        case isNaN(val):
                            e.target.value = min || 0;
                            break;
                        case min && val < min:
                            e.target.value = min;
                            break;
                        case max && val > max:
                            e.target.value = max;
                    }

                    if (typeof change == 'function') {
                        change.call(this, e);
                    }
                }.bind(input, data.change);
            }
        }
    };

    var createGroup = function(data) {
        var group = document.createElement('div'),
            num = Object.keys(groups).length,
            type = false;

        group.className = 'input-group';

        data.forEach(function(data, i) {
            if (i === 0) {
                type = data.type;
            }
            else {
                type = (data.type == type) ? type : false;
            }

            data.name = data.name || 'input-group' + num;
            group.appendChild(createSingle.call(types[data.type], data, true));
        });

        data.group = group;
        data.type = type;

        return group;
    };

    /**
     * @type {HTMLElement}
     * @param {HTMLElement} input
     * @param {object} data
     * @param {string|object} data.label
     * @param {string} [data.label.prefix]
     * @param {string} [data.label.suffix]
     */
    var createLabel = function(input, data) {
        if (!data.label) {
            throw new Error('Label data missing!');
        }

        var id = aliases[data.type] + this.index;
        var label, span, val;

        input.id = id;

        if (data.label instanceof Element) {
            label = data.label;
        }
        else {
            label = document.createElement('label');
            label.appendChild(input);

            if (typeof data.label == 'string') {
                label.insertAdjacentHTML('beforeend', data.label);
            }
            else {
                //stick spans around each part to help with styling later
                span = label.appendChild(document.createElement('span'));
                span.appendChild(input);

                if (data.label.prefix) {
                    label.insertAdjacentHTML('afterbegin', '<span class="label-prefix">' + data.label.prefix + '</span>');
                }
                if (data.label.suffix) {
                    label.insertAdjacentHTML('beforeend', '<span class="label-suffix">' + data.label.suffix + '</span>');
                }
            }
        }

        //wrap label in array (if not already) to work with it
        label = Array.isArray(label) ? label : [label];

        label.forEach(function(label) {
            val = label.getAttribute('for') || '';
            label.setAttribute('for', (val ? val + ' ' : '') + id);
        });

        return (label.length == 1) ? label[0] : label;
    };

    var isInput = function(type) {
        return !!aliases[type];
    };

    var getTagName = function(type) {
        return (type == 'radio' || type == 'checkbox' || type == 'text' || type == 'range') ? 'input' : type;
    };

    return {
        create: create,
        isInput: isInput
    };
})();