/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2010, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.gwt.elemento.core;

import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.Widget;
import elemental.client.Browser;
import elemental.dom.Element;
import elemental.dom.NamedNodeMap;
import elemental.dom.Node;
import elemental.events.EventListener;
import elemental.traversal.NodeFilter;
import elemental.traversal.TreeWalker;

/**
 * Static helper methods used from code generated by {@code @Templated} annotation processors. You should not need to
 * call any of these methods manually.
 *
 * @author Harald Pehl
 */
public final class TemplateUtil {

    @FunctionalInterface
    private interface SelectorFunction {

        Element select(Element context, String identifier);
    }


    private static SelectorFunction DATA_ELEMENT = (context, identifier) ->
            context.querySelector("[data-element=" + identifier + "]");

    private TemplateUtil() {}


    // ------------------------------------------------------ Element methods

    public static Element resolveElement(Element context, String identifier) {
        return DATA_ELEMENT.select(context, identifier);
    }

    @SuppressWarnings("unchecked")
    public static <E extends Element> E resolveElementAs(Element context, String identifier) {
        return (E) DATA_ELEMENT.select(context, identifier);
    }

    public static void replaceElement(Element context, String identifier, Element newElement) {
        if (newElement == null) {
            throw new NullPointerException("New element must not be null in TemplateUtils.replaceElement()");
        }
        Element oldElement = resolveElement(context, identifier);
        if (oldElement != null && oldElement.getParentElement() != null) {
            oldElement.getParentElement().replaceChild(newElement, oldElement);
        }
    }


    // ------------------------------------------------------ IsElement / (Is)Widget methods

    public static void replaceIsElement(Element context, String identifier, IsElement newElement) {
        replaceElement(context, identifier, newElement.asElement());
    }

    public static void replaceWidget(Element context, String identifier, Widget newWidget) {
        replaceElement(context, identifier, Elements.asElement(newWidget));
    }

    public static void replaceIsWidget(Element context, String identifier, IsWidget newWidget) {
        replaceElement(context, identifier, Elements.asElement(newWidget));
    }


    // ------------------------------------------------------ event handler

    public static void registerEventHandler(Element context, String identifier, EventType type, EventListener handler) {
        Element element = resolveElement(context, identifier);
        if (element == null) {
            throw new NullPointerException("Element must not be null in TemplateUtils.registerEventHandler()");
        }
        type.register(element, handler);
    }


    // ------------------------------------------------------ handlebars

    public static void replaceHandlebar(Element context, String expression, String value) {
        // It seems NodeFilter isn't supported yet in Elemental. See https://github.com/gwtproject/gwt/issues/9171
        // Once it's fixed replace the native JS implementation with this one:
        //        TreeWalker textWalker = Browser.getDocument().createTreeWalker(context, NodeFilter.SHOW_TEXT,
        //                node -> {
        //                    if (node.getNodeValue() != null && node.getNodeValue().contains(expression)) {
        //                        return NodeFilter.FILTER_ACCEPT;
        //                    }
        //                    return NodeFilter.FILTER_SKIP;
        //                }, false);
        //
        //        while (textWalker.nextNode() != null) {
        //            String currentValue = treeWalker.getCurrentNode().getNodeValue();
        //            String newValue = currentValue.replace(expression, value);
        //            treeWalker.getCurrentNode().setNodeValue(newValue);
        //        }
        replaceHandlebarInText(context, expression, value);
        replaceHandlebarInAttributes(context, expression, value);
    }

    private static native void replaceHandlebarInText(Element context, String expression, String value) /*-{
        var treeWalker = $doc.createTreeWalker(context, NodeFilter.SHOW_TEXT, {
            acceptNode: function (node) {
                if (node.nodeValue.indexOf(expression) != -1) {
                    return NodeFilter.FILTER_ACCEPT;
                } else {
                    return NodeFilter.FILTER_SKIP;
                }
            }
        }, false);
        while (treeWalker.nextNode()) {
            treeWalker.currentNode.nodeValue = treeWalker.currentNode.nodeValue.replace(expression, value);
        }
    }-*/;

    private static void replaceHandlebarInAttributes(final Element context, final String expression,
            final String value) {
        TreeWalker elementWalker = Browser.getDocument()
                .createTreeWalker(context, NodeFilter.SHOW_ELEMENT, null, false);

        while (elementWalker.nextNode() != null) {
            NamedNodeMap attributes = elementWalker.getCurrentNode().getAttributes();
            for (int i = 0; i < attributes.getLength(); i++) {
                Node attribute = attributes.item(i);
                final String currentValue = attribute.getNodeValue();
                if (currentValue.contains(expression)) {
                    String newValue = currentValue.replace(expression, value);
                    attribute.setNodeValue(newValue);
                }
            }
        }
    }
}
@@ -5083,32 +5083,38 @@
 %0A        replace
+Nested
 HandlebarInText(
@@ -5134,32 +5134,203 @@
 ession, value);%0A
+        replaceNestedHandlebarInAttributes(context, expression, value);%0A        // The call above does not catch the attributes in 'context', thus replace them explicitly%0A
         replaceH
@@ -5416,32 +5416,38 @@
 ive void replace
+Nested
 HandlebarInText(
@@ -6059,32 +6059,38 @@
 tic void replace
+Nested
 HandlebarInAttri
@@ -6384,86 +6384,310 @@
     
-NamedNodeMap attributes = elementWalker.getCurrentNode().getAttributes();%0A    
+replaceHandlebarInAttributes((Element) elementWalker.getCurrentNode(), expression, value);%0A        %7D%0A    %7D%0A%0A    private static void replaceHandlebarInAttributes(final Element context, final String expression,%0A            final String value) %7B%0A        NamedNodeMap attributes = context.getAttributes();%0A
     
@@ -6753,20 +6753,16 @@
         
-  
-  
 Node att
@@ -6790,20 +6790,16 @@
 tem(i);%0A
-    
         
@@ -6864,28 +6864,24 @@
             
-   
- 
 if (currentV
@@ -6905,28 +6905,24 @@
 ression)) %7B%0A
-    
             
@@ -6980,28 +6980,24 @@
 on, value);%0A
-    
             
@@ -7034,34 +7034,16 @@
 Value);%0A
-                %7D%0A
         

Fix handlebar replacement in top level attributes