/*
 * Copyright 2000-2016 JetBrains s.r.o.
 *
 * 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 com.intellij.ide.bookmarks;

import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.impl.AbstractEditorTest;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.vcs.changes.ChangeListManagerImpl;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.testFramework.LeakHunter;
import com.intellij.testFramework.TestFileType;
import org.jetbrains.annotations.NonNls;
import org.picocontainer.ComponentAdapter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author Denis Zhdanov
 * @since 12/27/10 1:43 PM
 */
public class BookmarkManagerTest extends AbstractEditorTest {
  private final List<Bookmark> myBookmarks = new ArrayList<>();
  
  @Override
  protected void tearDown() throws Exception {
    for (Bookmark bookmark : myBookmarks) {
      getManager().removeBookmark(bookmark);
    }
    myBookmarks.clear();
    super.tearDown();
  }

  public void testWholeTextReplace() throws IOException {
    @NonNls String text =
      "public class Test {\n" +
      "    public void test() {\n" +
      "        int i = 1;\n" +
      "    }\n" +
      "}";
    init(text, TestFileType.TEXT);

    addBookmark(2);
    List<Bookmark> bookmarksBefore = getManager().getValidBookmarks();
    assertEquals(1, bookmarksBefore.size());

    new WriteCommandAction.Simple(getProject()) {
      @Override
      protected void run() throws Throwable {
        myEditor.getDocument().setText(text);
      }
    }.execute().throwException();

    List<Bookmark> bookmarksAfter = getManager().getValidBookmarks();
    assertEquals(1, bookmarksAfter.size());
    assertSame(bookmarksBefore.get(0), bookmarksAfter.get(0));
    for (Bookmark bookmark : bookmarksAfter) {
      checkBookmarkNavigation(bookmark);
    }
  }
  
  public void testBookmarkLineRemove() throws IOException {
    List<ComponentAdapter> adapters = getProject().getPicoContainer().getComponentAdaptersOfType(ChangeListManagerImpl.class);
    System.out.println(adapters.size() + " adapters:");
    for (ComponentAdapter adapter : adapters) {
      System.out.println(adapter);
    }

    @NonNls String text =
      "public class Test {\n" +
      "    public void test() {\n" +
      "        int i = 1;\n" +
      "    }\n" +
      "}";
    init(text, TestFileType.TEXT);

    addBookmark(2);
    Document document = myEditor.getDocument();
    myEditor.getSelectionModel().setSelection(document.getLineStartOffset(2) - 1, document.getLineEndOffset(2));
    delete();
    assertTrue(getManager().getValidBookmarks().isEmpty());
  }

  public void testTwoBookmarksOnSameLine1() throws  IOException {
    @NonNls String text =
      "public class Test {\n" +
      "    public void test() {\n" +
      "        int i = 1;\n" +
      "        int j = 1;\n" +
      "    }\n" +
      "}";
    init(text, TestFileType.TEXT);

    addBookmark(2);
    addBookmark(3);
    List<Bookmark> bookmarksBefore = getManager().getValidBookmarks();
    assertEquals(2, bookmarksBefore.size());

    myEditor.getCaretModel().setCaretsAndSelections(
      Collections.singletonList(new CaretState(myEditor.visualToLogicalPosition(new VisualPosition(3, 0)), null, null)));
    backspace();

    List<Bookmark> bookmarksAfter = getManager().getValidBookmarks();
    assertEquals(1, bookmarksAfter.size());
    for (Bookmark bookmark : bookmarksAfter) {
      checkBookmarkNavigation(bookmark);
    }
  }
  public void testTwoBookmarksOnSameLine2() throws  IOException {
    @NonNls String text =
      "public class Test {\n" +
      "    public void test() {\n" +
      "        int i = 1;\n" +
      "        int j = 1;\n" +
      "    }\n" +
      "}";
    init(text, TestFileType.TEXT);

    addBookmark(2);
    addBookmark(3);
    List<Bookmark> bookmarksBefore = getManager().getValidBookmarks();
    assertEquals(2, bookmarksBefore.size());

    myEditor.getCaretModel().setCaretsAndSelections(
      Collections.singletonList(new CaretState(myEditor.visualToLogicalPosition(new VisualPosition(2, myEditor.getDocument().getLineEndOffset(2)+1)), null, null)));
    delete();

    List<Bookmark> bookmarksAfter = getManager().getValidBookmarks();
    assertEquals(1, bookmarksAfter.size());
    for (Bookmark bookmark : bookmarksAfter) {
      checkBookmarkNavigation(bookmark);
    }
    init(text, TestFileType.TEXT);
    myEditor.getCaretModel().setCaretsAndSelections(
      Collections.singletonList(
        new CaretState(myEditor.visualToLogicalPosition(new VisualPosition(2, myEditor.getDocument().getLineEndOffset(2))), null, null)));
    delete();
  }
  
