package de.flo56958.minetinker.commands;

import de.flo56958.minetinker.commands.subs.*;
import de.flo56958.minetinker.modifiers.ModManager;
import de.flo56958.minetinker.modifiers.Modifier;
import de.flo56958.minetinker.utils.ChatWriter;
import de.flo56958.minetinker.utils.LanguageManager;
import de.flo56958.minetinker.api.SubCommand;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.World;
import org.bukkit.command.BlockCommandSender;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

public class CommandManager implements TabExecutor {

	private final HashMap<String, SubCommand> map = new HashMap<>();
	private final ArrayList<String> cmds = new ArrayList<>();

	public CommandManager() {
		ArrayList<SubCommand> commands = new ArrayList<>();
		commands.add(new GiveCommand());
		commands.add(new ModifierListCommand());
		commands.add(new AddModifierCommand());
		commands.add(new CheckUpdateCommand());
		commands.add(new GiveModifierItemCommand());
		commands.add(new ConvertCommand());
		commands.add(new AddExpCommand());
		commands.add(new InfoCommand());
		commands.add(new ReloadCommand());
		commands.add(new NameCommand());
		commands.add(new RemoveModifierCommand());
		commands.add(new EditConfigurationCommand());
		commands.add(new ItemStatisticsCommand());

		commands.forEach(this::registerSubcommand);

		cmds.sort(String::compareToIgnoreCase);
	}

	public void registerSubcommand(SubCommand sub) {
		cmds.add(sub.getName());
		for (String alias : sub.getAliases(true)) {
			if (map.putIfAbsent(alias, sub) != null) {
				ChatWriter.logError(""); //make error if duplicate subcommand alias
			}
		}
	}

