package io.quarkus.qute;

import io.quarkus.qute.SectionHelperFactory.BlockInfo;
import io.quarkus.qute.TemplateNode.Origin;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

/**
 * Each section consists of one or more blocks. The main block is always present. Additional blocks start with a label
 * definition: <code>{#label param1}</code>.
 */
public final class SectionBlock {

    public final Origin origin;

    /**
     * Id generated by the parser. {@code main} for the main block.
     */
    public final String id;
    /**
     * Label used for the given part. {@code main} for the main block.
     */
    public final String label;
    /**
     * An ordered map of parsed parameters.
     */
    public final Map<String, String> parameters;

    public final Map<String, Expression> expressions;

    /**
     * Section content.
     */
    List<TemplateNode> nodes;

    public SectionBlock(Origin origin, String id, String label, Map<String, String> parameters,
            Map<String, Expression> expressions,
            List<TemplateNode> nodes) {
        this.origin = origin;
        this.id = id;
        this.label = label;
        this.parameters = parameters;
        this.expressions = expressions;
        this.nodes = ImmutableList.copyOf(nodes);
    }

    public boolean isEmpty() {
        return nodes.isEmpty();
    }

    List<Expression> getExpressions() {
        List<Expression> expressions = new ArrayList<>();
        expressions.addAll(this.expressions.values());
        for (TemplateNode node : nodes) {
            expressions.addAll(node.getExpressions());
        }
        return expressions;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("SectionBlock [origin=").append(origin).append(", id=").append(id).append(", label=").append(label)
                .append("]");
        return builder.toString();
    }

    void optimizeNodes(Set<TemplateNode> nodesToRemove) {
        List<TemplateNode> effectiveNodes = new ArrayList<>();
        for (TemplateNode node : nodes) {
            if (node instanceof SectionNode) {
                effectiveNodes.add(node);
                ((SectionNode) node).optimizeNodes(nodesToRemove);
            } else if (node != Parser.COMMENT_NODE && !(node instanceof ParameterDeclarationNode)
                    && (nodesToRemove.isEmpty() || !nodesToRemove.contains(node))) {
                // Ignore comments, param declarations and nodes for removal
                effectiveNodes.add(node);
            }
        }
        // Collapse adjacent text and line separator nodes
        List<TemplateNode> finalNodes = new ArrayList<>();
        List<TextNode> group = null;
        for (TemplateNode node : effectiveNodes) {
            if (node instanceof TextNode) {
                if (group == null) {
                    group = new ArrayList<>();
                }
                group.add((TextNode) node);
            } else {
                if (group != null) {
                    collapseGroup(group, finalNodes);
                    group = null;
                }
                finalNodes.add(node);
            }
        }
        if (group != null) {
            collapseGroup(group, finalNodes);
        }
        nodes = ImmutableList.copyOf(finalNodes);
    }

    private void collapseGroup(List<TextNode> group, List<TemplateNode> finalNodes) {
        if (group.size() > 1) {
            // Collapse the group...
            StringBuilder val = new StringBuilder();
            for (TextNode textNode : group) {
                val.append(textNode.getValue());
            }
            finalNodes.add(new TextNode(val.toString(), group.get(0).getOrigin()));
        } else {
            finalNodes.add(group.get(0));
        }
    }

    static SectionBlock.Builder builder(String id, Function<String, Expression> expressionFunc,
            Function<String, TemplateException> errorFun) {
        return new Builder(id, expressionFunc, errorFun).setLabel(id);
    }

    static class Builder implements BlockInfo {

        private final String id;
        private Origin origin;
        private String label;
        private final Map<String, String> parameters;
        private final List<TemplateNode> nodes;
        private final Map<String, Expression> expressions;
        private final Function<String, Expression> expressionFun;
        private final Function<String, TemplateException> errorFun;

        public Builder(String id, Function<String, Expression> expressionFun, Function<String, TemplateException> errorFun) {
            this.id = id;
            this.parameters = new LinkedHashMap<>();
            this.nodes = new ArrayList<>();
            this.expressions = new LinkedHashMap<>();
            this.expressionFun = expressionFun;
            this.errorFun = errorFun;
        }

        SectionBlock.Builder setOrigin(Origin origin) {
            this.origin = origin;
            return this;
        }

        SectionBlock.Builder addNode(TemplateNode node) {
            nodes.add(node);
            return this;
        }

        SectionBlock.Builder setLabel(String label) {
            this.label = label;
            return this;
        }

        SectionBlock.Builder addParameter(String name, String value) {
            this.parameters.put(name, value);
            return this;
        }

        @Override
        public Expression addExpression(String param, String value) {
            Expression expression = expressionFun.apply(value);
            expressions.put(param, expression);
            return expression;
        }

        public Map<String, String> getParameters() {
            return Collections.unmodifiableMap(parameters);
        }

        public String getLabel() {
            return label;
        }

        @Override
        public TemplateException createParserError(String message) {
            return errorFun.apply(message);
        }

        SectionBlock build() {
            return new SectionBlock(origin, id, label, parameters, expressions, nodes);
        }
    }

}
