package com.github.ustc_zzzz.virtualchest.command;

import com.github.ustc_zzzz.virtualchest.VirtualChestPlugin;
import com.github.ustc_zzzz.virtualchest.inventory.VirtualChestInventoryDispatcher;
import com.github.ustc_zzzz.virtualchest.translation.VirtualChestTranslation;
import com.github.ustc_zzzz.virtualchest.unsafe.SpongeUnimplemented;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.*;
import org.spongepowered.api.command.args.CommandContext;
import org.spongepowered.api.command.args.GenericArguments;
import org.spongepowered.api.command.spec.CommandSpec;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.scheduler.Scheduler;
import org.spongepowered.api.scheduler.Task;
import org.spongepowered.api.text.Text;
import org.spongepowered.api.text.action.TextActions;
import org.spongepowered.api.text.format.TextColors;
import org.spongepowered.api.text.format.TextStyles;
import org.spongepowered.api.util.annotation.NonnullByDefault;

import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.Locale;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * @author ustc_zzzz
 */
@NonnullByDefault
public class VirtualChestCommandManager implements Supplier<CommandCallable>
{
    private static final String VERSION = VirtualChestPlugin.VERSION;
    private static final String SUBTITLE = VirtualChestPlugin.DESCRIPTION;
    private static final String GITHUB_URL = VirtualChestPlugin.GITHUB_URL;
    private static final String WEBSITE_URL = VirtualChestPlugin.WEBSITE_URL;

