/*
 * 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.xslt.context;

import com.intellij.codeInsight.daemon.impl.analysis.CreateNSDeclarationIntentionFix;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.lang.xml.XMLLanguage;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.xml.SchemaPrefix;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlElement;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.IncorrectOperationException;
import org.intellij.lang.xpath.context.NamespaceContext;
import org.intellij.lang.xpath.xslt.XsltSupport;
import org.intellij.lang.xpath.xslt.impl.XsltChecker;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

public class XsltNamespaceContext implements NamespaceContext {
    public static final XsltNamespaceContext NAMESPACE_CONTEXT = new XsltNamespaceContext();

    @Override
    public String getNamespaceURI(String prefix, XmlElement context) {
        return getNamespaceUriStatic(prefix, context);
    }

    public static @Nullable String getNamespaceUriStatic(String prefix, XmlElement context) {
        final XmlTag tag = PsiTreeUtil.getParentOfType(context, XmlTag.class);
        return tag != null ? tag.getNamespaceByPrefix(prefix) : null;
    }

    @Override
    public @Nullable String getPrefixForURI(String uri, XmlElement context) {
        final XmlTag tag = PsiTreeUtil.getParentOfType(context, XmlTag.class);
        return tag != null ? tag.getPrefixByNamespace(uri) : null;
    }

    @Override
    public @NotNull Collection<String> getKnownPrefixes(XmlElement context) {
        return getPrefixes(context);
    }

    public static Collection<String> getPrefixes(XmlElement context) {
        final XmlTag tag = PsiTreeUtil.getParentOfType(context, XmlTag.class);
        if (tag != null) {
            final HashSet<String> allPrefixes = new HashSet<>();
            final String[] uris = tag.knownNamespaces();
            for (String uri : uris) {
                for (XmlTag p = tag; p != null; p = p.getParentTag()) {
                    final Set<Map.Entry<String,String>> localPrefixes = p.getLocalNamespaceDeclarations().entrySet();
                    for (Map.Entry<String,String> entry : localPrefixes) {
                        final String prefix = entry.getKey();
                        if (!prefix.isEmpty() && entry.getValue().equals(uri)) {
                          allPrefixes.add(prefix);
                        }
                    }
                }
            }
            return allPrefixes;
        } else {
            return Collections.emptySet();
        }
    }

    @Override
    public @Nullable PsiElement resolve(String prefix, XmlElement context) {
        return resolvePrefix(prefix, context);
    }

    public static @Nullable PsiElement resolvePrefix(final String prefix, XmlElement context) {
        final String name = "xmlns:" + prefix;

        XmlTag parent = PsiTreeUtil.getParentOfType(context, XmlTag.class);
        while (parent != null) {
            final XmlAttribute attribute = parent.getAttribute(name, null);
            if (attribute != null) {
              final TextRange textRange = TextRange.from("xmlns:".length(), prefix.length());
              return new SchemaPrefix(attribute, textRange, prefix) {
                @Override
                public boolean equals(Object obj) {
                  if (obj instanceof SchemaPrefix p) {
                    return prefix.equals(p.getName()) && p.getParent() == attribute;
                  }
                  return super.equals(obj);
                }
              };
            }
            parent = PsiTreeUtil.getParentOfType(parent, XmlTag.class);
        }
        return null;
    }

    @Override
    public IntentionAction[] getUnresolvedNamespaceFixes(@NotNull PsiReference reference, String localName) {
        return getUnresolvedNamespaceFixesStatic(reference, localName);
    }

    @Override
    public String getDefaultNamespace(XmlElement context) {
      if (context != null) {
        if (XsltSupport.getXsltLanguageLevel(context.getContainingFile()) == XsltChecker.LanguageLevel.V2) {
          context = PsiTreeUtil.getParentOfType(context, XmlTag.class, false);

          if (context != null) {
            do {
              final XmlTag tag = (XmlTag)context;
              String uri;
              if ((uri = tag.getAttributeValue("xpath-default-namespace", null)) != null ||
                  (uri = tag.getAttributeValue("xpath-default-namespace", XsltSupport.XSLT_NS)) != null) {
                return !uri.isEmpty() ? uri : null;
              }
              context = PsiTreeUtil.getParentOfType(context, XmlTag.class, true);
            }
            while (context != null);
          }
        }
      }

      return null;
    }

    public static IntentionAction[] getUnresolvedNamespaceFixesStatic(PsiReference reference, String localName) {
      PsiElement element = reference.getElement();
      XmlElementFactory factory = XmlElementFactory.getInstance(element.getProject());
      XmlTag tag = factory.createTagFromText("<" + reference.getCanonicalText() + ":" + localName + " />", XMLLanguage.INSTANCE);

      XmlFile xmlFile = PsiTreeUtil.getContextOfType(element, XmlFile.class, true);
      return xmlFile == null ? IntentionAction.EMPTY_ARRAY : new IntentionAction[]{
        new MyCreateNSDeclarationAction(tag, reference.getCanonicalText(), xmlFile)
      };
    }

  static class MyCreateNSDeclarationAction extends CreateNSDeclarationIntentionFix {
    private final SmartPsiElementPointer<XmlFile> myXmlFile;

    // TODO: verify API
    MyCreateNSDeclarationAction(@NotNull XmlElement xmlElement, String prefix, @NotNull XmlFile xmlFile) {
      super(xmlElement, prefix);
      myXmlFile = SmartPointerManager.createPointer(xmlFile);
    }

    @Override
    public void invoke(@NotNull Project project, Editor editor, @NotNull PsiFile psiFile) throws IncorrectOperationException {
      XmlFile xmlFile = myXmlFile.getElement();
      if (xmlFile != null) {
        super.invoke(project, editor, xmlFile);
      }
    }

    @Override
    public boolean showHint(@NotNull Editor editor) {
      return false; // doesn't work properly yet
    }

    @Override
    public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile psiFile) {
      XmlFile xmlFile = myXmlFile.getElement();
      return xmlFile != null && super.isAvailable(project, editor, xmlFile);
    }
  }
}
