/*
 * 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.refactoring.introduceParameter;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlDocument;
import com.intellij.psi.xml.XmlTag;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.ui.UsageViewDescriptorAdapter;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.util.IncorrectOperationException;
import org.intellij.lang.xpath.psi.XPathExpression;
import org.intellij.lang.xpath.psi.XPathVariableReference;
import org.intellij.lang.xpath.psi.impl.XPathChangeUtil;
import org.intellij.lang.xpath.xslt.XsltSupport;
import org.intellij.lang.xpath.xslt.psi.XsltElementFactory;
import org.intellij.lang.xpath.xslt.psi.XsltTemplate;
import org.intellij.lang.xpath.xslt.refactoring.RefactoringUtil;
import org.intellij.lang.xpath.xslt.util.XsltCodeInsightUtil;
import org.intellij.plugins.xpathView.XPathBundle;
import org.jetbrains.annotations.NotNull;

import java.util.*;

class IntroduceParameterProcessor extends BaseRefactoringProcessor {
    private final XPathExpression myExpression;
    private final Set<XPathExpression> myOtherExpressions;
    private final IntroduceParameterOptions mySettings;

    private final XsltTemplate myTemplate;

    IntroduceParameterProcessor(Project project, XPathExpression expression, Set<XPathExpression> otherExpressions, IntroduceParameterOptions settings) {
        super(project);
        mySettings = settings;
        myExpression = expression;
        myOtherExpressions = settings.isReplaceAll() ? otherExpressions : Collections.emptySet();

        final XmlTag templateTag = XsltCodeInsightUtil.getTemplateTag(myExpression, true, true);
        myTemplate = templateTag != null ? XsltElementFactory.getInstance().wrapElement(templateTag, XsltTemplate.class) : null;

        setPreviewUsages(settings.isPreview());
    }


    @Override
    protected @NotNull UsageViewDescriptor createUsageViewDescriptor(UsageInfo @NotNull [] usageInfos) {
        return new MyUsageViewDescriptorAdapter();
    }

    @Override
    protected UsageInfo @NotNull [] findUsages() {
        int usageCount = myOtherExpressions.size() + 1;

        final List<PsiElement> callsToUpdate;
        if (!mySettings.isCreateDefault()) {
            assert myTemplate != null;

            final Collection<PsiReference> references = ReferencesSearch.search(myTemplate, myTemplate.getUseScope(), false).findAll();
            callsToUpdate = new ArrayList<>(references.size());
            for (PsiReference reference : references) {
                final PsiElement e = reference.getElement();
                final XmlTag tag = PsiTreeUtil.getContextOfType(e, XmlTag.class, true);
                if (tag != null && XsltSupport.isTemplateCall(tag)) {
                    callsToUpdate.add(tag);
                }
            }
            usageCount += callsToUpdate.size();
        } else {
          callsToUpdate = Collections.emptyList();
        }

        final UsageInfo[] usageInfos = new UsageInfo[usageCount];
        usageInfos[0] = XPathUsageInfo.create(myExpression);

        int i = 1;
        for (XPathExpression expression : myOtherExpressions) {
            usageInfos[i++] = XPathUsageInfo.create(expression);
        }
        for (PsiElement o : callsToUpdate) {
            usageInfos[i++] = new UsageInfo(o, false);
        }
        return usageInfos;
    }

  @Override
  protected void performRefactoring(UsageInfo @NotNull [] usageInfos) {
        XmlTag tag;
        if (myTemplate != null) {
            tag = myTemplate.getTag();
        } else if ((tag = XsltCodeInsightUtil.getTemplateTag(myExpression, true, false)) == null) {
            final XmlDocument document = PsiTreeUtil.getContextOfType(myExpression, XmlDocument.class, true);
            assert document != null;
            tag = document.getRootTag();
        }
        assert tag != null;

        final XmlTag param = tag.createChildTag("param", XsltSupport.XSLT_NS, null, false);
        try {
            param.setAttribute("name", mySettings.getName());

            if (mySettings.isCreateDefault()) {
                param.setAttribute("select", myExpression.getText());
            }

            XmlTag anchorParam = null;
            for (UsageInfo info : usageInfos) {
                if (info instanceof XPathUsageInfo x) {
                  final XPathVariableReference variableReference = XPathChangeUtil.createVariableReference(x.getExpression(), mySettings.getName());
                    final XmlAttribute attribute = x.getAttribute();
                    assert attribute != null;

                    x.getExpression().replace(variableReference);

                    if (XsltSupport.isParam(attribute.getParent())) {
                        if (anchorParam == null) {
                            anchorParam = attribute.getParent();
                        } else if (attribute.getParent().getTextOffset() < anchorParam.getTextOffset()) {
                            anchorParam = attribute.getParent();
                        }
                    }
                } else {
                    final XmlTag t = (XmlTag)info.getElement();
                    if (t != null) {
                        final XmlTag p = t.createChildTag("with-param", t.getNamespace(), null, false);
                        p.setAttribute("name", mySettings.getName());
                        p.setAttribute("select", myExpression.getText());
                        t.add(p);
                    }
                }
            }

            if (anchorParam != null) {
                RefactoringUtil.addParameter(tag, param, PsiTreeUtil.getPrevSiblingOfType(anchorParam, XmlTag.class));
            } else {
                RefactoringUtil.addParameter(tag, param);
            }
        } catch (IncorrectOperationException e) {
            Logger.getInstance(getClass().getName()).error(e);
        }
    }

    @Override
    protected @NotNull String getCommandName() {
        return XPathBundle.message("command.name.introduce.xslt.parameter");
    }

    private class MyUsageViewDescriptorAdapter extends UsageViewDescriptorAdapter {

        @Override
        public PsiElement @NotNull [] getElements() {
            return new PsiElement[]{ myTemplate };
        }

        @Override
        public String getProcessedElementsHeader() {
            return XPathBundle.message("header.adding.parameter.to.template");
        }
    }
}
