/*
 * 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.openapi.editor.impl;

import com.intellij.openapi.editor.HighlighterColors;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.impl.view.IterationState;
import com.intellij.openapi.editor.markup.HighlighterLayer;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.fileTypes.PlainTextFileType;
import com.intellij.testFramework.EditorTestUtil;
import com.intellij.testFramework.fixtures.EditorMouseFixture;
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;

import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;

public class IterationStateTest extends LightPlatformCodeInsightFixtureTestCase {
  private Color DEFAULT_BACKGROUND;
  private Color CARET_ROW_BACKGROUND;
  private Color SELECTION_BACKGROUND;

  @Override
  public void setUp() throws Exception {
    super.setUp();
    EditorColorsScheme colorsScheme = EditorColorsManager.getInstance().getGlobalScheme();
    DEFAULT_BACKGROUND = colorsScheme.getDefaultBackground();
    CARET_ROW_BACKGROUND = colorsScheme.getColor(EditorColors.CARET_ROW_COLOR);
    SELECTION_BACKGROUND = colorsScheme.getColor(EditorColors.SELECTION_BACKGROUND_COLOR);
    assertEquals(3, new HashSet<>(Arrays.asList(DEFAULT_BACKGROUND, CARET_ROW_BACKGROUND, SELECTION_BACKGROUND)).size());
  }

  public void testBlockSelection() {
    init("aa,<block>bb\n" +
         "cc,d</block>d");
    verifySplitting(true,
                    new Segment(0, 3, Color.BLACK),
                    new Segment(3, 4, Color.WHITE),
                    new Segment(4, 5, Color.BLACK),
                    new Segment(5, 6, Color.BLACK),
                    new Segment(6, 9, Color.BLACK),
                    new Segment(9, 10, Color.WHITE),
                    new Segment(10, 11, Color.BLACK));
  }

  public void testColumnModeBlockSelection() {
    init("a\n" +
         "bbb\n" +
         "ccccc");
    setColumnModeOn();
    mouse().pressAt(0, 2).dragTo(2, 4).release();
    verifySplitting(false,
                    new Segment(0, 1, DEFAULT_BACKGROUND),
                    new Segment(1, 2, DEFAULT_BACKGROUND),
                    new Segment(2, 4, DEFAULT_BACKGROUND),
                    new Segment(4, 5, SELECTION_BACKGROUND),
                    new Segment(5, 6, DEFAULT_BACKGROUND),
                    new Segment(6, 8, CARET_ROW_BACKGROUND),
                    new Segment(8, 10, SELECTION_BACKGROUND),
                    new Segment(10, 11, CARET_ROW_BACKGROUND));
  }

  public void testColumnModeBlockSelectionAtLastNonEmptyLine() {
    init("a\n" +
         "bbb\n" +
         "ccccc");
    setColumnModeOn();
    mouse().pressAt(0, 2).dragTo(2, 6).release();
    verifySplitting(false,
                    new Segment(0, 1, DEFAULT_BACKGROUND),
                    new Segment(1, 2, DEFAULT_BACKGROUND),
                    new Segment(2, 4, DEFAULT_BACKGROUND),
                    new Segment(4, 5, SELECTION_BACKGROUND),
                    new Segment(5, 6, DEFAULT_BACKGROUND),
                    new Segment(6, 8, CARET_ROW_BACKGROUND),
                    new Segment(8, 11, SELECTION_BACKGROUND));
  }

  public void testColumnModeBlockSelectionAtLastEmptyLine() {
    init("a\n" +
         "");
    setColumnModeOn();
    mouse().pressAt(1, 1).dragTo(1, 2).release();
    verifySplitting(false,
                    new Segment(0, 1, DEFAULT_BACKGROUND),
                    new Segment(1, 2, DEFAULT_BACKGROUND));
  }

  public void testColumnModeBlockSelectionAtEmptyLines() {
    init("\n");
    setColumnModeOn();
    mouse().pressAt(0, 1).dragTo(1, 2).release();
    verifySplitting(false,
                    new Segment(0, 1, DEFAULT_BACKGROUND));
  }

  public void testColumnModeSelectionWithCurrentBreakpointHighlighting() {
    init("line1\n" +
         "line2");
    setColumnModeOn();

    Color breakpointColor = Color.RED;
    myFixture.getEditor().getMarkupModel().addLineHighlighter(0,
                                                              HighlighterLayer.CARET_ROW + 1,
                                                              new TextAttributes(null, breakpointColor, null, null, Font.PLAIN));
    Color currentDebuggingLineColor = Color.CYAN;
    myFixture.getEditor().getMarkupModel().addLineHighlighter(0,
                                                              HighlighterLayer.SELECTION - 1,
                                                              new TextAttributes(null, currentDebuggingLineColor, null, null, Font.PLAIN));

    mouse().pressAt(0, 4).dragTo(0, 6).release();
    verifySplitting(false,
                    new Segment(0, 4, currentDebuggingLineColor),
                    new Segment(4, 5, SELECTION_BACKGROUND),
                    new Segment(5, 6, currentDebuggingLineColor),
                    new Segment(6, 11, DEFAULT_BACKGROUND));
  }

  public void testLinesInRange() {
    init("     line1\n" +
         "     line2");

    Color breakpointColor = Color.RED;
    myFixture.getEditor().getMarkupModel().addLineHighlighter(0,
                                                              HighlighterLayer.CARET_ROW + 1,
                                                              new TextAttributes(null, breakpointColor, null, null, Font.PLAIN));

    verifySplitting(false,
                    new Segment(0, 5, breakpointColor),
                    new Segment(5, 10, breakpointColor),
                    new Segment(10, 11, breakpointColor),
                    new Segment(11, 16, DEFAULT_BACKGROUND),
                    new Segment(16, 21, DEFAULT_BACKGROUND));
  }
  
  public void testBoldDefaultFont() {
    init("abc");
    myFixture.getEditor().getColorsScheme().setAttributes(HighlighterColors.TEXT, 
                                                          new TextAttributes(Color.black, Color.white, null, null, Font.BOLD));
    IterationState it = new IterationState((EditorEx)myFixture.getEditor(), 0, 3, null, false, false, true, false);
    assertFalse(it.atEnd());
    assertEquals(0, it.getStartOffset());
    assertEquals(3, it.getEndOffset());
    TextAttributes attributes = it.getMergedAttributes();
    assertEquals(Font.BOLD, attributes.getFontType());
  }

  private void verifySplitting(boolean checkForegroundColor, @NotNull Segment... expectedSegments) {
    EditorEx editor = (EditorEx)myFixture.getEditor();
    IterationState.CaretData caretData = IterationState.createCaretData(editor);
    IterationState iterationState = new IterationState(editor, 0, editor.getDocument().getTextLength(),
                                                       caretData, false, false, true, false);
    List<Segment> actualSegments = new ArrayList<>();
    do {
      Segment segment = new Segment(iterationState.getStartOffset(),
                                    iterationState.getEndOffset(),
                                    checkForegroundColor ? iterationState.getMergedAttributes().getForegroundColor()
                                                         : iterationState.getMergedAttributes().getBackgroundColor());
      actualSegments.add(segment);
      iterationState.advance();
    }
    while (!iterationState.atEnd());

    Assert.assertArrayEquals(expectedSegments, actualSegments.toArray());
  }

  private void init(String text) {
    myFixture.configureByText(PlainTextFileType.INSTANCE, text);
    EditorTestUtil.setEditorVisibleSize(myFixture.getEditor(), 1000, 1000);
  }

  private void setColumnModeOn() {
    ((EditorEx)myFixture.getEditor()).setColumnMode(true);
  }

  private EditorMouseFixture mouse() {
    return new EditorMouseFixture((EditorImpl)myFixture.getEditor());
  }

  private static class Segment {
    private final int start;
    private final int end;
    private final Color color;

    private Segment(int start, int end, Color color) {
      this.start = start;
      this.end = end;
      this.color = color;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      Segment segment = (Segment)o;

      if (end != segment.end) return false;
      if (start != segment.start) return false;
      if (color != null ? !color.equals(segment.color) : segment.color != null) return false;

      return true;
    }

    @Override
    public int hashCode() {
      int result = start;
      result = 31 * result + end;
      result = 31 * result + (color != null ? color.hashCode() : 0);
      return result;
    }

    @Override
    public String toString() {
      return "Segment{" +
             "start=" + start +
             ", end=" + end +
             ", color=" + color +
             '}';
    }
  }
}