  public void testBookmarkIsSavedAfterRemoteChange() throws IOException {
    @NonNls String text =
      "public class Test {\n" +
      "    public void test() {\n" +
      "        int i = 1;\n" +
      "    }\n" +
      "}";
    init(text, TestFileType.TEXT);
    addBookmark(2);

    new WriteCommandAction.Simple(getProject()) {
      @Override
      protected void run() throws Throwable {
        myEditor.getDocument().setText("111\n222" + text + "333");
      }
    }.execute().throwException();

    List<Bookmark> bookmarks = getManager().getValidBookmarks();
    assertEquals(1, bookmarks.size());
    Bookmark bookmark = bookmarks.get(0);
    assertEquals(3, bookmark.getLine());
    checkBookmarkNavigation(bookmark);
  }

  public void testBookmarkManagerDoesNotHardReferenceDocuments() throws IOException {
    @NonNls String text =
      "public class Test {\n" +
      "}";

    myVFile = WriteAction.compute(() -> {
      VirtualFile file = getSourceRoot().createChildData(null, getTestName(false) + ".txt");
      VfsUtil.saveText(file, text);
      return file;
    });
    PsiDocumentManager.getInstance(getProject()).commitAllDocuments();

    Bookmark bookmark = getManager().addTextBookmark(myVFile, 1, "xxx");
    assertNotNull(bookmark);
    LeakHunter.checkLeak(getManager(), Document.class, doc -> myVFile.equals(FileDocumentManager.getInstance().getFile(doc)));

    Document document = FileDocumentManager.getInstance().getDocument(myVFile);
    assertNotNull(document);
    PsiDocumentManager.getInstance(getProject()).getPsiFile(document); // create psi so that PsiChangeHandler won't leak

    WriteCommandAction.runWriteCommandAction(ourProject, () -> document.insertString(0, "line 0\n"));

    assertEquals(2, bookmark.getLine());

    myEditor = createEditor(myVFile);
    checkBookmarkNavigation(bookmark);
  }
  
  private void addBookmark(int line) {
    Bookmark bookmark = getManager().addTextBookmark(getFile().getVirtualFile(), line, "");
    myBookmarks.add(bookmark);
  }
  
  private static BookmarkManager getManager() {
    return BookmarkManager.getInstance(getProject());
  }

  @Override
  public Object getData(String dataId) {
    if (dataId.equals(OpenFileDescriptor.NAVIGATE_IN_EDITOR.getName())) {
      return myEditor;
    }
    return super.getData(dataId);
  }

  private static void checkBookmarkNavigation(Bookmark bookmark) {
    int line = bookmark.getLine();
    int anotherLine = line;
    if (line > 0) {
      anotherLine--;
    }
    else {
      anotherLine++;
    }
    CaretModel caretModel = myEditor.getCaretModel();
    caretModel.moveToLogicalPosition(new LogicalPosition(anotherLine, 0));
    bookmark.navigate(true);
    assertEquals(line, caretModel.getLogicalPosition().line);
  }
}
