package com.karateca.ddescriber;

import com.intellij.find.FindResult;
import com.intellij.openapi.editor.Document;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Andres Dominguez.
 */
public class Hierarchy {
  private final Document document;
  private final TestFindResult closest;
  private final List<TestFindResult> testFindResults;

  public Hierarchy(Document document, List<FindResult> findResults, int caretOffset) {
    this.document = document;
    testFindResults = new ArrayList<TestFindResult>();
    for (FindResult findResult : findResults) {
      TestFindResult result = new TestFindResult(document, findResult);
      testFindResults.add(result);
    }

    this.closest = getClosestTestFromCaret(caretOffset);
  }

  /**
   * Get the closest unit test or suite from the current caret position.
   *
   * @param caretOffset The current caret position in the editor.
   * @return The closest test or suite.
   */
  private TestFindResult getClosestTestFromCaret(int caretOffset) {
    int lineNumber = document.getLineNumber(caretOffset) + 1;
    TestFindResult closest = null;
    int minDistance = Integer.MAX_VALUE;

    // Get the closest unit test or suite from the current caret.
    for (TestFindResult testFindResult : testFindResults) {
      int distance = Math.abs(lineNumber - testFindResult.getLineNumber());
      if (distance < minDistance) {
        closest = testFindResult;
        minDistance = distance;
      } else {
        return closest;
      }
    }

    return closest;
  }

  public List<TestFindResult> getUnitTestsForCurrentDescribe() {
    List<TestFindResult> matches = new ArrayList<TestFindResult>();

    int index = testFindResults.indexOf(closest);

    matches.add(closest);

    if (!closest.isDescribe()) {
      // The closest match is an 'it()'. Add all the elements before and after
      // with the same indentation
      addAllElementsWithSameIndentation(matches, index, closest.getIndentation());
    } else if (testFindResults.size() > index + 1) {
      // The closest match is a 'describe()'. Add all the elements inside this
      // describe.
      int indentation = testFindResults.get(index + 1).getIndentation();
      addAllElementsWithSameIndentation(matches, index, indentation);
    }

    // Mark excluded.
    int includedCount = 0;
    for (TestFindResult findResult : matches) {
      includedCount += findResult.isMarkedForRun() ? 1 : 0;
    }

    if (includedCount > 0) {
      for (TestFindResult findResult : matches) {
        findResult.setExcluded(!findResult.isMarkedForRun());
      }
    }

    addParentElements(matches, index);

    return matches;
  }

  /**
   * Add all the it() and describe() elements before and after with the same indentation.
   *
   * @param matches     the elements in the current describe block.
   * @param pivotIndex  the index of the closest it() or describe().
   * @param indentation filter elements before and after with this indentation value.
   */
  private void addAllElementsWithSameIndentation(List<TestFindResult> matches, int pivotIndex, int indentation) {
    // Add elements before with the same indentation.
    for (int i = pivotIndex - 1; i >= 0; i--) {
      TestFindResult element = testFindResults.get(i);
      if (element.getIndentation() != indentation) {
        break;
      }
      matches.add(0, element);
    }

    // Add elements after with the same indentation.
    for (int i = pivotIndex + 1; i < testFindResults.size(); i++) {
      TestFindResult element = testFindResults.get(i);

      if (element.getIndentation() != indentation) {
        break;
      }
      matches.add(element);
    }
  }

  /**
   * Add the parent elements to the current set of matched elements.
   *
   * @param matches    the elements in the current describe block.
   * @param pivotIndex the index of the closest it() or describe().
   */
  private void addParentElements(List<TestFindResult> matches, int pivotIndex) {
    int currentIndentation = closest.getIndentation();
    for (int i = pivotIndex - 1; i >= 0; i--) {
      TestFindResult element = testFindResults.get(i);
      if (element.getIndentation() < currentIndentation) {
        matches.add(0, element);
        currentIndentation = element.getIndentation();
      }
    }
  }

  public TestFindResult getClosest() {
    return closest;
  }

  public List<TestFindResult> getMarkedElements() {
    ArrayList<TestFindResult> results = new ArrayList<TestFindResult>();

    for (TestFindResult element : testFindResults) {
      if (element.isMarkedForRun()) {
        results.add(element);
      }
    }

    return results;
  }

  public List<TestFindResult> getAllUnitTests() {
    return testFindResults;
  }
}

@@ -4694,24 +4694,123 @@
 itTests() %7B%0A
+    for (TestFindResult findResult : testFindResults) %7B%0A      findResult.setExcluded(false);%0A    %7D%0A
     return t

Clean before showing