/*
 * Copyright 2005 Sascha Weinreuter
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.intellij.lang.xpath.validation.inspections;

import com.intellij.codeInspection.InspectionManager;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.lang.Language;
import com.intellij.psi.xml.XmlElement;
import org.intellij.lang.xpath.XPathFileType;
import org.intellij.lang.xpath.context.ContextProvider;
import org.intellij.lang.xpath.context.NamespaceContext;
import org.intellij.lang.xpath.psi.PrefixedName;
import org.intellij.lang.xpath.psi.XPathNodeTest;
import org.intellij.plugins.xpathView.XPathBundle;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.xml.namespace.QName;
import java.text.MessageFormat;
import java.util.Set;

public class CheckNodeTest extends XPathInspection {
    private static final @NonNls String SHORT_NAME = "CheckNodeTest";

    @Override
    protected Visitor createVisitor(InspectionManager manager, boolean isOnTheFly) {
        return new MyVisitor(manager, isOnTheFly);
    }

  @Override
  public @NotNull @NonNls String getShortName() {
        return SHORT_NAME;
    }

    @Override
    public boolean isEnabledByDefault() {
        return true;
    }

    @Override
    protected boolean acceptsLanguage(Language language) {
      return language == XPathFileType.XPATH.getLanguage() || language == XPathFileType.XPATH2.getLanguage();
    }

    static final class MyVisitor extends Visitor {
        MyVisitor(InspectionManager manager, boolean isOnTheFly) {
            super(manager, isOnTheFly);
        }

        @Override
        protected void checkNodeTest(XPathNodeTest nodeTest) {
            final ContextProvider contextProvider = ContextProvider.getContextProvider(nodeTest.getContainingFile());
            final XmlElement contextNode = contextProvider.getContextElement();
            final NamespaceContext namespaceContext = contextProvider.getNamespaceContext();
            if (namespaceContext == null) return;

            if (nodeTest.isNameTest() && contextNode != null) {
                final PrefixedName prefixedName = nodeTest.getQName();
                assert prefixedName != null;
                if (!"*".equals(prefixedName.getLocalName()) && !"*".equals(prefixedName.getPrefix())) {
                    boolean found;

                    if (nodeTest.getPrincipalType() == XPathNodeTest.PrincipalType.ELEMENT) {
                        final Set<QName> elementNames = contextProvider.getElements(true);
                        if (elementNames != null) {
                            found = false;
                            for (QName pair : elementNames) {
                              if (matches(nodeTest.getQName(), pair, namespaceContext, contextNode, true)) {
                                    found = true;
                                    break;
                                }
                            }
                            if (!found) {
                                registerProblem(contextProvider, prefixedName, nodeTest, false);
                            }
                        }
                    } else if (nodeTest.getPrincipalType() == XPathNodeTest.PrincipalType.ATTRIBUTE) {
                        final Set<QName> attributeNames = contextProvider.getAttributes(true);
                        if (attributeNames != null) {
                            found = false;
                            for (QName pair : attributeNames) {
                                if (matches(nodeTest.getQName(), pair, namespaceContext, contextNode, false)) {
                                    found = true;
                                    break;
                                }
                            }
                            if (!found) {
                                registerProblem(contextProvider, prefixedName, nodeTest, true);
                            }
                        }
                    }
                }
            }
        }

        private void registerProblem(ContextProvider contextProvider, PrefixedName prefixedName, XPathNodeTest nodeTest, boolean attribute) {
            final QName qName = contextProvider.getQName(prefixedName, nodeTest);
            final String name;
            if (qName != null) {
                final String pattern;
                if (!"".equals(qName.getNamespaceURI())) {
                    pattern = "''{0}'' ({1})";
                } else {
                    pattern = "''{0}''";
                }
                name = MessageFormat.format(pattern, qName.getLocalPart(), qName.getNamespaceURI());
            } else {
                name = MessageFormat.format("''{0}''", prefixedName.getLocalName());
            }

            final LocalQuickFix[] fixes = contextProvider.getQuickFixFactory().createUnknownNodeTestFixes(nodeTest);
            addProblem(myManager.createProblemDescriptor(nodeTest,
                                                         XPathBundle.message(attribute ? "inspection.message.html.unknown.attribute.name.html"
                                                                                       : "inspection.message.html.unknown.element.name.html",
                                                                             name),
                                                         myOnTheFly, fixes, ProblemHighlightType.GENERIC_ERROR_OR_WARNING));
        }

        private static boolean matches(@Nullable PrefixedName prefixedName,
                                       QName element,
                                       NamespaceContext namespaceContext,
                                       XmlElement context,
                                       boolean allowDefaultNamespace)
        {
            if (prefixedName == null) return false;

            boolean b = prefixedName.getLocalName().equals(element.getLocalPart()) || "*".equals(element.getLocalPart());

            final String prefix = prefixedName.getPrefix();
            if (prefix != null) {
              if (!"*".equals(prefix)) {
                final String namespaceURI = namespaceContext.getNamespaceURI(prefix, context);
                b = b && element.getNamespaceURI().equals(namespaceURI);
              }
            } else if (allowDefaultNamespace) {
              final String namespaceURI = namespaceContext.getDefaultNamespace(context);
              b = b && (element.getNamespaceURI().equals(namespaceURI) || (element.getNamespaceURI().isEmpty() && namespaceURI == null));
            } else {
              b = b && element.getNamespaceURI().isEmpty();
            }
          return b;
        }
    }
}
