package de.yannicpunktdee.yoshibot.main;

import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager;
import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers;
import com.sedmelluq.discord.lavaplayer.source.local.LocalAudioSourceManager;
import de.yannicpunktdee.yoshibot.audio.AudioLoadResultHandlerImpl;
import de.yannicpunktdee.yoshibot.audio.AudioPlayerListener;
import de.yannicpunktdee.yoshibot.audio.AudioSendHandlerImpl;
import de.yannicpunktdee.yoshibot.command.YoshiCommandContext;
import de.yannicpunktdee.yoshibot.command.YoshiCommandDistributor;
import de.yannicpunktdee.yoshibot.listeners.CommandLine;
import de.yannicpunktdee.yoshibot.listeners.DiscordEventListener;
import de.yannicpunktdee.yoshibot.utils.*;
import lombok.Getter;
import lombok.SneakyThrows;
import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.JDABuilder;
import net.dv8tion.jda.api.OnlineStatus;
import net.dv8tion.jda.api.entities.Activity;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.PermissionOverride;
import net.dv8tion.jda.api.entities.VoiceChannel;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * Repräsentiert einen Yoshi-Bot. Der Bot initialisiert alle Ressourcen und schaltet sich in der startYoshiBot-Methode
 * online und beginnt dann zu lauschen. Parallel lauscht ein Thread auf Konsoleneingaben für administrative Zwecke, die
 * nicht über den Chat erledigt werden sollten.
 *
 * @author Yannic Link
 */
public final class YoshiBot {
	
	private CommandLine commandLineThread;
	
	/**
	 * Erlaubt es einige Einstellungen vor und nach der Erzeugung eines Bots vorzunehmen.
	 */
	public JDABuilder jdaBuilder;
	/**
	 * Instanz vom aktuell laufenden Bot.
	 */
	public JDA jda;
	/**
	 * LavaPlayer AudioPlayerManager.
	 */
	public AudioPlayerManager audioPlayerManager;
	
	@Getter
	private Guild guild;
	
	public AudioPlayer audioPlayer;
	
	private static YoshiBot instance = null;
	
	@Getter
	private final Random random = new Random();
	
	private final Set<Provider> allProvides = new HashSet<>();
	
	/**
	 * Initialisiert alle dynamisch hinzugefügten und statischen Ressourcen. Startet aber nicht den Bot selbst.
	 */
	public boolean init(String configPath) {
		return Resources.init(configPath);
	}
	
	/**
	 * Startet den Bot und schaltet ihn online. Beginnt auf Konsoleneingaben für administrative Zwecke zu lauschen.
	 **/
	@SneakyThrows
	public void start() {
		System.out.println("Starte YoshiBot.");
		
		jdaBuilder = JDABuilder.createDefault(Resources.getJda_builder_string());
		jdaBuilder.setAutoReconnect(true);
		
		jdaBuilder.addEventListeners(new DiscordEventListener());
		
		audioPlayerManager = new DefaultAudioPlayerManager();
		audioPlayerManager.registerSourceManager(new LocalAudioSourceManager());
		AudioSourceManagers.registerRemoteSources(audioPlayerManager);
		
		jda = jdaBuilder.build();
		try {
			jda.awaitReady();
		} catch (InterruptedException e) {
			Logger.logError("Konnte nicht auf jda warten. Thread unterbrochen.");
			return;
		}
		
		jda.awaitReady();
		guild = jda.getGuildById(Resources.getGuild_id());
		
		audioPlayer = audioPlayerManager.createPlayer();
		audioPlayer.addListener(new AudioPlayerListener(guild.getAudioManager()));
		guild.getAudioManager().setSendingHandler(new AudioSendHandlerImpl(audioPlayer));
		
		jdaBuilder.setStatus(OnlineStatus.ONLINE);
		
		Logger.logInfo("YoshiBot online.");
		
		commandLineThread = new CommandLine();
		commandLineThread.start();
		
		StatusProviderFactory.createStatusProviders(Resources.getMcserver_config_file(), allProvides);
		
		//SauceProvider.init(300);
		
		
		Executors.newScheduledThreadPool(1).scheduleAtFixedRate(YoshiBot::setRandomActivity, 0, 10, TimeUnit.HOURS);
		
		Runtime.getRuntime().addShutdownHook(new Thread(() -> YoshiBot.getInstance().stop()));
		
		joinVoiceChannelWithMostMembers();
	}
	
