package osmedile.intellij.stringmanip;

import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.CaretAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.UnprotectedUserDataHolder;
import com.intellij.openapi.util.UserDataHolder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import shaded.org.apache.commons.lang3.StringUtils;

import java.util.HashMap;
import java.util.Map;

/**
 * @author Olivier Smedile
 * @version $Id: AbstractStringManipAction.java 62 2008-04-20 11:11:54Z osmedile $
 */
public abstract class AbstractStringManipAction<T> extends MyEditorAction {

	protected final boolean setupHandler;
	protected MyEditorWriteActionHandler<T> myHandler = null;

	protected AbstractStringManipAction() {
		this(true);
	}

	protected AbstractStringManipAction(boolean setupHandler) {
		super(null);
		if (setupHandler) {
			this.setupHandler(myHandler = new MyEditorWriteActionHandler<T>(getActionClass()) {
				@NotNull
				@Override
				protected Pair<Boolean, T> beforeWriteAction(Editor editor, DataContext dataContext) {
					return AbstractStringManipAction.this.beforeWriteAction(editor, dataContext);
				}

				@Override
				protected void executeWriteAction(Editor editor, @Nullable Caret caret, final DataContext dataContext, final T additionalParam) {
					executeMyWriteAction(editor, dataContext, additionalParam);
				}

			});
		}
		this.setupHandler = setupHandler;
	}

	@NotNull
	public Pair<Boolean, T> beforeWriteAction(Editor editor, DataContext dataContext) {
		return new Pair<Boolean, T>(true, null);
	}

	protected final Pair<Boolean, T> stopExecution() {
		return new Pair<Boolean, T>(false, null);
	}

	protected final Pair<Boolean, T> continueExecution(T param) {
		return new Pair<Boolean, T>(true, param);
	}

	protected final Pair<Boolean, T> continueExecution() {
		return new Pair<Boolean, T>(true, null);
	}

	protected void executeMyWriteAction(Editor editor, final DataContext dataContext, final T additionalParam) {
		Map<String, Object> actionContext = new HashMap<>();
		editor.getCaretModel().runForEachCaret(new CaretAction() {
			@Override
			public void perform(Caret caret) {
				executeMyWriteActionPerCaret(caret.getEditor(), caret, actionContext, dataContext, additionalParam);
			}
		});
	}

	protected void executeMyWriteActionPerCaret(Editor editor, Caret caret, Map<String, Object> actionContext, DataContext dataContext, T additionalParam) {
		final SelectionModel selectionModel = editor.getSelectionModel();
		String selectedText = selectionModel.getSelectedText();

		if (selectedText == null) {
			selectSomethingUnderCaret(editor, dataContext, selectionModel);
			selectedText = selectionModel.getSelectedText();

			if (selectedText == null) {
				return;
			}
		}

		String s = transformSelection(editor, actionContext, dataContext, selectedText, additionalParam);
		s = s.replace("\r\n", "\n");
		s = s.replace("\r", "\n");
        editor.getDocument().replaceString(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd(), s);
    }


	protected String transformSelection(Editor editor, Map<String, Object> actionContext, DataContext dataContext, String selectedText, T additionalParam) {
		String[] textParts = selectedText.split("\n");

		for (int i = 0; i < textParts.length; i++) {
			if (!StringUtils.isBlank(textParts[i])) {
				textParts[i] = transformByLine(actionContext, textParts[i]);
			}
		}

		String join = StringUtils.join(textParts, '\n');

		if (selectedText.endsWith("\n")) {
			return join + "\n";
		}
		return join;
	}

	protected boolean selectSomethingUnderCaret(Editor editor, DataContext dataContext, SelectionModel selectionModel) {
		selectionModel.selectLineAtCaret();
		String selectedText = selectionModel.getSelectedText();
		if (selectedText != null && selectedText.endsWith("\n")) {
			selectionModel.setSelection(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd() - 1);
		}
		return true;
	}

	public final String transformByLine( String s) {
		return transformByLine(new HashMap<>(),s );
	}

	public abstract String transformByLine(Map<String, Object> actionContext, String s);
}