package de.yannicpunktdee.yoshibot.command;

import java.util.*;

import de.yannicpunktdee.yoshibot.command.YoshiCommandDistributor.Action;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;;

/**
 * Parst einen Eingabestring, entscheidet ob er ein Kommando ist und zerlegt ihn in seine Bestandteile. Kommandos
 * besitzen einen Status, der Auskunft gibt, ob Fehler beim Parsen des Eingabestrings aufgetreten sind oder der String
 * gar kein Kommando war. Außerdem wird bei erfolgreich geparsten Kommandos eine Aktion festgelegt, die das Kommando
 * ausführen soll, sowie die Argumente mit welcher das Kommando ausgeführt wird. Außerdem enthalten ist eine Referenz
 * auf Ursprungs-User und -TextChannel.
 *
 * @author Yannic Link
 */
public class YoshiCommandContext {
	
	/**
	 * Repräsentiert einen Status, den eine YoshiCommand-Instanz hat, nachdem der Eingabestring eingelesen und geparst
	 * wurde. Sofern der Status nicht OK ist, ist diese Instanz entweder kein Kommando oder besitzt eine fehlerhafte
	 * Syntax.
	 */
	public enum State {
		/**
		 * Das Kommando wurde erfolgreich geparst und besitzt keine Syntaxfehler.
		 */
		OK,
		/**
		 * Der Eingabestring war kein Kommando für den Yoshi-Bot.
		 */
		NO_COMMAND,
		/**
		 * Im Kommando wurde keine Aktion spezifiziert.
		 */
		NO_ACTION,
		/**
		 * Die angegebene Aktion existiert nicht.
		 */
		UNKNOWN_ACTION,
		/**
		 * Das Kommando hatt einen Syntaxfehler.
		 */
		BAD_SYNTAX
	}
	
	/**
	 * Hilfskonstrukt. Beschreibt den Zustand vom Parser.
	 */
	private enum ReadingState {
		/**
		 * Der Parser ist dabei festzustellen, ob es sich um ein Kommando handelt.
		 */
		VERIFYING,
		/**
		 * Zwischenzustand zwischen VERIFYING und READING_ACTION.
		 */
		AFTER_VERIFY,
		/**
		 * Der Parser liest die Action ein.
		 */
		READING_ACTION,
		/**
		 * Zustand nach READING_ACTION, READING_VALUE oder READING_STRING).
		 */
		INTERMEDIATE,
		/**
		 * Der Parser ist dabei einen Argumentenkey einzulesen.
		 */
		READING_KEY,
		/**
		 * Der Parser hat gerade einen Argumentenkey eingelesen.
		 */
		AFTER_KEY,
		/**
		 * Der Parser liest gerade einen Argumentenwert ein.
		 */
		READING_VALUE,
		/**
		 * Der Parser liest gerade einen zusammenhängenden Argumentenwert ein.
		 */
		READING_STRING
	}
	
	;
	
	
	/**
	 * Das Präfix, mit dem Yoshi-Bot-Kommandos beginnen.
	 */
	public static final String PREFIX = "::yoshi";
	
	/**
	 * Der (Fehler-)Status, den der Parser nach Einlesen des Eingabestrings angenommen hat.
	 */
	private State state;
	/**
	 * Die im Eingabesting spezifizierte Aktion.
	 */
	private Action action;
	/**
	 * Eine Map, die die Key-Werte der Argumente (ohne Bindestrich) auf dessen Werte abbildet.
	 */
	private Map<String, String> arguments;
	
	protected List<String> argumentList;
	
