/*
 * Decompiled with CFR 0.152.
 */
package org.apache.logging.log4j.core.config.plugins.util;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.plugins.PluginAliases;
import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.config.plugins.util.PluginType;
import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor;
import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitors;
import org.apache.logging.log4j.core.util.Assert;
import org.apache.logging.log4j.core.util.Builder;
import org.apache.logging.log4j.status.StatusLogger;

public class PluginBuilder<T>
implements Builder<T> {
    private static final Logger LOGGER = StatusLogger.getLogger();
    private final PluginType<T> pluginType;
    private final Class<T> clazz;
    private Configuration configuration;
    private Node node;
    private LogEvent event;

    public PluginBuilder(PluginType<T> pluginType) {
        this.pluginType = pluginType;
        this.clazz = pluginType.getPluginClass();
    }

    public PluginBuilder<T> withConfiguration(Configuration configuration) {
        this.configuration = configuration;
        return this;
    }

    public PluginBuilder<T> withConfigurationNode(Node node) {
        this.node = node;
        return this;
    }

    public PluginBuilder<T> forLogEvent(LogEvent event) {
        this.event = event;
        return this;
    }

    @Override
    public T build() {
        this.verify();
        try {
            LOGGER.debug("Building Plugin[name={}, class={}]. Searching for builder factory method...", this.pluginType.getElementName(), this.pluginType.getPluginClass().getName());
            Builder<T> builder = PluginBuilder.createBuilder(this.clazz);
            if (builder != null) {
                this.injectFields(builder);
                T result = builder.build();
                LOGGER.debug("Built Plugin[name={}] OK from builder factory method.", this.pluginType.getElementName());
                return result;
            }
        }
        catch (Exception e) {
            LOGGER.catching(Level.ERROR, e);
            LOGGER.error("Unable to inject fields into builder class for plugin type {}, element {}.", this.clazz, this.node.getName());
        }
        try {
            LOGGER.debug("Still building Plugin[name={}, class={}]. Searching for factory method...", this.pluginType.getElementName(), this.pluginType.getPluginClass().getName());
            Method factory = PluginBuilder.findFactoryMethod(this.clazz);
            Object[] params = this.generateParameters(factory);
            Object plugin = factory.invoke(null, params);
            LOGGER.debug("Built Plugin[name={}] OK from factory method.", this.pluginType.getElementName());
            return (T)plugin;
        }
        catch (Exception e) {
            LOGGER.catching(Level.ERROR, e);
            LOGGER.error("Unable to invoke factory method in class {} for element {}.", this.clazz, this.node.getName());
            return null;
        }
    }

    private void verify() {
        Assert.requireNonNull(this.configuration, "No Configuration object was set.");
        Assert.requireNonNull(this.node, "No Node object was set.");
    }

    private static <T> Builder<T> createBuilder(Class<T> clazz) throws InvocationTargetException, IllegalAccessException {
        for (Method method : clazz.getDeclaredMethods()) {
            if (!method.isAnnotationPresent(PluginBuilderFactory.class) || !Modifier.isStatic(method.getModifiers())) continue;
            Builder builder = (Builder)method.invoke(null, new Object[0]);
            LOGGER.debug("Found builder factory method [{}]: {}.", method.getName(), method);
            return builder;
        }
        LOGGER.debug("No builder factory method found in class {}. Going to try finding a factory method instead.", clazz.getName());
        return null;
    }

    private void injectFields(Builder<T> builder) throws IllegalAccessException {
        Field[] fields = builder.getClass().getDeclaredFields();
        StringBuilder log = new StringBuilder();
        for (Field field : fields) {
            log.append(log.length() == 0 ? "with params(" : ", ");
            field.setAccessible(true);
            Annotation[] annotations = field.getDeclaredAnnotations();
            String[] aliases = PluginBuilder.extractPluginAliases(annotations);
            for (Annotation a : annotations) {
                Object value;
                PluginVisitor<? extends Annotation> visitor;
                if (a instanceof PluginAliases || (visitor = PluginVisitors.findVisitor(a.annotationType())) == null || (value = visitor.setAliases(aliases).setAnnotation(a).setConversionType(field.getType()).setStrSubstitutor(this.configuration.getStrSubstitutor()).setMember(field).visit(this.configuration, this.node, this.event, log)) == null) continue;
                field.set(builder, value);
            }
        }
        if (log.length() > 0) {
            log.append(')');
        }
        LOGGER.debug("Calling build() on class {} for element {} {}", builder.getClass(), this.node.getName(), log.toString());
        this.checkForRemainingAttributes();
        this.verifyNodeChildrenUsed();
    }

    private static <T> Method findFactoryMethod(Class<T> clazz) {
        for (Method method : clazz.getDeclaredMethods()) {
            if (!method.isAnnotationPresent(PluginFactory.class) || !Modifier.isStatic(method.getModifiers())) continue;
            LOGGER.debug("Found factory method [{}]: {}.", method.getName(), method);
            return method;
        }
        LOGGER.debug("No factory method found in class {}.", clazz.getName());
        return null;
    }

    private Object[] generateParameters(Method factory) {
        StringBuilder log = new StringBuilder();
        Class<?>[] types = factory.getParameterTypes();
        Annotation[][] annotations = factory.getParameterAnnotations();
        Object[] args = new Object[annotations.length];
        for (int i = 0; i < annotations.length; ++i) {
            log.append(log.length() == 0 ? "with params(" : ", ");
            String[] aliases = PluginBuilder.extractPluginAliases(annotations[i]);
            for (Annotation a : annotations[i]) {
                PluginVisitor<? extends Annotation> visitor;
                if (a instanceof PluginAliases || (visitor = PluginVisitors.findVisitor(a.annotationType())) == null) continue;
                args[i] = visitor.setAliases(aliases).setAnnotation(a).setConversionType(types[i]).setStrSubstitutor(this.configuration.getStrSubstitutor()).setMember(factory).visit(this.configuration, this.node, this.event, log);
            }
        }
        if (log.length() > 0) {
            log.append(')');
        }
        this.checkForRemainingAttributes();
        this.verifyNodeChildrenUsed();
        LOGGER.debug("Calling {} on class {} for element {} {}", factory.getName(), this.clazz.getName(), this.node.getName(), log.toString());
        return args;
    }

    private static String[] extractPluginAliases(Annotation ... parmTypes) {
        String[] aliases = null;
        for (Annotation a : parmTypes) {
            if (!(a instanceof PluginAliases)) continue;
            aliases = ((PluginAliases)a).value();
        }
        return aliases;
    }

    private void checkForRemainingAttributes() {
        Map<String, String> attrs = this.node.getAttributes();
        if (!attrs.isEmpty()) {
            StringBuilder sb = new StringBuilder();
            for (String key : attrs.keySet()) {
                if (sb.length() == 0) {
                    sb.append(this.node.getName());
                    sb.append(" contains ");
                    if (attrs.size() == 1) {
                        sb.append("an invalid element or attribute ");
                    } else {
                        sb.append("invalid attributes ");
                    }
                } else {
                    sb.append(", ");
                }
                sb.append('\"');
                sb.append(key);
                sb.append('\"');
            }
            LOGGER.error(sb.toString());
        }
    }

    private void verifyNodeChildrenUsed() {
        List<Node> children = this.node.getChildren();
        if (!this.pluginType.isDeferChildren() && !children.isEmpty()) {
            for (Node child : children) {
                String nodeType = this.node.getType().getElementName();
                String start = nodeType.equals(this.node.getName()) ? this.node.getName() : nodeType + ' ' + this.node.getName();
                LOGGER.error("{} has no parameter that matches element {}", start, child.getName());
            }
        }
    }
}
