/* --------------------------------------------------------------
 form.js 2016-09-08
 Gambio GmbH
 http://www.gambio.de
 Copyright (c) 2016 Gambio GmbH
 Released under the GNU General Public License (Version 2)
 [http://www.gnu.org/licenses/gpl-2.0.html]
 --------------------------------------------------------------
 */

jse.libs.form = jse.libs.form || {};

/**
 * ## Form Utilities Library
 *
 * This library contains form helpers mostly required by old modules (JS Engine v1.0).
 *
 * @module JSE/Libs/forms
 * @exports jse.libs.forms
 */
(function (exports) {

    'use strict';

    /**
     * Get URL parameters.
     *
     * @param {String} url
     * @param {Boolean} deep
     *
     * @return {Object}
     */
    function _getUrlParams(url, deep) {
        url = decodeURIComponent(url || location.href);

        let splitUrl = url.split('?'),
            splitParam = (splitUrl.length > 1) ? splitUrl[1].split('&') : [],
            regex = new RegExp(/\[(.*?)\]/g),
            result = {};

        $.each(splitParam, function (i, v) {
            let keyValue = v.split('='),
                regexResult = regex.exec(keyValue[0]),
                base = null,
                basename = keyValue[0].substring(0, keyValue[0].search('\\[')),
                keys = [],
                lastKey = null;

            if (!deep || regexResult === null) {
                result[keyValue[0]] = keyValue[1].split('#')[0];
            } else {

                result[basename] = result[basename] || [];
                base = result[basename];

                do {
                    keys.push(regexResult[1]);
                    regexResult = regex.exec(keyValue[0]);
                } while (regexResult !== null);

                $.each(keys, function (i, v) {
                    let next = keys[i + 1];
                    v = v || '0';

                    if (typeof (next) === 'string') {
                        base[v] = base[v] || [];
                        base = base[v];
                    } else {
                        base[v] = base[v] || undefined;
                        lastKey = v;
                    }
                });

                if (lastKey !== null) {
                    base[lastKey] = keyValue[1];
                } else {
                    base = keyValue[1];
                }
            }

        });

        return result;
    }

    /**
     * Create Options
     *
     * Function to add options to a select field. The full dataset for each option is added at the
     * option element.
     *
     * @param {object} $destination    jQuery-object of the select field.
     * @param {json} dataset Array that contains several objects with at least a "name" and a "value" field.
     * @param {bool} addEmpty If true, an empty select option will be generated (value = -1).
     * @param {bool} order Orders the dataset by name if true.
     *
     * @public
     */
    exports.createOptions = function ($destination, dataset, addEmpty, order) {
        var markup = [];

        // Helper for sorting the dataset
        var _optionsSorter = function (a, b) {
            a = a.name.toLowerCase();
            b = b.name.toLowerCase();

            return (a < b) ? -1 : 1;
        };

        // Sort data
        dataset = order ? dataset.sort(_optionsSorter) : dataset;

        // Add an empty element if "addEmpty" is true
        if (addEmpty) {
            markup.push($('<option value="-1"> </option>'));
        }

        // Adding options to the markup
        $.each(dataset, function (index, value) {
            var $element = $('<option value="' + value.value + '">' + value.name + '</option>');
            $element.data('data', value);
            markup.push($element);
        });

        $destination.append(markup);
    };

    /**
     * Pre-fills a form by the given key value pairs in "options".
     *
     * @param {object} $form Element in which the form fields are searched.
     * @param {object} options A JSON with key-value pairs for the form fields.
     * @param {boolean} trigger A "change"-event gets triggered on the modified form field if true.
     *
     * @public
     */
    exports.prefillForm = function ($form, options, trigger) {
        $.each(options, function (index, value) {
            var $element = $form.find('[name="' + index + '"]'),
                type = null;

            if ($element.length) {
                type = $element.prop('tagName').toLowerCase();
                type = (type !== 'input') ? type : $element.attr('type').toLowerCase();

                switch (type) {
                    case 'select':
                        if (typeof value === 'object') {
                            // Case for multi-select
                            $.each(value, function (i, value) {
                                $element
                                    .find('option[value="' + value + '"]')
                                    .prop('selected', true);
                            });
                        } else {
                            // Case for single select
                            $element
                                .find('option[value="' + value + '"]')
                                .prop('selected', true);
                        }
                        break;
                    case 'checkbox':
                        $element.prop('checked', (value !== 'false') ? true : false);
                        break;
                    case 'radio':
                        $element.prop('checked', false);
                        $element.each(function () {
                            var $self = $(this);
                            if ($self.val() === value.toString()) {
                                $self.prop('checked', true);
                            }
                        });
                        break;
                    case 'textarea':
                        $element.val(value);
                        break;
                    default:
                        $element.val(value);
                        break;
                }

                if (trigger) {
                    $element.trigger('change', []);
                }
            }
        });

    };

    /**
     * Returns the data from the form fields in a jQuery advantageous JSON format
     *
     * @param {object} $form Target form selector object to be searched.
     * @param {string} ignoreSelector Selector string to be ignored.
     *
     * @return {object} Returns the data from the form elements.
     *
     * @public
     */
    exports.getData = function ($form, ignore, asJSON) {
        var $elements = $form.find('input, textarea, select'),
            result = {};

        if (ignore) {
            $elements = $elements.filter(':not(' + ignore + ')');
        }

        $elements.each(function () {
            var $self = $(this),
                type = $self.prop('tagName').toLowerCase(),
                name = $self.attr('name'),
                regex = new RegExp(/\[(.*?)\]/g),
                regexResult = regex.exec(name),
                watchdog = 5,
                $selected = null,
                res = null,
                base = null,
                lastKey = null;

            type = (type !== 'input') ? type : $self.attr('type').toLowerCase();

            if (regexResult !== null) {

                var basename = name.substring(0, name.search('\\[')),
                    keys = [];

                result[basename] = result[basename] || (asJSON ? {} : []);
                base = result[basename];

                do {
                    keys.push(regexResult[1]);
                    regexResult = regex.exec(name);
                    watchdog -= 1;
                } while (regexResult !== null || watchdog <= 0);

                $.each(keys, function (i, v) {
                    var next = keys[i + 1];
                    v = v || '0';

                    if (typeof (next) === 'string') {
                        base[v] = base[v] || (asJSON ? {} : []);
                        base = base[v];
                    } else if (type !== 'radio') {
                        v = (v && v !== '0') ? v :
                            (asJSON) ? Object.keys(base).length : base.length;
                        base[v] = base[v] || undefined;
                    }

                    lastKey = v;
                });

            }

            switch (type) {
                case 'radio':
                    res = $elements
                        .filter('input[name="' + $self.attr('name') + '"]:checked')
                        .val();
                    break;
                case 'checkbox':
                    res = ($self.prop('checked')) ? $self.val() : false;
                    break;
                case 'select':
                    $selected = $self.find(':selected');
                    if ($selected.length > 1) {
                        res = [];
                        $selected.each(function () {
                            res.push($(this).val());
                        });
                    } else {
                        res = $selected.val();
                    }
                    break;
                case 'button':
                    break;
                default:
                    if (name) {
                        res = $self.val();
                    }
                    break;
            }

            if (base !== null) {
                base[lastKey] = res;
            } else {
                result[name] = res;
            }

        });

        return result;
    };

    /**
     * Returns the form field type.
     *
     * @param {object} $element Element selector to be checked.
     *
     * @return {string} Returns the field type name of the element.
     *
     * @public
     */
    exports.getFieldType = function ($element) {
        var type = $element.prop('tagName').toLowerCase();
        return (type !== 'input') ? type : $element.attr('type').toLowerCase();
    };

    /**
     * Adds a hidden field to the provided target.
     *
     * @param {object} $target Target element to prepend the hidden field to.
     * @param {boolean} replace Should the target element be replaced?
     */
    exports.addHiddenByUrl = function ($target, replace) {
        var urlParam = _getUrlParams(null),
            $field = null,
            hiddens = '',
            update = [];

        $.each(urlParam, function (k, v) {
            if (v) {
                $field = $target.find('[name="' + k + '"]');

                if ($field.length === 0) {
                    hiddens += '<input type="hidden" name="' + k + '" value="' + v + '" />';
                } else {
                    update.push(k, v);
                }
            }
        });

        if (replace) {
            exports.prefillForm($target, update);
        }

        $target.prepend(hiddens);
    };

    /**
     * Resets the the provided target form.
     *
     * This method will clear all textfields. All radio buttons
     * and checkboxes will be unchecked, only the first checkbox and
     * radio button will get checked.
     *
     * @param {object} $target Form to reset.
     */
    exports.reset = function ($target) {
        $target
            .find('select, input, textarea')
            .each(function () {
                var $self = $(this),
                    type = exports.getFieldType($self);

                switch (type) {
                    case 'radio':
                        $target
                            .find('input[name="' + $self.attr('name') + '"]:checked')
                            .prop('checked', false)
                            .first()
                            .prop('checked', true);
                        break;
                    case 'checkbox':
                        $self.prop('checked', false);
                        break;
                    case 'select':
                        $self
                            .children()
                            .first()
                            .prop('selected', true);
                        break;
                    case 'textarea':
                        $self.val('');
                        break;
                    case 'text':
                        $self.val('');
                        break;
                    default:
                        break;
                }
            });
    };

})(jse.libs.form);