package org.jboss.windup.rules.files;

import static org.joox.JOOX.$;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.inject.Inject;

import org.apache.commons.lang3.StringUtils;
import org.jboss.windup.config.metadata.RuleMetadataType;
import org.jboss.windup.config.parser.ElementHandler;
import org.jboss.windup.config.parser.NamespaceElementHandler;
import org.jboss.windup.config.parser.ParserContext;
import org.jboss.windup.config.parser.xml.RuleProviderHandler;
import org.jboss.windup.graph.GraphTypeManager;
import org.jboss.windup.graph.model.WindupFrame;
import org.jboss.windup.graph.model.resource.FileModel;
import org.jboss.windup.util.exception.WindupException;
import org.jboss.windup.util.xml.XmlUtil;
import org.ocpsoft.rewrite.config.Rule;
import org.ocpsoft.rewrite.context.Context;
import org.w3c.dom.Element;

/**
 * Handles parsing the "file-mapping" element to add rules to the current ruleset.
 */
@NamespaceElementHandler(elementName = FileMappingHandler.ELEM_NAME, namespace = RuleProviderHandler.WINDUP_RULE_NAMESPACE)
public class FileMappingHandler implements ElementHandler<Void> {
    protected static final String ELEM_NAME = "file-mapping";
    private static final String FROM = "from";
    private static final String TO = "to";
    private static final String ON_PARSE_ERROR = "onParseError";

    @Inject
    private GraphTypeManager typeManager;

    @Override
    @SuppressWarnings("unchecked")
    public Void processElement(ParserContext context, Element element) {
        String id = $(element).attr("id");

        String from = $(element).attr(FROM);
        String to = $(element).attr(TO);
        String onParseError = $(element).attr(ON_PARSE_ERROR);

        if (StringUtils.isBlank(from)) {
            throw new WindupException("The '" + ELEM_NAME + "' element must have a non-empty '" + FROM + "' attribute");
        }
        if (StringUtils.isBlank(to)) {
            throw new WindupException("The '" + ELEM_NAME + "' element must have a non-empty '" + TO + "' attribute");
        }
        if (!StringUtils.isBlank(onParseError)) {
            final List<String> acceptableValues = Arrays.asList(new String[]{ATTR_PARSE_IGNORE, "warn", "warning"});
            if (!acceptableValues.contains(onParseError))
                throw new WindupException("The '<" + ELEM_NAME + ">' attribute '" + ON_PARSE_ERROR + "' must contain one of: " + StringUtils.join(acceptableValues, ", "));
        }


        List<Class<? extends WindupFrame<?>>> types = new ArrayList<>();
        List<String> typeNames = Arrays.asList(to.trim().split("\\s*,\\s*"));
        for (String name : typeNames) {
            List<Class<? extends WindupFrame<?>>> matchingTypes = new ArrayList<>();
            for (Class<? extends WindupFrame<?>> modelType : typeManager.getRegisteredTypes()) {
                if (modelType.getName().equals(name) ||
                        modelType.getSimpleName().equals(name + ".class") ||
                        modelType.getSimpleName().equals(name + "Model") ||
                        modelType.getSimpleName().equals(name)) {
                    matchingTypes.add(modelType);
                }
            }
            if (matchingTypes.isEmpty()) {
                throw new WindupException("A '" + ELEM_NAME + "' element specifies a file mapping type [" + name
                        + "] that could not be found in any installed addons.");
            }
            if (matchingTypes.size() > 1) {
                throw new WindupException("A '" + ELEM_NAME + "' element specifies a file mapping type ["
                        + name + "] that matched multiple file types. Please select one of matches and "
                        + "update the configuration to use the fully qualified name: ["
                        + matchingTypes.toString().replaceAll(",", System.lineSeparator()) + "]");
            }
            types.addAll(matchingTypes);
        }

        FileMappingTo mappingTo = FileMapping.from(from).to(types.toArray(new Class[types.size()])).onParseError(FileModel.OnParseError.IGNORE);
        Rule rule = StringUtils.isNotBlank(id) ? mappingTo.withId(id) : mappingTo;
        if (rule instanceof Context)
            ((Context) rule).put(RuleMetadataType.RULE_XML, XmlUtil.nodeToString(element));
        context.getBuilder().addRule(rule);
        return null;
    }

    private static final String ATTR_PARSE_IGNORE = "ignore";

}
