package org.jetbrains.plugins.cucumber.codeinsight;

import com.intellij.codeInsight.editorActions.TypedHandlerDelegate;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorModificationUtil;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.cucumber.psi.GherkinFileType;
import org.jetbrains.plugins.cucumber.psi.GherkinTable;
import org.jetbrains.plugins.cucumber.psi.GherkinTableRow;

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

public class GherkinTypedHandler extends TypedHandlerDelegate {

  public static final char PIPE = '|';

  /**
   * Looks for Example table where the caret is located
   * @param element to start looking for
   * @return GherkinTable or null
   */
  @Nullable
  private static GherkinTable getTable(@NotNull PsiElement element) {
    while (element != null) {
      if (element instanceof GherkinTable) {
        break;
      }
      element = element.getParent();
    }
    return (GherkinTable)element;
  }

  /**
   * Looks for GherkinTableRow where the caret is located
   * @return GherkinTableRow if caret is inside row, null otherwise
   */
  @Nullable
  private static GherkinTableRow findCurrentRow(@NotNull final Editor editor, @NotNull final PsiFile file) {
    int offset = editor.getCaretModel().getOffset();
    PsiElement cursorElement = file.findElementAt(offset - 1);
    if (cursorElement == null) {
      return null;
    }

    GherkinTable element = getTable(cursorElement);
    if (element == null) {
      cursorElement = file.findElementAt(offset - cursorElement.getTextLength());
      if (cursorElement == null) {
        return null;
      }
      element = getTable(cursorElement);
      if (element == null) {
        return null;
      }
    }

    final GherkinTable table = element;
    final int tableOffset = table.getTextOffset();
    final int caretOffsetInParent = editor.getCaretModel().getOffset() - tableOffset;

    final List<GherkinTableRow> rowList = new ArrayList<>();
    if (table.getHeaderRow() != null) {
      rowList.add(table.getHeaderRow());
    }
    rowList.addAll(table.getDataRows());

    for (int i = 0; i < rowList.size() - 1; i++) {
      final GherkinTableRow row = rowList.get(i);
      final GherkinTableRow nextRow = rowList.get(i + 1);

      final int start = row.getStartOffsetInParent();
      final int end = start + row.getTextLength();

      if (start <= caretOffsetInParent && caretOffsetInParent <= end) {
        return row;
      }
      if (end < caretOffsetInParent && caretOffsetInParent <= nextRow.getStartOffsetInParent()) {
        return row;
      }
    }
    return rowList.get(rowList.size() - 1);
  }

  /**
   * Calculates GherkinTableRow above currentRow
   * @param currentRow row to start
   * @return GherkinTableRow (or header) if there is a row above or null otherwise
   */
  @Nullable
  private static GherkinTableRow getPreviousRow(@NotNull final GherkinTableRow currentRow) {
    if (currentRow.getParent() != null && currentRow.getParent() instanceof GherkinTable table) {
      int i = table.getDataRows().indexOf(currentRow);
      if (i > 0) {
        return table.getDataRows().get(i - 1);
      } else if (i == 0) {
        return table.getHeaderRow();
      }
    }
    return null;
  }

  /**
   * Calculates number of column where the caret is located
   * @param row where to look for
   * @return number of column
   */
  private static int getColumnNumber(@NotNull final GherkinTableRow row, final int caretPosition) {
    final String rowText = row.getText();
    final int length = Math.min(caretPosition, rowText.length());
    int count = 0;
    for (int i = 0; i < length; i++) {
      if (rowText.charAt(i) == PIPE) {
        count++;
      }
    }
    return count - 1;
  }

  private static String getSpaceLine(int n) {
    char[] spaces = new char[n];
    Arrays.fill(spaces, ' ');
    return String.valueOf(spaces);
  }

  /**
   * Calculates where next Pipe symbol should be
   * @param row a row above current
   * @return offset in parent that corresponds preferred position of typed pipe symbol
   */
  private static int getPreferredPipeOffset(@NotNull final GherkinTableRow row, final int columnNumber) {
    final String rowText = row.getText();
    int i = 0;
    int passedPipeCount = 0;
    while (i < rowText.length() && passedPipeCount - 2 < columnNumber) {
      if (rowText.charAt(i) == '|') {
        passedPipeCount++;
      }
      i++;
    }
    return (passedPipeCount - 2 == columnNumber) ? i : -1;
  }

  @NotNull
  @Override
  public Result beforeCharTyped(char c,
                                @NotNull Project project,
                                @NotNull Editor editor,
                                @NotNull PsiFile file,
                                @NotNull FileType fileType) {
    if (fileType.equals(GherkinFileType.INSTANCE)) {
      if (c == PIPE) {
        final GherkinTableRow currentRow = findCurrentRow(editor, file);
        if (currentRow != null) {
          final GherkinTableRow previousRow = getPreviousRow(currentRow);
          if (previousRow != null) {
            final int offsetInParent = editor.getCaretModel().getOffset() - currentRow.getTextOffset();
            final int cellNumber = getColumnNumber(currentRow, offsetInParent);
            int rightPosition = getPreferredPipeOffset(previousRow, cellNumber);
            if (offsetInParent < rightPosition - 1) {
              EditorModificationUtil.insertStringAtCaret(editor, getSpaceLine(rightPosition - offsetInParent - 1));
            }
          }
        }
      }
    }

    return super.beforeCharTyped(c, project, editor, file, fileType);
  }
}
