package me.despawningbone.discordbot.command.music;
	
import com.sedmelluq.discord.lavaplayer.player.AudioPlayer;
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter;
import com.sedmelluq.discord.lavaplayer.track.AudioTrack;
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason;

import me.despawningbone.discordbot.DiscordBot;
import me.despawningbone.discordbot.command.music.AudioTrackHandler.TrackData;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.jsoup.Connection;
import org.jsoup.Jsoup;

/**
 * Handles everything related to music queuing
 * Boilerplate from https://github.com/sedmelluq/lavaplayer/tree/master/demo-jda/src/main/java/com/sedmelluq/discord/lavaplayer/demo/jda
 */
public class TrackScheduler extends AudioEventAdapter {
	
	private final AudioPlayer player;
	private final BlockingQueue<AudioTrack> queue;
	private AudioTrackHandler ap;
	private Guild guild;

	public String loop = null;  //way too lazy to use getter setters lmao

	public TrackScheduler(Guild parent, AudioTrackHandler handler, AudioPlayer player) {
		this.player = player;
		this.queue = new LinkedBlockingQueue<>();
		this.ap = handler;
		this.guild = parent;
	}

	public void queue(AudioTrack track) {
		if (!player.startTrack(track, true)) {
			queue.offer(track);
		} else if (loop != null && loop.equals("autoplay")) {  //i dont think this is needed as people need to play something before autoplay can be toggled anyways
			queueAutoplay(track);
		}
	}

	public void nextTrack() {  //DONE rewrite to not include q.remove here so that stuff like interrupted wont break the queue?
		AudioTrack track = queue.poll();
		player.startTrack(track, false);
		if(track == null) {
			//System.out.println("finished");   //debug
			loop = null;  //reset loop and autoplay tracking
			delayCloseConnection(player);  //required because if not it will throw InterruptedException
		}
	}  //externally called from AudioTrackHandler.skipTrack()

	public void queueAutoplay(AudioTrack track) {  //DONE check duplicate please, some can get into a dead loop like cosMo@暴走P - WalpurgisNacht and Ice - 絶     //using yt tracking params now; still can get into loops but random should fix
		ap.ex.submit(() -> {  //async so it can free up the event 
			try {
				//System.out.println("autoplay");   //debug
				
				Connection con = Jsoup.connect("https://www.youtube.com/watch?v=" + track.getIdentifier() + "&pbj=1");

				String cmd = track.getUserData(TrackData.class).getYoutubeAutoplayParam();
				if(cmd != null) con.data("command", cmd);  //use tracking params to prevent dupes
				
				JSONArray autoplayFeed = new JSONArray(new JSONTokener(con.ignoreContentType(true).post().text()))
						.getJSONObject(3).getJSONObject("response").getJSONObject("contents").getJSONObject("twoColumnWatchNextResults")
						.getJSONObject("secondaryResults").getJSONObject("secondaryResults").getJSONArray("results");
				
				//filter non video results out
				for(int i = 0; i < autoplayFeed.length(); i++)
					if(!autoplayFeed.getJSONObject(i).has("compactVideoRenderer"))
						autoplayFeed.remove(i--);  //remove and reset index pointer by 1
				
				//choose one from top 2 results (should always have more than 2); second result should still be consistent due to tracking unlike without
				JSONObject autoplay = autoplayFeed.getJSONObject(ThreadLocalRandom.current().nextInt(2))
						.getJSONObject("compactVideoRenderer").getJSONObject("navigationEndpoint");
				
				
				String url = "https://youtube.com" + autoplay.getJSONObject("commandMetadata").getJSONObject("webCommandMetadata").getString("url");
					
				//load
				Member user = guild.getMemberById(DiscordBot.BotID); //no need to use original track user as it would introduce performance overhead and other complications when leaving channels
				ap.load(user, url, (n, l) -> {
					AudioTrack auto = l.get(0);
					auto.setUserData(new TrackData(auto.getInfo().uri, auto.getDuration(), autoplay.toString()));  //set new tracking
					ap.queueTracks(l, user);  //no need sublist coz l is always a single video
				}, ex -> {
					ex.printStackTrace();
					DiscordBot.lastMusicCmd.get(guild.getId()).sendMessage("Something went wrong when loading next autoplay track: " + ex.getMessage()).queue();
					loop = null;
				});
			} catch (Exception e) {
				e.printStackTrace();
				DiscordBot.lastMusicCmd.get(guild.getId()).sendMessage("Something went wrong when loading next autoplay track. Is the last track a youtube video?").queue();
				loop = null;
			}
		});
	}

	public List<AudioTrack> findTracks(int startIndex, int range) {
		AudioTrack[] a = queue.toArray(new AudioTrack[queue.size()]);
		int i = startIndex - 1 + range < 0 ? Integer.MAX_VALUE : startIndex - 1 + range;  //prevent overflow
		AudioTrack[] t = Arrays.copyOfRange(a, startIndex - 1, Math.min(queue.size() + 1, i));  //accounts for first track player thats not in queue
		
		return Arrays.asList(t);
	}

	public AudioTrack removeTrack(int num) {
		Iterator<AudioTrack> i = queue.iterator();
		num = num - 1;

		for (int times = 0; i.hasNext(); times++) {
			AudioTrack removed = i.next();
			if (num == times) {
				i.remove();
				return removed;
			}
		}

		return null;
	}
	
	public AudioTrack moveTrack(int from, int to) {
		List<AudioTrack> q = new ArrayList<>(Arrays.asList(queue.toArray(new AudioTrack[queue.size()])));
		AudioTrack track = q.remove(from);
		q.add(to, track);
		synchronized (queue) {  //obtain lock and operate before releasing or else queue might not be complete
			queue.clear();
			for(AudioTrack t : q) queue.offer(t);
		}
		return track;
	}
	
	public void shuffleQueue() {
		List<AudioTrack> q = Arrays.asList(queue.toArray(new AudioTrack[queue.size()]));
		Collections.shuffle(q);
		synchronized (queue) {  //obtain lock and operate before releasing or else queue might not be complete
			queue.clear();
			for(AudioTrack t : q) queue.offer(t);
		}
	}

	public void clearSchedulerQueue() {
		queue.clear();
	}

	public int getQueueSize() {
		return queue.size();
	}


	@Override
	public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) {
		//System.out.println(endReason);  //debug  
		
		boolean mayStartNext = endReason.mayStartNext;

		if (mayStartNext) {
			//doesnt queue clone if skipped
			if (loop != null && loop.equals("loop")) { //so what the hecc if loop is null and i do loop.equals("Loop") it freezes the thread
				AudioTrack clone = track.makeClone();
				clone.setUserData(new TrackData(clone.getUserData(TrackData.class)));  //clone track data
				queue.offer(clone);
			}
			
			nextTrack();
		}
		
		if(mayStartNext || endReason == AudioTrackEndReason.REPLACED) {  //queues new if skipped too
			if (loop != null && loop.equals("autoplay") && queue.size() < 1) {
				queueAutoplay(player.getPlayingTrack());
			}
		}
	}

	private void delayCloseConnection(AudioPlayer player) {  //TODO configurable delay; dont leave if new things play within the delay
		ap.ex.schedule(() -> guild.getAudioManager().closeAudioConnection(), 1, TimeUnit.MILLISECONDS);
	}

}