    private static final SimpleDateFormat RFC3339 = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);

    private final VirtualChestPlugin plugin;
    private final VirtualChestTranslation translation;

    private final CommandCallable reloadCommand;
    private final CommandCallable listCommand;
    private final CommandCallable updateCommand;
    private final CommandCallable openCommand;
    private final CommandCallable versionCommand;

    public VirtualChestCommandManager(VirtualChestPlugin plugin)
    {
        this.plugin = plugin;
        this.translation = plugin.getTranslation();

        this.reloadCommand = CommandSpec.builder()
                .description(this.translation.take("virtualchest.reload.description"))
                .arguments(GenericArguments.optional(
                        GenericArguments.literal(Text.of("extract-examples"), "extract-examples")))
                .executor(this::processReloadCommand).build();

        this.listCommand = CommandSpec.builder()
                .description(this.translation.take("virtualchest.list.description"))
                .arguments(GenericArguments.none())
                .executor(this::processListCommand).build();

        this.updateCommand = CommandSpec.builder()
                .description(this.translation.take("virtualchest.update.description"))
                .arguments(GenericArguments.seq(
                        new VirtualChestInventoryCommandElement(this.plugin, Text.of("inventory")),
                        GenericArguments.playerOrSource(Text.of("player"))))
                .executor(this::processUpdateCommand).build();

        this.openCommand = CommandSpec.builder()
                .description(this.translation.take("virtualchest.open.description"))
                .arguments(GenericArguments.seq(
                        new VirtualChestInventoryCommandElement(this.plugin, Text.of("inventory")),
                        GenericArguments.playerOrSource(Text.of("player"))))
                .executor(this::processOpenCommand).build();

        this.versionCommand = CommandSpec.builder()
                .description(this.translation.take("virtualchest.version.description"))
                .arguments(GenericArguments.none())
                .executor(this::processVersionCommand).build();
    }

    public void init()
    {
        CommandManager commandManager = Sponge.getCommandManager();
        commandManager.register(this.plugin, this.get(), "virtualchest", "vchest", "vc");
    }

    private CommandResult processVersionCommand(CommandSource source, CommandContext args) throws CommandException
    {
        source.sendMessage(Text.of("================================================================"));
        source.sendMessage(this.translation.take("virtualchest.version.description.title", VERSION));
        source.sendMessage(this.translation.take("virtualchest.version.description.subtitle", SUBTITLE));
        source.sendMessage(Text.of("================================================================"));
        try
        {
            // RFC 3339
            Date releaseDate = RFC3339.parse("@release_date@");
            String gitCommitHash = "@git_hash@";

            source.sendMessage(this.translation
                    .take("virtualchest.version.description.line1", releaseDate));
            source.sendMessage(this.translation
                    .take("virtualchest.version.description.line2", gitCommitHash));

            Text urlWebsite = Text.builder(WEBSITE_URL)
                    .color(TextColors.GREEN).style(TextStyles.BOLD)
                    .onClick(TextActions.openUrl(new URL(WEBSITE_URL))).build();
            Text urlGitHub = Text.builder(GITHUB_URL)
                    .color(TextColors.GREEN).style(TextStyles.BOLD)
                    .onClick(TextActions.openUrl(new URL(GITHUB_URL))).build();

            source.sendMessage(Text.join(this.translation
                    .take("virtualchest.version.description.line3", ""), urlWebsite));
            source.sendMessage(Text.join(this.translation
                    .take("virtualchest.version.description.line4", ""), urlGitHub));

            source.sendMessage(Text.of("================================================================"));
        }
        catch (MalformedURLException | ParseException e)
        {
            throw new RuntimeException(e);
        }
        return CommandResult.success();
    }

    private CommandResult processOpenCommand(CommandSource source, CommandContext args) throws CommandException
    {
        // noinspection ConstantConditions
        String inventoryName = args.<String>getOne("inventory").get();
        Collection<Player> players = args.getAll("player");
        Scheduler spongeScheduler = Sponge.getScheduler();
        for (Player player : players)
        {
            if (player.equals(source))
            {
                if (!source.hasPermission("virtualchest.open.self." + inventoryName))
                {
                    String errorKey = "virtualchest.open.self.noPermission";
                    throw new CommandException(this.translation.take(errorKey, inventoryName));
                }
            }
            else if (source instanceof Player)
            {
                if (!source.hasPermission("virtualchest.open.others." + inventoryName))
                {
                    String errorKey = "virtualchest.open.others.noPermission";
                    throw new CommandException(this.translation.take(errorKey, inventoryName, player.getName()));
                }
            }
            Task.Builder builder = spongeScheduler.createTaskBuilder().name("VirtualChestCommandManager");
            builder.execute(task -> this.plugin.getDispatcher().open(inventoryName, player)).submit(this.plugin);
        }
        return CommandResult.success();
    }

    private CommandResult processUpdateCommand(CommandSource source, CommandContext args) throws CommandException
    {
        // noinspection ConstantConditions
        String inventoryName = args.<String>getOne("inventory").get();
        Collection<Player> players = args.getAll("player");
        for (Player player : players)
        {
            if (player.equals(source))
            {
                if (!source.hasPermission("virtualchest.update.self." + inventoryName))
                {
                    String errorKey = "virtualchest.update.self.noPermission";
                    throw new CommandException(this.translation.take(errorKey, inventoryName));
                }
            }
            else if (source instanceof Player)
            {
                if (!source.hasPermission("virtualchest.update.others." + inventoryName))
                {
                    String errorKey = "virtualchest.update.others.noPermission";
                    throw new CommandException(this.translation.take(errorKey, inventoryName, player.getName()));
                }
            }
        }
        int total = players.size();
        long success = players.stream().filter(p -> this.plugin.getDispatcher().update(inventoryName, p)).count();
        source.sendMessage(this.translation.take("virtualchest.update.finish", inventoryName, success, total));
        return CommandResult.success();
    }

    private CommandResult processListCommand(CommandSource source, CommandContext args) throws CommandException
    {
        VirtualChestInventoryDispatcher dispatcher = this.plugin.getDispatcher();
        if (source instanceof Player && !source.hasPermission("virtualchest.list"))
        {
            Text error = translation.take("virtualchest.list.noPermission", source.getName());
            throw new CommandException(error);
        }
        Set<String> inventories = dispatcher.ids();
        if (source instanceof Player)
        {
            Predicate<String> inventoryNamePredict = name -> dispatcher.hasPermission((Player) source, name);
            inventories = inventories.stream().filter(inventoryNamePredict).collect(Collectors.toSet());
        }
        source.sendMessage(translation.take("virtualchest.list.overview", inventories.size()));
        source.sendMessage(Text.of(String.join(", ", inventories)));
        return CommandResult.success();
    }

    private CommandResult processReloadCommand(CommandSource source, CommandContext args) throws CommandException
    {
        if (!source.hasPermission("virtualchest.reload"))
        {
            return CommandResult.empty();
        }
        else
        {
            if (args.getOne("extract-examples").isPresent())
            {
                this.plugin.getDispatcher().releaseExample();
            }
            this.plugin.onReload(() -> SpongeUnimplemented.createCause(source));
            return CommandResult.success();
        }
    }

    @Override
    public CommandCallable get()
    {
        return CommandSpec.builder()
                .child(this.reloadCommand, "reload", "r")
                .child(this.listCommand, "list", "l")
                .child(this.updateCommand, "update", "u")
                .child(this.openCommand, "open", "o")
                .child(this.versionCommand, "version", "v")
                .build();
    }
}