	@Override
	public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String s,
							 @NotNull String[] args) {
		if (!(sender.hasPermission("minetinker.commands.main"))) {
			sendError(sender, LanguageManager.getString("Commands.Failure.Cause.NoPermission"));
			return true;
		}

		if (args.length <= 0) {
			sendError(sender, LanguageManager.getString("Commands.Failure.Cause.InvalidArguments"));
			sendHelp(sender, null);
			return true;
		}

		if (args[0].equalsIgnoreCase("help") || args[0].equalsIgnoreCase("?")) {
			sendHelp(sender, args.length >= 2 ? map.get(args[1].toLowerCase()) : null);
			return true;
		}

		SubCommand sub = map.get(args[0].toLowerCase());
		if (sub == null) {
			sendError(sender, LanguageManager.getString("Commands.Failure.Cause.UnknownCommand"));
			sendHelp(sender, null);
			return true;
		}

		parseArguments(sender, sub, args);

		//@all / @allworld
		{
			int index = -1;
			boolean worldOnly = false;
			for (int i = 0; i < args.length; i++) {
				List<ArgumentType> types = sub.getArgumentsToParse().get(i);
				if (types != null && types.contains(ArgumentType.PLAYER)) {
					if (args[i].startsWith("@aw")) {
						index = i;
						worldOnly = true;
						break;
					} else if (args[i].startsWith("@a")) {
						index = i;
						break;
					}
				}
			}
			if (index != -1) {
				Collection<? extends Player> players;
				if (worldOnly) {
					World world = null;
					if (sender instanceof BlockCommandSender) {
						world = ((BlockCommandSender) sender).getBlock().getWorld();
					} else if (sender instanceof Entity) {
						world = ((Entity) sender).getWorld();
					}

					if (world == null) return true;
					players = world.getPlayers();
				} else {
					players = Bukkit.getOnlinePlayers();
				}

				boolean ret = true;
				for (Player player : players) {
					String[] arg = args.clone();
					arg[index] = player.getName();
					ret = ret && onCommand(sender, command, s, arg);
				}
			}
		}

		if (!sender.hasPermission(sub.getPermission())) {
			sendError(sender, LanguageManager.getString("Commands.Failure.Cause.NoPermission"));
			return true;
		}

		return sub.onCommand(sender, args);
	}

	@Override
	public @Nullable List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command,
												@NotNull String s, @NotNull String[] args) {
		List<String> result = new ArrayList<>();
		if (args.length == 0) {
			return result;
		} else if (args.length == 1) {
			result.addAll(cmds);
			ArrayList<String> toRemove = new ArrayList<>();
			for (String st : result) {
				SubCommand sub = map.get(st);
				if (sub != null) {
					if (!commandSender.hasPermission(sub.getPermission())) {
						toRemove.add(st);
					}
				} else
					toRemove.add(st);
			}
			result.removeAll(toRemove);
		} else {
			SubCommand sub = map.get(args[0].toLowerCase());
			if (sub == null) {
				if (args.length == 2 && args[0].equalsIgnoreCase("help")) { //help auto-complete
					result.addAll(cmds);
					ArrayList<String> toRemove = new ArrayList<>();
					for (String st : result) {
						sub = map.get(st);
						if (sub != null) {
							if (!commandSender.hasPermission(sub.getPermission())) {
								toRemove.add(st);
							}
						} else
							toRemove.add(st);
					}
					result.removeAll(toRemove);
				}
				return result;
			}
			if (commandSender.hasPermission(sub.getPermission())) {
				result = sub.onTabComplete(commandSender, args);
			}
		}
		if (result != null) {
			if (args.length < 2) result.add("help");
			//filter out any command that is not the beginning of the typed command
			List<String> tempResult = new ArrayList<>();
			for (String str : result) {
				tempResult.add(ChatColor.stripColor(str));
			}
			result = tempResult;
			result.removeIf(str -> !str.toLowerCase().startsWith(args[args.length - 1].toLowerCase()));
			result.sort(String::compareToIgnoreCase);
		}
		return result;
	}

	private void parseArguments(@NotNull CommandSender sender, @NotNull SubCommand sub, String[] args) {
		for (int i = 1; i < args.length; i++) {
			List<ArgumentType> atypes = sub.getArgumentsToParse().get(i);
			if (atypes == null) continue;

			for (ArgumentType type : atypes) {
				switch (type) {
					case COLORED_TEXT:
						break;
					case PLAYER:
						if (args[i].startsWith("@p")) {
							if (sender instanceof Player) {
								args[i] = sender.getName();
							} else if (sender instanceof BlockCommandSender) {
								List<Player> players = ((BlockCommandSender) sender).getBlock()
																					.getWorld().getPlayers();
								double distance = Double.POSITIVE_INFINITY;

								for (Player player : players) {
									double newDist = player.getLocation().distance(((BlockCommandSender) sender)
																					.getBlock().getLocation());
									if (newDist < distance) {
										distance = newDist;
										args[i] = player.getName();
									}
								}
							}
						} else if (args[i].startsWith("@rw")) { //random Player in World
							World world = null;
							if (sender instanceof BlockCommandSender) {
								world = ((BlockCommandSender) sender).getBlock().getWorld();
							} else if (sender instanceof Entity) {
								world = ((Entity) sender).getWorld();
							}

							if (world == null) continue;

							List<Player> players = world.getPlayers();
							args[i] = players.get(new Random().nextInt(players.size())).getName();
						} else if (args[i].startsWith("@r")) {
							Collection<? extends Player> onlinePlayers = Bukkit.getOnlinePlayers();
							args[i] = onlinePlayers.toArray(new Player[0])
									[new Random().nextInt(onlinePlayers.size())].getName();
						} else {
							try {
								UUID uuid = UUID.fromString(args[i]);
								Player player = Bukkit.getPlayer(uuid);
								if (player != null) args[i] = player.getName();
								else {
									sendError(sender, LanguageManager.getString("Commands.Failure.Cause.PlayerNotFound")
											.replace("%p", args[i]));
								}
							} catch (IllegalArgumentException ignored) {
							}
						}
						break;
					case RANDOM_NUMBER:
						//like range 1-5 (inclusive), or from 1,2,3,6,7,8, or all of them combined like 1-5,7-9,11,13
						String[] rules = args[i].split(",");
						int index = new Random().nextInt(rules.length);
						boolean isMod = false;
						for (Modifier mod : ModManager.instance().getAllMods()) {
							if (mod.getName().replace(" ", "_").equals(rules[index])) {
								isMod = true;
								break;
							}
						}
						if (!isMod && rules[index].indexOf('-') != -1) {
							String[] nums = rules[index].split("-");
							if (nums.length != 2) {
								sendError(sender,
										LanguageManager.getString("Commands.Failure.Cause.NumberFormatException"));
								break;
							}
							try{
								int min = Integer.parseInt(nums[0]);
								int max = Integer.parseInt(nums[1]);
								int rand = new Random().nextInt(max - min) + min;
								args[i] = String.valueOf(rand);
							} catch (NumberFormatException e) {
								sendError(sender,
										LanguageManager.getString("Commands.Failure.Cause.NumberFormatException"));
							}
						} else {
							args[i] = rules[index];
						}
						break;
					default:
				}
			}
		}
	}

	public static void sendError(CommandSender sender, String cause) {
		ChatWriter.sendMessage(sender, ChatColor.RED, LanguageManager.getString("Commands.Failure.Main")
				.replaceAll("%cause", cause));
	}

	private void sendHelp(CommandSender sender, @Nullable SubCommand command) {
		if (command == null) {
			int index = 1;
			for (String cmd : cmds) {
				SubCommand sub = map.get(cmd);
				if (sender.hasPermission(sub.getPermission()))
				ChatWriter.sendMessage(sender, ChatColor.WHITE, index++ + ". " + cmd + " "
						+ sub.getAliases(false).toString() + ": " + sub.syntax());
			}
			return;
		}

		if (sender.hasPermission(command.getPermission()))
			ChatWriter.sendMessage(sender, ChatColor.WHITE, command.syntax());
	}
}