/*
 * Copyright (C) 2013-2018 Gonçalo Baltazar <me@goncalomb.com>
 *
 * This file is part of NBTEditor.
 *
 * NBTEditor is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * NBTEditor is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with NBTEditor.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.goncalomb.bukkit.mylib.command;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;

import org.apache.commons.lang.StringUtils;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.Permission;

import com.goncalomb.bukkit.mylib.command.MyCommand.Command;
import com.goncalomb.bukkit.mylib.command.MyCommand.CommandType;

class MySubCommand {

	private Permission _perm;
	private MyCommand _command;
	private Method _exeMethod = null;
	private Method _tabMethod = null;
	private CommandType _type = CommandType.DEFAULT;
	private String _usage;
	private int _minArgs;
	private int _maxArgs;
	private LinkedHashMap<String, MySubCommand> _subCommands = new LinkedHashMap<String, MySubCommand>();

	public MySubCommand() {
		if (this instanceof MyCommand) {
			_command = (MyCommand) this;
		}
	}

	private MySubCommand(MyCommand command) {
		_command = command;
	}

	void setupPermissions(String name, Permission parent) {
		String permName = parent.getName();
		if (permName.endsWith(".*")) {
			permName = permName.substring(0, permName.length() - 2);
		}
		_perm = new Permission(permName + "." + name);
		_perm.addParent(parent, true);
		for (Entry<String, MySubCommand> entry : _subCommands.entrySet()) {
			entry.getValue().setupPermissions(entry.getKey(), _perm);
		}
		Bukkit.getPluginManager().addPermission(_perm);
		parent.recalculatePermissibles();
	}

	void removePermissions() {
		for (MySubCommand command : _subCommands.values()) {
			command.removePermissions();
		}
		Bukkit.getPluginManager().removePermission(_perm);
	}

	boolean addSubCommand(String[] args, int argsIndex, Command config, MyCommand command, Method exeMethod, Method tabMethod) {
		if (args.length == argsIndex) {
			if (_exeMethod == null) {
				_exeMethod = exeMethod;
				_tabMethod = tabMethod;
				_type = config.type();
				_usage = config.usage();
				_minArgs = (config.minargs() >= 0 ? config.minargs() : 0);
				_maxArgs = (config.maxargs() >= 0 ? config.maxargs() : 0);
				if (_minArgs > _maxArgs) {
					_maxArgs = _minArgs;
				}
				return true;
			}
			return false;
		} else {
			MySubCommand subCommand = _subCommands.get(args[argsIndex]);
			if (subCommand == null) {
				subCommand = new MySubCommand(command);
				_subCommands.put(args[argsIndex], subCommand);
			}
			return subCommand.addSubCommand(args, argsIndex + 1, config, command, exeMethod, tabMethod);
		}
	}

	void execute(CommandSender sender, String label, String[] args, int argsIndex) {
		// Find sub-command.
		if (argsIndex < args.length) {
			MySubCommand subCommand = _subCommands.get(args[argsIndex].toLowerCase());
			if (subCommand != null) {
				subCommand.execute(sender, label, args, argsIndex + 1);
				return;
			}
		}
		// Sub-command not found or no more arguments, let's try to run this one.
		if (_exeMethod != null) {
			if (_type.isValidSender(sender)) {
				if (sender.hasPermission(_perm)) {
					int argsLeft = args.length - argsIndex;
					if (argsLeft >= _minArgs && argsLeft <= _maxArgs) {
						if (invokeExeMethod(sender, Arrays.copyOfRange(args, argsIndex,args.length))) {
							return;
						}
					}
				} else {
					sender.sendMessage("§cYou don't have permission to use that command!");
					return;
				}
			} else {
				sender.sendMessage(_type.INVALID_MESSAGE);
				return;
			}
		}
		// Missing arguments or failed command, let's send usage and sub-commands!
		String prefix = "/" + label + " " + (argsIndex > 0 ? StringUtils.join(args, ' ', 0, argsIndex).toLowerCase() + " " : "");
		boolean sentUsage = false;
		if (_exeMethod != null && sender.hasPermission(_perm)) {
			sender.sendMessage(ChatColor.RESET + prefix + _usage);
			sentUsage = true;
		}
		if (sendAllSubCommands(sender, this, prefix) == 0 && !sentUsage) {
			sender.sendMessage("§cYou don't have permission to use that command!");
		}
	}

	private static int sendAllSubCommands(CommandSender sender, MySubCommand command, String prefix) {
		int i = 0;
		for (Entry<String, MySubCommand> subCommandEntry : command._subCommands.entrySet()) {
			MySubCommand subCommand = subCommandEntry.getValue();
			if (subCommand._type.isValidSender(sender) && sender.hasPermission(subCommand._perm)) {
				String newPrefix = prefix + subCommandEntry.getKey() + " ";
				if (subCommand._exeMethod != null) {
					sender.sendMessage(ChatColor.GRAY + newPrefix + subCommand._usage);
					i++;
				}
				i += sendAllSubCommands(sender, subCommand, newPrefix);
			}
		}
		return i;
	}

	List<String> tabComplete(CommandSender sender, String[] args, int argsIndex) {
		// Find sub-command.
		if (argsIndex < args.length) {
			MySubCommand subCommand = _subCommands.get(args[argsIndex].toLowerCase());
			if (subCommand != null) {
				return subCommand.tabComplete(sender, args, argsIndex + 1);
			}
		}
		// Sub-command not found or no more arguments, let's try to run this one.
		int argsLeft = args.length - argsIndex;
		if (_tabMethod != null) {
			if (argsLeft >= _minArgs && argsLeft <= _maxArgs && _type.isValidSender(sender) && sender.hasPermission(_perm)) {
				return invokeTabMethod(sender, Arrays.copyOfRange(args, argsIndex,args.length));
			}
			return null;
		}
		// Tab completion not found, send all sub-commands.
		if (argsLeft == 1) {
			ArrayList<String> allowedCommands = new ArrayList<String>();
			String arg = args[args.length - 1].toLowerCase();
			for (Entry<String, MySubCommand> entry : _subCommands.entrySet()) {
				String name = entry.getKey();
				MySubCommand command = entry.getValue();
				if (name.startsWith(arg) && command._type.isValidSender(sender) && sender.hasPermission(command._perm)) {
					allowedCommands.add(name);
				}
			}
			return allowedCommands;
		}
		return null;
	}

	private boolean invokeExeMethod(CommandSender sender, String[] args) {
		try {
			return (Boolean) _exeMethod.invoke(_command, sender, args);
		} catch (InvocationTargetException e) {
			if (e.getCause() instanceof MyCommandException) {
				sender.sendMessage(e.getCause().getMessage());
				return true;
			} else {
				throw new RuntimeException(e.getCause());
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@SuppressWarnings("unchecked")
	private List<String> invokeTabMethod(CommandSender sender, String[] args) {
		try {
			return (List<String>) _tabMethod.invoke(_command, sender, args);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

}
