/**
 * Copyright (c) 2010 Maxim Vasiliev
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @author Maxim Vasiliev
 * Date: 09.09.2010
 * Time: 19:02:33
 */


(function (root, factory) {
    if (typeof exports !== 'undefined' && typeof module !== 'undefined' && module.exports) {
        // NodeJS
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(factory);
    } else {
        // Browser globals
        root.form2js = factory();
    }
}(this, function () {
    "use strict";

    /**
     * Returns form values represented as Javascript object
     * "name" attribute defines structure of resulting object
     *
     * @param rootNode {Element|String} root form element (or it's id) or array of root elements
     * @param skipEmpty {Boolean} should skip empty text values, defaults to true
     * @param delimiter {String} structure parts delimiter defaults to '.'
     * @param nodeCallback {Function} custom function to get node value
     * @param useIdIfEmptyName {Boolean} if true value of id attribute of field will be used if name of field is empty
     */
    function form2js(rootNode, delimiter = "[]", skipEmpty = false, nodeCallback, useIdIfEmptyName = true) {

        rootNode = typeof rootNode == 'string' ? document.getElementById(rootNode) : rootNode;

        let formValues = [],
            currNode,
            i = 0;

        /* If rootNode is array - combine values */
        if (rootNode.constructor === Array || (typeof NodeList != "undefined" && rootNode.constructor === NodeList)) {
            while (currNode = rootNode[i++]) {
                formValues = formValues.concat(currNode.serializeArray());
            }
        } else {
            formValues = rootNode.serializeArray()
        }

        return ProcessNameValues(formValues, skipEmpty, delimiter);
    }

    function getFormValues(rootNode, nodeCallback, useIdIfEmptyName, getDisabled) {
        const result = extractNodeValues(rootNode, nodeCallback, useIdIfEmptyName, getDisabled);
        return result.length > 0 ? result : getSubFormValues(rootNode, nodeCallback, useIdIfEmptyName, getDisabled);
    }

    function getSubFormValues(rootNode, nodeCallback, useIdIfEmptyName, getDisabled) {
        let result = [],
            currentNode = rootNode.firstChild;

        while (currentNode) {
            result = result.concat(extractNodeValues(currentNode, nodeCallback, useIdIfEmptyName, getDisabled));
            currentNode = currentNode.nextSibling;
        }

        return result;
    }

    function extractNodeValues(node, nodeCallback, useIdIfEmptyName, getDisabled) {
        if (node.disabled && !getDisabled) return [];

        let callbackResult, fieldValue, result, fieldName = getFieldName(node, useIdIfEmptyName);

        callbackResult = nodeCallback && nodeCallback(node);

        if (callbackResult && callbackResult.name) {
            result = [callbackResult];
        } else if (fieldName != '' && node.nodeName.match(/INPUT|TEXTAREA/i)) {
            fieldValue = getFieldValue(node, getDisabled);
            if (null === fieldValue) {
                result = [];
            } else {
                result = [{name: fieldName, value: fieldValue}];
            }
        } else if (fieldName != '' && node.nodeName.match(/SELECT/i)) {
            fieldValue = getFieldValue(node, getDisabled);
            result = [{name: fieldName.replace(/\[\]$/, ''), value: fieldValue}];
        } else {
            result = getSubFormValues(node, nodeCallback, useIdIfEmptyName, getDisabled);
        }

        return result;
    }

    function getFieldName(node, useIdIfEmptyName) {
        if (node.name && node.name != '') return node.name;
        else if (useIdIfEmptyName && node.id && node.id != '') return node.id;
        else return '';
    }


    function getFieldValue(fieldNode, getDisabled) {
        if (fieldNode.disabled && !getDisabled) return null;

        switch (fieldNode.nodeName) {
            case 'INPUT':
            case 'TEXTAREA':
                switch (fieldNode.type.toLowerCase()) {
                    case 'radio':
                        if (fieldNode.checked && fieldNode.value === "false") return false;
                        break;
                    case 'checkbox':
                        if (fieldNode.checked && fieldNode.value === "true") return true;
                        if (!fieldNode.checked && fieldNode.value === "true") return false;
                        if (fieldNode.checked) return fieldNode.value;
                        break;

                    case 'button':
                    case 'reset':
                    case 'submit':
                    case 'image':
                        return '';

                    default:
                        return fieldNode.value;
                }
                break;

            case 'SELECT':
                return getSelectedOptionValue(fieldNode);

            default:
                break;
        }

        return null;
    }

    function getSelectedOptionValue(selectNode) {
        let multiple = selectNode.multiple,
            result = [],
            options,
            i, l;

        if (!multiple) return selectNode.value;

        for (options = selectNode.getElementsByTagName("option"), i = 0, l = options.length; i < l; i++) {
            if (options[i].selected) result.push(options[i].value);
        }

        return result;
    }

    return form2js;

}));