	private MessageReceivedEvent event;
	
	
	/**
	 * Erzeugt aus einem unbearbeiteten Eingabestring ein Kommando. Nach dem parsen enthält die state-Variable den
	 * Endzustand des Parsers.
	 *
	 * @param argumentsString Ein unbearbeiteter Eingabestring
	 */
	public YoshiCommandContext(String argumentsString, MessageReceivedEvent event) {
		this.event = event;
		
		argumentsString = argumentsString.trim();
		
		argumentList = new ArrayList<>();
		argumentList.addAll(Arrays.asList(argumentsString.split(" ")));
		argumentList.remove(0);
		
		arguments = new HashMap<>();
		
		ReadingState readingState = ReadingState.VERIFYING;
		String currentKey = null;
		int startPos = 0;
		int length = argumentsString.length();
		
		for (int position = 0; position < length; position++) {
			char currentChar = argumentsString.charAt(position);
			
			switch (readingState) {
				case VERIFYING:
					if (!Character.isWhitespace(currentChar)) continue;
					if (!argumentsString.substring(0, position).equals(PREFIX)) {
						state = State.NO_COMMAND;
						return;
					}
					readingState = ReadingState.AFTER_VERIFY;
					continue;
				case AFTER_VERIFY:
					if (Character.isWhitespace(currentChar)) continue;
					if (currentChar == '-') {
						state = State.NO_ACTION;
						return;
					}
					startPos = position;
					readingState = ReadingState.READING_ACTION;
					continue;
				case READING_ACTION:
					if (!Character.isWhitespace(currentChar)) continue;
					try {
						action       = Action.valueOf(argumentsString.substring(startPos, position).toUpperCase());
						readingState = ReadingState.INTERMEDIATE;
					} catch (IllegalArgumentException e) {
						state = State.UNKNOWN_ACTION;
						return;
					}
					continue;
				case INTERMEDIATE:
					if (Character.isWhitespace(currentChar)) continue;
					if (currentChar != '-' || currentChar == '"') {
						state = State.BAD_SYNTAX;
						return;
					}
					startPos = position + 1;
					readingState = ReadingState.READING_KEY;
					continue;
				case READING_KEY:
					if (!Character.isWhitespace(currentChar)) continue;
					currentKey = argumentsString.substring(startPos, position);
					readingState = ReadingState.AFTER_KEY;
					continue;
				case AFTER_KEY:
					if (Character.isWhitespace(currentChar)) continue;
					if (currentChar == '-') {
						arguments.put(currentKey, null);
						startPos     = position + 1;
						readingState = ReadingState.READING_KEY;
					} else if (currentChar == '"') {
						startPos     = position + 1;
						readingState = ReadingState.READING_STRING;
					} else {
						startPos     = position;
						readingState = ReadingState.READING_VALUE;
					}
					continue;
				case READING_VALUE:
					if (!Character.isWhitespace(currentChar)) continue;
					arguments.put(currentKey, argumentsString.substring(startPos, position));
					readingState = ReadingState.INTERMEDIATE;
					continue;
				case READING_STRING:
					if (currentChar != '"') continue;
					if (argumentsString.charAt(position - 1) == '\\') continue;
					arguments.put(currentKey, argumentsString.substring(startPos, position));
					readingState = ReadingState.INTERMEDIATE;
					continue;
			}
		}
		
		switch (readingState) {
			case INTERMEDIATE:
				state = State.OK;
				return;
			case VERIFYING:
				if (argumentsString.equals(PREFIX)) {
					action = Action.HELP;
					state  = State.OK;
				} else {
					state = State.NO_COMMAND;
				}
				return;
			case READING_ACTION:
				try {
					action       = Action.valueOf(argumentsString.substring(startPos).toUpperCase());
					readingState = ReadingState.INTERMEDIATE;
				} catch (IllegalArgumentException e) {
					state = State.UNKNOWN_ACTION;
					return;
				}
				state = State.OK;
				return;
			case READING_KEY:
				arguments.put(argumentsString.substring(startPos), null);
				state = State.OK;
				return;
			case READING_VALUE:
				arguments.put(currentKey, argumentsString.substring(startPos));
				state = State.OK;
				return;
			default:
				break;
		}
	}
	
	/**
	 * Prüft, ob der Eingabestring ein valides Kommando war.
	 */
	public boolean isValid() {
		return state.equals(State.OK);
	}
	
	/**
	 * Gibt den (Fehler-)Status des Parsers zurück.
	 */
	public State getState() {
		return state;
	}
	
	/**
	 * Gibt die im Kommando spezifizierte Aktion zurück. null, wenn status fehlerhaft oder kein Kommando.
	 */
	public Action getAction() {
		return action;
	}
	
	/**
	 * Prüft, ob das Kommando mit Argumenten aufgerufen wurde.
	 */
	public boolean hasArguments() {
		return !arguments.isEmpty();
	}
	
	
	public boolean containsArgument(String arg){
		return this.containsArguments(new String[]{arg});
	}
	/**
	 * Prüft, ob alle Key-Werte in der Argumentenliste vorhanden sind.
	 *
	 * @param args Liste von den auf Existenz zu überprüfenden Argumenten.
	 */
	public boolean containsArguments(String[] args) {
		for (String arg : args) {
			if (!arguments.containsKey(arg)) {
				return false;
			}
		}
		return true;
	}
	
	/**
	 * Gibt den Wert eines Arguments zurück.
	 *
	 * @param arg Name des Arguments.
	 */
	public String getArgument(String arg) {
		if (!arguments.containsKey(arg)) return null;
		return arguments.get(arg);
	}
	
	public MessageReceivedEvent getEvent() {
		return event;
	}
	
	public List<String> getArguments() {
		return this.argumentList;
	}
	
}
