package org.ssssssss.script;

import org.ssssssss.script.parsing.Span;
import org.ssssssss.script.parsing.TokenStream;

/**
 * All errors reported by the library go through the static functions of this class.
 */
public class MagicScriptError {

	/**
	 * <p>
	 * Create an error message based on the provided message and stream, highlighting the line on which the error happened. If the
	 * stream has more tokens, the next token will be highlighted. Otherwise the end of the source of the stream will be
	 * highlighted.
	 * </p>
	 *
	 * <p>
	 * Throws a {@link RuntimeException}
	 * </p>
	 */
	public static void error(String message, TokenStream stream) {
		if (stream.hasMore()) {
			error(message, stream.consume().getSpan());
		} else {
			error(message, stream.getPrev().getSpan());
		}
	}

	/**
	 * Create an error message based on the provided message and location, highlighting the location in the line on which the
	 * error happened. Throws a {@link ScriptException}
	 **/
	public static void error(String message, Span location, Throwable cause) {

		Span.Line line = location.getLine();
		String errorMessage = "Script Error : " + message + "\n\n";
		errorMessage += line.getText();
		errorMessage += "\n";
		int errorStart = location.getStart() - line.getStart();
		int errorEnd = errorStart + location.getText().length() - 1;
		for (int i = 0, n = line.getText().length(); i < n; i++) {
			boolean useTab = line.getText().charAt(i) == '\t';
			errorMessage += i >= errorStart && i <= errorEnd ? "^" : useTab ? "\t" : " ";
		}

		if (cause == null) {
			throw new ScriptException(errorMessage, message, line);
		} else {
			throw new ScriptException(errorMessage, message, cause, line);
		}
	}

	/**
	 * Create an error message based on the provided message and location, highlighting the location in the line on which the
	 * error happened. Throws a {@link ScriptException}
	 **/
	public static void error(String message, Span location) {
		error(message, location, null);
	}

	public static class DebugTimeoutException extends RuntimeException{

		public DebugTimeoutException() {
			super("debug超时");
		}

		public DebugTimeoutException(Throwable cause) {
			super(cause);
		}
	}

	public static class ScriptException extends RuntimeException {
		private static final long serialVersionUID = 1L;
		private final String errorMessage;
		private final String simpleMessage;
		private final Span.Line line;

		public ScriptException(String errorMessage, String simpleMessage, Span.Line line) {
			super(errorMessage);
			this.errorMessage = errorMessage;
			this.simpleMessage = simpleMessage;
			this.line = line;
		}

		public ScriptException(String errorMessage, Span.Line line) {
			this(errorMessage, errorMessage, line);
		}

		public ScriptException(String errorMessage) {
			this(errorMessage, errorMessage, null);
		}

		public ScriptException(String message, String simpleMessage, Throwable cause, Span.Line line) {
			super(message, cause);
			this.simpleMessage = simpleMessage;
			this.errorMessage = message;
			this.line = line;
		}

		public String getSimpleMessage() {
			return simpleMessage;
		}

		public Span.Line getLine() {
			return line;
		}

		@Override
		public String getMessage() {
			StringBuilder builder = new StringBuilder();

			if (getCause() == null || getCause() == this) {
				return super.getMessage();
			}

			builder.append(errorMessage, 0, errorMessage.indexOf('\n'));
			builder.append("\n");

			Throwable cause = getCause();
			while (cause != null && cause != this) {
				if (cause instanceof ScriptException) {
					ScriptException ex = (ScriptException) cause;
					if (ex.getCause() == null || ex.getCause() == ex) {
						builder.append(ex.errorMessage);
					} else {
						builder.append(ex.errorMessage, 0, ex.errorMessage.indexOf('\n'));
					}
					builder.append("\n");
				}
				cause = cause.getCause();
			}
			return builder.toString();
		}
	}

	public static class StringLiteralException extends RuntimeException {

		private static final long serialVersionUID = 1L;

	}
}