package edu.hm.hafner.analysis.parser.fxcop;

import java.util.Optional;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import edu.hm.hafner.analysis.IssueBuilder;
import edu.hm.hafner.analysis.IssueParser;
import edu.hm.hafner.analysis.ParsingCanceledException;
import edu.hm.hafner.analysis.ParsingException;
import edu.hm.hafner.analysis.ReaderFactory;
import edu.hm.hafner.analysis.Report;
import edu.hm.hafner.util.XmlElementUtil;

/**
 * Parses a fxcop xml report file.
 *
 * <p> Note that instances of this parser are not thread safe. </p>
 */
@SuppressWarnings("unused")
public class FxCopParser extends IssueParser {
    private static final long serialVersionUID = -7208558002331355408L;

    @Override
    public Report parse(final ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException {
        return new XmlParser().parse(readerFactory);
    }

    /**
     * Handles parsing of the XML file.
     */
    private static class XmlParser {
        private final Report warnings = new Report();
        private final FxCopRuleSet ruleSet = new FxCopRuleSet();

        public Report parse(final ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException {
            Document doc = readerFactory.readDocument();

            NodeList mainNode = doc.getElementsByTagName("FxCopReport");

            Element rootElement = (Element) mainNode.item(0);
            parseRules(rootElement);
            parseNamespaces(rootElement);
            parseTargets(rootElement);

            return warnings;

        }

        private void parseRules(final Element rootElement) {
            Optional<Element> rulesElement = XmlElementUtil.getFirstChildElementByName(rootElement, "Rules");
            if (rulesElement.isPresent()) {
                for (Element rule : XmlElementUtil.getChildElementsByName(rulesElement.get(), "Rule")) {
                    ruleSet.addRule(rule);
                }
            }
        }

        private void parseTargets(final Element rootElement) {
            Optional<Element> targetsElement = XmlElementUtil.getFirstChildElementByName(rootElement, "Targets");
            if (targetsElement.isPresent()) {
                for (Element target : XmlElementUtil.getChildElementsByName(targetsElement.get(), "Target")) {
                    String name = getString(target, "Name");
                    parseMessages(target, name);
                    parseModules(target, name);
                    parseResources(target, name);
                }
            }
        }

        private void parseResources(final Element target, final String parentName) {
            Optional<Element> resources = XmlElementUtil.getFirstChildElementByName(target, "Resources");
            if (resources.isPresent()) {
                for (Element resource : XmlElementUtil.getChildElementsByName(resources.get(), "Resource")) {
                    String name = getString(resource, "Name");
                    parseMessages(resource, name);
                }
            }
        }

        private void parseModules(final Element target, final String parentName) {
            Optional<Element> modulesElement = XmlElementUtil.getFirstChildElementByName(target, "Modules");
            if (modulesElement.isPresent()) {
                for (Element module : XmlElementUtil.getChildElementsByName(modulesElement.get(), "Module")) {
                    String name = getString(module, "Name");
                    parseMessages(module, name);
                    parseNamespaces(module);
                }
            }
        }

        private void parseNamespaces(final Element rootElement) {
            Optional<Element> namespacesElement = XmlElementUtil.getFirstChildElementByName(rootElement, "Namespaces");
            if (namespacesElement.isPresent()) {
                for (Element namespace : XmlElementUtil.getChildElementsByName(namespacesElement.get(), "Namespace")) {
                    String name = getString(namespace, "Name");

                    parseMessages(namespace, name);
                    parseTypes(namespace, name);
                }
            }
        }

        private void parseTypes(final Element typesElement, final String parentName) {
            Optional<Element> types = XmlElementUtil.getFirstChildElementByName(typesElement, "Types");
            if (types.isPresent()) {
                for (Element type : XmlElementUtil.getChildElementsByName(types.get(), "Type")) {
                    String name = parentName + "." + getString(type, "Name");

                    parseMessages(type, name);
                    parseMembers(type, name);
                }
            }
        }

        private void parseMembers(final Element members, final String parentName) {
            Optional<Element> membersElement = XmlElementUtil.getFirstChildElementByName(members, "Members");
            if (membersElement.isPresent()) {
                for (Element member : XmlElementUtil.getChildElementsByName(membersElement.get(), "Member")) {
                    parseMember(member, parentName);
                }
            }
        }

        private void parseAccessors(final Element accessorsElement, final String parentName) {
            Optional<Element> accessors = XmlElementUtil.getFirstChildElementByName(accessorsElement, "Accessors");
            if (accessors.isPresent()) {
                for (Element member : XmlElementUtil.getChildElementsByName(accessors.get(), "Accessor")) {
                    parseMember(member, parentName);
                }
            }
        }

        private void parseMember(final Element member, final String parentName) {
            parseMessages(member, parentName);
            parseAccessors(member, parentName);
        }

        private void parseMessages(final Element messages, final String parentName) {
            Optional<Element> messagesElement = XmlElementUtil.getFirstChildElementByName(messages, "Messages");
            if (messagesElement.isPresent()) {
                for (Element message : XmlElementUtil.getChildElementsByName(messagesElement.get(), "Message")) {
                    for (Element issue : XmlElementUtil.getChildElementsByName(message, "Issue")) {
                        parseIssue(issue, message, parentName);
                    }
                }
            }
        }

        private void parseIssue(final Element issue, final Element parent, final String parentName) {
            String typeName = getString(parent, "TypeName");
            String category = getString(parent, "Category");
            String checkId = getString(parent, "CheckId");
            String issueLevel = getString(issue, "Level");

            StringBuilder msgBuilder = new StringBuilder();
            FxCopRule rule = ruleSet.getRule(category, checkId);
            if (rule == null) {
                msgBuilder.append(typeName);
            }
            else {
                msgBuilder.append("<a href=\"");
                msgBuilder.append(rule.getUrl());
                msgBuilder.append("\">");
                msgBuilder.append(typeName);
                msgBuilder.append("</a>");
            }
            msgBuilder.append(" - ");
            msgBuilder.append(issue.getTextContent());

            String filePath = getString(issue, "Path");
            String fileName = getString(issue, "File");
            String fileLine = getString(issue, "Line");

            IssueBuilder builder = new IssueBuilder().setFileName(filePath + "/" + fileName)
                    .setLineStart(fileLine)
                    .setCategory(category)
                    .setMessage(msgBuilder.toString())
                    .guessSeverity(issueLevel);
            if (rule != null) {
                builder.setDescription(rule.getDescription());
            }
            warnings.add(builder.build());
        }

        private String getString(final Element element, final String name) {
            if (element.hasAttribute(name)) {
                return element.getAttribute(name);
            }
            else {
                return "";
            }
        }
    }
}