	public synchronized void stop() {
		allProvides.forEach(Provider::onStop);
		commandLineThread.stopCommandLine();
		System.out.println("Beende YoshiBot ...");
		jdaBuilder.setStatus(OnlineStatus.OFFLINE);
		jda.shutdown();
		System.out.println("YoshiBot offline.");
	}
	
	/**
	 * Leitet den Context an den CommandDistributor weiter.
	 *
	 * @param command Der Kontext für das Kommando.
	 */
	public static void executeCommand(YoshiCommandContext command) {
		YoshiCommandDistributor.distribute(command);
	}
	
	public static YoshiBot getInstance() {
		if (YoshiBot.instance == null) {
			YoshiBot.instance = new YoshiBot();
		}
		return YoshiBot.instance;
	}
	
	@SneakyThrows
	private static void setRandomActivity() {
		YoshiBot yoshiBot = YoshiBot.getInstance();
		yoshiBot.jda.awaitReady();
		List<String> text = Files.readAllLines(new File(Resources.getActivitiesPath()).toPath());
		String activity = text.get(yoshiBot.random.nextInt(text.size()));
		yoshiBot.jda.getPresence().setActivity(Activity.playing(activity));
		Logger.logInfo("Setze Aktivität auf " + activity);
	}
	
	public synchronized void joinVoiceChannel(VoiceChannel vc) {
		if (vc == null) {
			guild.getAudioManager().closeAudioConnection();
			return;
		}
		if (guild.getAudioManager().getConnectedChannel() != null &&
			vc.getIdLong() == guild.getAudioManager().getConnectedChannel().getIdLong()) return;
		try {
			guild.getAudioManager().openAudioConnection(vc);
		} catch (InsufficientPermissionException e) {
			Logger.logWarning("Durfte dem VoiceChannel " + vc.getName() + " nicht beitreten.");
		}
	}
	
	public VoiceChannel joinVoiceChannelWithMostMembers() {
		Map<VoiceChannel, Long> vcAmount =
				guild.getVoiceChannels().stream()
						.filter(channel -> channel != guild.getAfkChannel())
						.collect(Collectors.toMap(vc -> vc,
												  vc -> vc.getMembers().stream()
														  .filter(m -> !m.getUser().isBot())
														  .count()));
		
		VoiceChannel channel =
				vcAmount.entrySet().stream()
						.filter(vc -> vc.getValue() > 0)
						.max(Comparator.comparingLong(Map.Entry::getValue))
						.map(Map.Entry::getKey)
						.orElse(null);
		if (channel != null) {
			PermissionOverride override = channel.getPermissionOverride(
					Objects.requireNonNull(guild.getMember(jda.getSelfUser())));
		}
		joinVoiceChannel(channel);
		return channel;
	}
	
	public boolean playSound(File file, VoiceChannel vc) {
		if (!file.isFile()) return false;
		
		joinVoiceChannel(vc);
		
		audioPlayerManager.loadItem(file.getAbsolutePath(), new AudioLoadResultHandlerImpl());
		
		return true;
	}
	
	public boolean sayTTS(String text, VoiceChannel vc) {
		String path = Resources.getEnsuredTempPath() + UUID.randomUUID() + ".opus";
		
		try {
			ProcessBuilder pb = new ProcessBuilder(
					"python3",
					Resources.getTtsPath(),
					"--text",
					text,
					"--lang",
					"de",
					"--out",
					path);
			
			Process p = pb.start();
			BufferedReader errorReader = new BufferedReader(new InputStreamReader(p.getErrorStream()));
			StringBuilder builder = new StringBuilder();
			String line;
			while ((line = errorReader.readLine()) != null) {
				builder.append(line).append("\n");
			}
			if (builder.toString().length() > 0) {
				Logger.logError(builder.toString());
			}
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		
		return playSound(new File(path), vc);
	}
	
}
