/**
 * @author Rafael Ostertag <rafael.ostertag@math.uzh.ch>
 */


/* global $ */
/* @depend Utils.js */

var QfqNS = QfqNS || {};

(function (n) {
    'use strict';

    /**
     * @name addFields
     * @function
     * @global
     * @deprecated use appendTemplate().
     *
     */
    n.addFields = function (templateSelector, targetSelector, maxLines) {
        n.appendTemplate(templateSelector, targetSelector, maxLines);
    };

    /**
     * Append a child created from a template to a target. All occurrences of `%d` in attribute values will be replaced
     * by the number of children of target.
     *
     * @name appendTemplate
     * @public
     * @global
     * @function
     *
     * @param {string} templateSelector jQuery selector uniquely identifying the template.
     * @param {string} targetSelector jQuery selector uniquely identifying the target container.
     * @param {number} maxChildren do not allow more than `maxChildren` of children in target.
     *
     */
    n.appendTemplate = function (templateSelector, targetSelector, maxChildren) {
        var responsibleButton;
        var escapedTemplateSelector = n.escapeJqueryIdSelector(templateSelector);
        var escapedTargetSelector = n.escapeJqueryIdSelector(targetSelector);

        var $template = $(escapedTemplateSelector);

        var $target = $(escapedTargetSelector);

        var lines = n.countLines(escapedTargetSelector) + 1;

        if (lines >= maxChildren + 1) {
            return;
        }

        var deserializedTemplate = n.deserializeTemplateAndRetainPlaceholders($template.text());
        n.expandRetainedPlaceholders(deserializedTemplate, lines);

        // Store the button object, so we can easily access it when this `line` is removed by the user
        responsibleButton = n.getResponsibleButtonFromTarget($target);
        if (responsibleButton) {
            deserializedTemplate.data('field.template.addButton', responsibleButton);
        }

        $target.append(deserializedTemplate);
        n.informFormOfChange($target);

        lines = n.countLines(escapedTargetSelector);
        if (lines >= maxChildren && responsibleButton) {
            n.disableButton(responsibleButton);
        }
    };

    n.getResponsibleButtonFromTarget = function ($target) {
        var $button;
        var buttonSelector = $target.data('qfq-line-add-button');
        if (!buttonSelector) {
            return null;
        }

        $button = $(buttonSelector);
        if ($button.length === 0) {
            return null;
        }

        return $button[0];
    };

    n.disableButton = function (button) {
        $(button).attr('disabled', 'disabled');
    };

    n.enableButton = function (button) {
        $(button).removeAttr('disabled');
    };

    n.informFormOfChange = function ($sourceOfChange) {
        var $enclosingForm = $sourceOfChange.closest("form");
        $enclosingForm.change();
    };

    /**
     * @name initializeFields
     * @global
     * @function
     * @deprecated use initializeTemplateTarget()
     */
    n.initializeFields = function (element, templateSelectorData) {
        n.initializeTemplateTarget(element, templateSelectorData);
    };

    /**
     * When the template target contains no children, it initializes the target element by appending the first child
     * created from the template.
     *
     * @name initializeTemplateTarget
     * @global
     * @function
     *
     * @param {HTMLElement} element the target HTMLElement to be initialized
     * @param {string} [templateSelectorData=qfq-line-template] name of the `data-` attribute containing the jQuery
     * selector
     * selecting the template
     */
    n.initializeTemplateTarget = function (element, templateSelectorData) {
        var responsibleButton;
        var $element = $(element);
        var templateSelector, escapedTemplateSelector, $template, deserializedTemplate;

        templateSelector = $element.data(templateSelectorData || 'qfq-line-template');
        escapedTemplateSelector = n.escapeJqueryIdSelector(templateSelector);

        $template = $(escapedTemplateSelector);

        if ($element.children().length > 0) {
            n.setPlaceholderRetainers($template.text(), element);
            return;
        }

        deserializedTemplate = n.deserializeTemplateAndRetainPlaceholders($template.text());
        n.expandRetainedPlaceholders(deserializedTemplate, 1);

        deserializedTemplate.find('.qfq-delete-button').remove();
        $element.append(deserializedTemplate);
    };

    /**
     * @name removeFields
     * @global
     * @function
     * @deprecated use removeThisChild()
     */
    n.removeFields = function (target) {
        n.removeThisChild(target);
    };

    /**
     * Remove the element having a class `qfq-line`. Uses `eventTarget` as start point for determining the closest
     * element.
     *
     * @name removeFields
     * @global
     * @function
     * @param {HTMLElement} eventTarget start point for determining the closest `.qfq-line`.
     */
    n.removeThisChild = function (eventTarget) {
        var $line = $(eventTarget).closest('.qfq-line');
        var $container = $line.parent();
        var buttonToEnable = $line.data('field.template.addButton');

        $line.remove();

        n.reExpandLineByLine($container);
        n.informFormOfChange($container);
        if (buttonToEnable) {
            n.enableButton(buttonToEnable);
        }
    };

    /**
     * Takes a template as string and deserializes it into DOM. Any attributes having a value containing `%d` will be
     *
     * @private
     * @param template
     *
     */
    n.deserializeTemplateAndRetainPlaceholders = function (template) {
        var $deserializedTemplate = $(template);

        $deserializedTemplate.find("*").each(function () {
            var $element = $(this);
            $.each(this.attributes, function () {
                if (this.value.indexOf('%d') !== -1) {
                    $element.data(this.name, this.value);
                }
            });

            if (n.isTextRetainable($element)) {
                $element.data('element-text', $element.text());
            }
        });

        return $deserializedTemplate;
    };


    /**
     * Set placeholder retainer on existing lines
     * @private
     * @param template
     * @param element container element
     */
    n.setPlaceholderRetainers = function (template, element) {
        var responsibleButton;
        var $deserializedTemplate = $(template);

        var $flatTemplate = $deserializedTemplate.find('*');

        var $element = $(element);

        var $childrenOfElement = $element.children();

        responsibleButton = n.getResponsibleButtonFromTarget($element);

        $childrenOfElement.each(function () {
            var $this = $(this);

            // Add button reference
            $this.data('field.template.addButton', responsibleButton);

            // We use .find() to increase chances of $flatTemplate and $childrenOfChild having the same ordering.
            var $childrenOfChild = $this.find('*');
            $childrenOfChild.each(function (childIndex) {
                var correspondingTemplateElement, $correspondingTemplateElement, $child;

                if (childIndex >= ($flatTemplate.length - 1)) {
                    // the current child element has no corresponding element in the template, so no use of trying to
                    // copy over retaining information.
                    return;
                }

                $child = $(this);

                correspondingTemplateElement = $flatTemplate[childIndex];
                $correspondingTemplateElement = $(correspondingTemplateElement);

                // Create the retainers on the child for each child
                $.each(correspondingTemplateElement.attributes, function () {
                    if (this.value.indexOf('%d') !== -1) {
                        $child.data(this.name, this.value);
                    }
                });

                if (n.isTextRetainable($correspondingTemplateElement)) {
                    $child.data('element-text', $correspondingTemplateElement.text());
                }
            });
        });
    };

    /**
     * @private
     */
    n.expandRetainedPlaceholders = function ($elements, value) {
        $elements.find('*').each(function () {
            var $element = $(this);
            $.each(this.attributes, function () {
                var retainedPlaceholder = $element.data(this.name);
                if (retainedPlaceholder) {
                    this.value = n.replacePlaceholder(retainedPlaceholder, value);
                }
            });

            if (n.hasRetainedText($element)) {
                $element.text(n.replacePlaceholder($element.data('element-text'), value));
            }

        });
    };

    /**
     * @private
     * @param $element
     * @returns {*}
     */
    n.isTextRetainable = function ($element) {
        return $element.is("label") && $element.text().indexOf('%d') !== -1 && $element.children().length === 0;
    };

    /**
     * @private
     * @param $element
     * @returns {boolean}
     */
    n.hasRetainedText = function ($element) {
        return $element.data('element-text') !== undefined;
    };

    /**
     * @private
     * @param targetSelector
     * @returns {jQuery}
     */
    n.countLines = function (targetSelector) {
        var escapedTargetSelector = n.escapeJqueryIdSelector(targetSelector);
        return $(targetSelector).children().length;
    };

    /**
     * @private
     */
    n.replacePlaceholder = function (haystack, by) {
        return haystack.replace(/%d/g, by);
    };

    /**
     * @private
     * @param $container
     */
    n.reExpandLineByLine = function ($container) {
        $container.children().each(function (index) {
            var $element = $(this);
            n.expandRetainedPlaceholders($element, index + 1);
        });
    };
})(QfqNS);