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.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
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.JSONObject;
import org.json.JSONTokener;

/**
 * This class schedules tracks for the audio player. It contains the queue of
 * tracks.
 */
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

	/**
	 * @param player
	 *            The audio player this scheduler uses
	 */
	public TrackScheduler(Guild parent, AudioTrackHandler handler, AudioPlayer player) {
		this.player = player;
		this.queue = new LinkedBlockingQueue<>();
		this.ap = handler;
		this.guild = parent;
	}

	/**
	 * Add the next track to queue or play right away if nothing is in the
	 * queue.
	 *
	 * @param track
	 *            The track to play or add to queue.
	 */
	public void queue(AudioTrack track) {
		// Calling startTrack with the noInterrupt set to true will start the track only if nothing is currently playing. If
		// something is playing, it returns false and does nothing. In that case the player was already playing so this
		// track goes to the queue instead.
		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);
		}
	}

	/**
	 * Start the next track, stopping the current one if it is playing.
	 */
	public void nextTrack() {  //DONE rewrite to not include q.remove here so that stuff like interrupted wont break the queue?
		// Start the next track, regardless of if something is already playing or not. In case queue was empty, we are
		// giving null to startTrack, which is a valid argument and will simply stop the player.
		AudioTrack track = queue.poll();
		player.startTrack(track, false);
		if(track == null) {
			//System.out.println("finished");   //debug
			loop = null;
			delayCloseConnection(player);  //required because if not it will throw InterruptedException
		}
	} //seems to be called internally somehow; even when mayStartNext is false (REPLACED, STOPPED etc) this still fires 
	//NVM ITS CALLED FROM AudioTrackHandler.skipTrack() LOL

	public void queueAutoplay(AudioTrack track) {  //check duplicate please, some can get into a dead loop like cosMo@暴走P - WalpurgisNacht and Ice - 絶  //well randoming it works
		ap.ex.submit(() -> {  //async so it can free up the event 
			try {
				//System.out.println("autoplay");
				InputStream input = new URL("https://www.googleapis.com/youtube/v3/search?part=snippet&relatedToVideoId=" + track.getIdentifier() + "&type=video&key=" + ap.GAPI).openStream();
				JSONTokener result = new JSONTokener(new InputStreamReader(input, "UTF-8"));
				Member temp;
				try {
					temp = guild.getMemberByTag(track.getUserData(TrackData.class).getUserWithDiscriminator());				
				} catch (IllegalArgumentException e) {  //track was autoplay queued before this
					temp = guild.getMemberById(DiscordBot.BotID);  //fallback
				}
				final Member user = temp;
				int t = ThreadLocalRandom.current().nextInt(2);
				String url = "https://youtube.com/watch?v=" + new JSONObject(result).getJSONArray("items").getJSONObject(t).getJSONObject("id").getString("videoId");
				ap.load(user, url, (n, l) -> {
					AudioTrack auto = l.get(0);
					auto.setUserData(new TrackData(auto.getInfo().uri, "Autoplay", auto.getDuration()));
					ap.queueTracks(l.subList(0, 1), user);
				}, 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) {
		// Only start the next track if the end reason is suitable for it (FINISHED or LOAD_FAILED or REPLACED)
		//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();
				TrackData origData = clone.getUserData(TrackData.class);
				clone.setUserData(new TrackData(origData.getUrl(), origData.getUserWithDiscriminator(), clone.getDuration()));  //wipe votes
				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) {
		ap.ex.schedule(() -> guild.getAudioManager().closeAudioConnection(), 1, TimeUnit.MILLISECONDS);
	}

}