package complete;

import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectLocator;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
import com.intellij.openapi.vcs.VcsRoot;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiFileFactoryImpl;
import com.intellij.psi.presentation.java.SymbolPresentationUtil;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * User: zolotov
 * Date: 5/24/13
 */
public final class EmbeditorUtil {
    private EmbeditorUtil() {
    }

    public static int getCompletionPrefixLength(@NotNull CompletionParameters completionParameters) {
        return completionParameters.getPosition().getTextLength() - CompletionInitializationContext.DUMMY_IDENTIFIER_TRIMMED.length();
    }

    public static int getOffsetFromLineStart(@NotNull CompletionParameters completionParameters, @NotNull Document document) {
        TextRange range = completionParameters.getPosition().getTextRange();
        if (range != null) {
            int offset = range.getStartOffset();
            int lineNumber = document.getLineNumber(offset);
            return offset - document.getLineStartOffset(lineNumber);
        }
        return 0;
    }

    @NotNull
    public static ResolveOutcome getSingleResolveOutcome(@NotNull final String path,
                                                         @NotNull final String fileContent,
                                                         final int line,
                                                         final int column) {
        Collection<ResolveOutcome> resolveOutcome = getResolveOutcomes(path, fileContent, line, column);
        return resolveOutcome.size() == 1
                ? ContainerUtil.getFirstItem(resolveOutcome, ResolveOutcome.NULL)
                : ResolveOutcome.NULL;
    }

    @NotNull
    public static List<ResolveOutcome> getResolveOutcomes(@NotNull final String path,
                                                          @NotNull final String fileContent,
                                                          final int line,
                                                          final int column) {
        final Ref<List<ResolveOutcome>> ref = Ref.create();
        UIUtil.invokeAndWaitIfNeeded(new Runnable() {
            @Override
            public void run() {
                PsiElement[] elements = performResolve(path, fileContent, line, column);
                if (elements.length > 0) {
                    List<ResolveOutcome> result = new LinkedList<ResolveOutcome>();
                    for (PsiElement element : elements) {
                        if (element != null) {
                            PsiFile resolveToFile = element.getContainingFile();
                            if (resolveToFile != null) {
                                VirtualFile virtualFile = resolveToFile.getOriginalFile().getVirtualFile();
                                String resolveToPath = virtualFile != null ? virtualFile.getPath() : path;
                                Document doc = resolveToFile.getViewProvider().getDocument();

                                assert doc != null;
                                int offset = element.getTextOffset();
                                int resolveToRow = doc.getLineNumber(offset);
                                int lineStartOffset = doc.getLineStartOffset(resolveToRow);
                                int resolveToColumn = offset - lineStartOffset;
                                String resolveToText = SymbolPresentationUtil.getSymbolPresentableText(element);
                                result.add(new ResolveOutcome(resolveToPath, resolveToRow, resolveToColumn, resolveToText));
                            }
                        }
                    }
                    ref.set(result);
                }
            }
        });
        return !ref.isNull() ? ref.get() : Collections.<ResolveOutcome>emptyList();
    }

    @NotNull
    private static PsiElement[] performResolve(@NotNull final String path,
                                               @Nullable final String fileContent,
                                               final int line,
                                               final int column) {
        final PsiFile targetPsiFile = findTargetFile(path);
        final VirtualFile targetVirtualFile = targetPsiFile != null ? targetPsiFile.getVirtualFile() : null;
        if (targetPsiFile != null && targetVirtualFile != null) {
            final Project project = targetPsiFile.getProject();
            final Document originalDocument = PsiDocumentManager.getInstance(project).getDocument(targetPsiFile);
            if (originalDocument != null) {

                PsiFile fileCopy = fileContent != null
                        ? createDummyPsiFile(project, fileContent, targetPsiFile)
                        : createDummyPsiFile(project, targetPsiFile.getText(), targetPsiFile);
                final Document document = fileCopy.getViewProvider().getDocument();
                if (document != null) {
                    int offset = lineAndColumnToOffset(document, line, column);
                    PsiReference reference = fileCopy.findReferenceAt(offset);
                    if (reference instanceof PsiPolyVariantReference) {
                        ResolveResult[] resolveResults = ((PsiPolyVariantReference)reference).multiResolve(true);
                        PsiElement[] elements = new PsiElement[resolveResults.length];
                        for (int i = 0; i < resolveResults.length; i++) {
                            elements[i] = resolveResults[i].getElement();
                        }
                        return elements;
                    }
                    else if (reference != null) {
                        return new PsiElement[]{reference.resolve()};
                    }
                }
            }
        }
        return PsiElement.EMPTY_ARRAY;
    }

