
package com.intellij.openapi.editor.actions;

import com.intellij.codeInsight.hint.HintManager;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.editor.actionSystem.EditorAction;
import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;

/**
 * @author yole
 */
public abstract class ConvertIndentsActionBase extends EditorAction {
  protected ConvertIndentsActionBase() {
    super(null);
    setupHandler(new Handler());
  }

  public static int convertIndentsToTabs(Document document, int tabSize, TextRange textRange) {
    return processIndents(document, tabSize, textRange, tabIndentBuilder);
  }

  public static int convertIndentsToSpaces(Document document, int tabSize, TextRange textRange) {
    return processIndents(document, tabSize, textRange, spaceIndentBuilder);
  }

  private interface IndentBuilder {
    String buildIndent(int length, int tabSize);
  }

  private static int processIndents(Document document, int tabSize, TextRange textRange, IndentBuilder indentBuilder) {
    int changedLines = 0;
    int startLine = document.getLineNumber(textRange.getStartOffset());
    int endLine = document.getLineNumber(textRange.getEndOffset());
    for (int line = startLine; line <= endLine; line++) {
      int indent = 0;
      final int lineStart = document.getLineStartOffset(line);
      final int lineEnd = document.getLineEndOffset(line);
      int indentEnd = lineEnd;
      for(int offset = Math.max(lineStart, textRange.getStartOffset()); offset < lineEnd; offset++) {
        char c = document.getCharsSequence().charAt(offset);
        if (c == ' ') {
          indent++;
        }
        else if (c == '\t') {
          indent = ((indent / tabSize) + 1) * tabSize;
        }
        else {
          indentEnd = offset;
          break;
        }
      }
      if (indent > 0) {
        String oldIndent = document.getCharsSequence().subSequence(lineStart, indentEnd).toString();
        String newIndent = indentBuilder.buildIndent(indent, tabSize);
        if (!oldIndent.equals(newIndent)) {
          document.replaceString(lineStart, indentEnd, newIndent);
          changedLines++;
        }
      }
    }
    return changedLines;
  }

  private static IndentBuilder tabIndentBuilder = new IndentBuilder() {
    @Override
    public String buildIndent(int length, int tabSize) {
      return StringUtil.repeatSymbol('\t', length / tabSize) + StringUtil.repeatSymbol(' ', length % tabSize);
    }
  };

  private static IndentBuilder spaceIndentBuilder = new IndentBuilder() {
    @Override
    public String buildIndent(int length, int tabSize) {
      return StringUtil.repeatSymbol(' ', length);
    }
  };

  protected abstract int performAction(Editor editor, TextRange textRange);

  private class Handler extends EditorWriteActionHandler {
    @Override
    public void executeWriteAction(final Editor editor, DataContext dataContext) {
      final SelectionModel selectionModel = editor.getSelectionModel();
      int changedLines = 0;
      if (selectionModel.hasSelection()) {
        changedLines = performAction(editor, new TextRange(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd()));
      }
      else {
        changedLines += performAction(editor, new TextRange(0, editor.getDocument().getTextLength()));
      }
      if (changedLines == 0) {
        HintManager.getInstance().showInformationHint(editor, "All lines already have requested indentation");
      }
      else {
        HintManager.getInstance().showInformationHint(editor, "Changed indentation in " + changedLines + (changedLines == 1 ? " line" : " lines"));
      }
    }
  }
}