    public static void performCompletion(@NotNull final String path,
                                         @Nullable final String fileContent,
                                         final int line,
                                         final int column,
                                         @NotNull final CompletionCallback completionCallback) {
        UIUtil.invokeAndWaitIfNeeded(new Runnable() {
            @Override
            public void run() {
                final PsiFile targetPsiFile = findTargetFile(path);
                final VirtualFile targetVirtualFile = targetPsiFile != null ? targetPsiFile.getVirtualFile() : null;
                if (targetPsiFile != null && targetVirtualFile != null) {
                    final EditorFactory editorFactory = EditorFactory.getInstance();
                    final Project project = targetPsiFile.getProject();
                    final Document originalDocument = PsiDocumentManager.getInstance(project).getDocument(targetPsiFile);
                    if (originalDocument != null) {

                        PsiFile fileCopy = fileContent != null
                                ? createDummyPsiFile(project, fileContent, targetPsiFile)
                                : createDummyPsiFile(project, targetPsiFile.getText(), targetPsiFile);
                        final Document document = fileCopy.getViewProvider().getDocument();
                        if (document != null) {
                            final Editor editor = editorFactory.createEditor(document, project, targetVirtualFile, false);
                            int offset = lineAndColumnToOffset(document, line, column);
                            editor.getCaretModel().moveToOffset(offset);
                            CommandProcessor.getInstance().executeCommand(project, new Runnable() {
                                @Override
                                public void run() {
                                    final CodeCompletionHandlerBase handler = new CodeCompletionHandlerBase(CompletionType.BASIC) {

                                        @Override
                                        protected void completionFinished(@NotNull CompletionProgressIndicator indicator,
                                                                          boolean hasModifiers) {
                                            CompletionServiceImpl.setCompletionPhase(new CompletionPhase.ItemsCalculated(indicator));
                                            completionCallback.completionFinished(indicator.getParameters(), indicator, document);
                                        }
                                    };

                                    handler.invokeCompletion(project, editor);
                                }
                            }, null, null);
                        }
                    }
                }
            }
        });
    }

    public static LightVirtualFile createDummyVirtualFile(Project project, String contents, PsiFile original) {
        return new LightVirtualFile(original.getName(), original.getFileType(), contents);
    }

    public static PsiFile createDummyPsiFile(Project project, String contents, PsiFile original) {
        final PsiFileFactory factory = PsiFileFactory.getInstance(project);
        final LightVirtualFile virtualFile = createDummyVirtualFile(project, contents, original);

        final PsiFile psiFile = ((PsiFileFactoryImpl)factory).trySetupPsiForFile(virtualFile, original.getLanguage(), false, true);
        assert psiFile != null;
        return psiFile;
    }

    public static int lineAndColumnToOffset(Document document, int line, int column) {
        return document.getLineStartOffset(line) + column;
    }

    @Nullable
    public static PsiFile findTargetFile(@NotNull String path) {
        Pair<VirtualFile, Project> data = new File(path).isAbsolute() ? findByAbsolutePath(path) : findByRelativePath(path);
        return data != null ? PsiManager.getInstance(data.second).findFile(data.first) : null;
    }

    @Nullable
    public static Pair<VirtualFile, Project> findByAbsolutePath(@NotNull String path) {
        File file = new File(FileUtil.toSystemDependentName(path));
        if (file.exists()) {
            VirtualFile vFile = findVirtualFile(file);
            if (vFile != null) {
                Project project = ProjectLocator.getInstance().guessProjectForFile(vFile);
                if (project != null) {
                    return Pair.create(vFile, project);
                }
            }
        }

        return null;
    }

    @Nullable
    public static Pair<VirtualFile, Project> findByRelativePath(@NotNull String path) {
        Project[] projects = ProjectManager.getInstance().getOpenProjects();
        String localPath = FileUtil.toSystemDependentName(path);

        for (Project project : projects) {
            File file = new File(project.getBasePath(), localPath);
            if (file.exists()) {
                VirtualFile vFile = findVirtualFile(file);
                return vFile != null ? Pair.create(vFile, project) : null;
            }
        }

        for (Project project : projects) {
            for (VcsRoot vcsRoot : ProjectLevelVcsManager.getInstance(project).getAllVcsRoots()) {
                VirtualFile root = vcsRoot.getPath();
                if (root != null) {
                    File file = new File(FileUtil.toSystemDependentName(root.getPath()), localPath);
                    if (file.exists()) {
                        VirtualFile vFile = findVirtualFile(file);
                        return vFile != null ? Pair.create(vFile, project) : null;
                    }
                }
            }
        }

        return null;
    }

    @Nullable
    public static VirtualFile findVirtualFile(@NotNull final File file) {
        return ApplicationManager.getApplication().runWriteAction(new Computable<VirtualFile>() {
            @Nullable
            @Override
            public VirtualFile compute() {
                return LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
            }
        });
    }

    public static interface CompletionCallback {
        void completionFinished(@NotNull CompletionParameters parameters,
                                @NotNull CompletionProgressIndicator indicator,
                                @NotNull Document document);
    }
}