using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using Discord;
using Discord.Audio;
using System;
using YoutubeExplode;
using System.Linq;
using YoutubeExplode.Models;
using System.Reflection;
using YoutubeSearch;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using NAudio.Wave;
using YoutubeExplode.Models.MediaStreams;

namespace Terrabot
{
    /// <summary>
    /// This class controls all audio funtions of terrabot. It's a bit messy but works somehow.
    /// </summary>
    public class AudioService
    {
        private readonly ConcurrentDictionary<ulong, IAudioClient> ConnectedChannels = new ConcurrentDictionary<ulong, IAudioClient>();
        public readonly string lastplaypath = System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + "\\replay\\";
        public readonly string youdownloadpath = System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + "\\down\\";
        public double vol = 0.2;
        public List<IGuild> LLock = new List<IGuild>();
        public List<Terrabot.Queue> LQueue = new List<Terrabot.Queue>();

        public async Task JoinAudio(IGuild guild, IVoiceChannel target)
        {
            // Joins requested audio channel
            IAudioClient client;
            if (ConnectedChannels.TryGetValue(guild.Id, out client))
            {
                return;
            }
            if (target.Guild.Id != guild.Id)
            {
                return;
            }
            var audioClient = await target.ConnectAsync();
            if (ConnectedChannels.TryAdd(guild.Id, audioClient)) { }
        }
        public async Task LeaveAudio(IGuild guild)
        {
            // Leaves any connected audio channel in a guild
            IAudioClient client;
            if (ConnectedChannels.TryRemove(guild.Id, out client))
            {
                await client.StopAsync();
                System.IO.File.WriteAllText($"{lastplaypath}{guild.Id}.repeat.txt", "0");
                Predicate<IGuild> guildFinder = (IGuild lockgf) => { return lockgf == guild; };
                LLock.RemoveAll(guildFinder);
            }
        }
        public async Task SendAudioAsync(IGuild guild, IMessageChannel channel, string path)
        {
            // Translates a file to discord audio
            if (!File.Exists(path))
            {
                await channel.SendMessageAsync("File does not exist.");
                return;
            }
            System.IO.File.WriteAllText($"{lastplaypath}{guild.Id}.replay.txt", path);
            IAudioClient client;
            if (ConnectedChannels.TryGetValue(guild.Id, out client))
            {
                var output = CreateStream(path).StandardOutput.BaseStream;
                var stream = client.CreatePCMStream(AudioApplication.Music);

                try
                {
                    LLock.Add(guild);
                    await output.CopyToAsync(stream);
                }
                catch (System.Threading.Tasks.TaskCanceledException)
                { Console.WriteLine("FFMPEG Canceled"); }
                catch (System.OperationCanceledException)
                { Console.WriteLine("FFMPEG Canceled"); }
                await stream.FlushAsync().ConfigureAwait(false);
                LLock.Remove(guild);
                // Repeat Function
                if (File.Exists($"{lastplaypath}{guild.Id}.repeat.txt"))
                {
                    string RepNum = System.IO.File.ReadAllText($"{lastplaypath}{guild.Id}.repeat.txt");
                    int RepInt = Int32.Parse(RepNum);
                    if (RepInt > 0)
                    {
                        RepInt = RepInt - 1;
                        System.IO.File.WriteAllText($"{lastplaypath}{guild.Id}.repeat.txt", RepInt.ToString());
                        await SendAudioAsyncRepeat(guild, channel, path);
                    }
                    if (RepInt < 0)
                    {
                        await SendAudioAsyncRepeat(guild, channel, path);
                    }
                }
                // Check if queue is active
                if (LQueue.Count() > 0)
                {
                    await YouProcess(guild);
                }
            }
        }
        public async Task SendAudioAsyncRepeat(IGuild guild, IMessageChannel msg, string path)
        {
            // For replay funtionality. Replays last file.
            await SendAudioAsync(guild, msg, path);
        }
        private Process CreateStream(string path)
        {
            // Runs FFMPEG to transcode audio
            return Process.Start(new ProcessStartInfo
            {
                FileName = "ffmpeg.exe",
                Arguments = $"-hide_banner -loglevel panic -i \"{path}\" -vn -filter:a \"volume = {vol}\" -ac 2 -f s16le -ar 48000 pipe:1",
                UseShellExecute = false,
                RedirectStandardOutput = true
            });
        }
        public async Task YoutubeExDL(IGuild guild, IVoiceChannel target, IMessageChannel channel, string url, string queue="")
        {
            // Downloads Youtube video then plays it.
            var client = new YoutubeClient();
            YoutubeClient.TryParseVideoId(url, out string id);
            try
            {
                var videoInfo = await client.GetVideoAsync(id);
                var streamInfoSet = await client.GetVideoMediaStreamInfosAsync(id);
                var streamInfo = streamInfoSet.Muxed.WithHighestVideoQuality();
                string fileName = $"{videoInfo.Id}.{streamInfo.VideoQuality}.mp4";
                string filePath = $"{youdownloadpath}{fileName}";
                if (File.Exists(filePath))
                {
                    await JoinAudio(guild, target);
                    await SendAudioAsync(guild, channel, filePath);
                    return;
                }
                await channel.SendMessageAsync($"Starting download of {videoInfo.Title}");
                await client.DownloadMediaStreamAsync(streamInfo, filePath);
                await channel.SendMessageAsync($"{videoInfo.Title} has finished downloading.");
                try { await JoinAudio(guild, target); } catch (System.NullReferenceException) { await channel.SendMessageAsync("You're not in a vaild Voice Channel"); }
                if (queue.Equals("yes"))
                {
                    await Queue("you", guild, target, channel, filePath);
                    await channel.SendMessageAsync($"{videoInfo.Title} has been added to the queue.");
                }
                await SendAudioAsync(guild, channel, filePath);
            }
            catch (System.InvalidOperationException)
            {
                await channel.SendMessageAsync("Video was not able to be downloaded.");
            }
            catch (System.NullReferenceException)
            {
                await channel.SendMessageAsync("Video does not exist. (Is it a Live video?)");
            }
        }
        public async Task YouSearch(IGuild guild, IVoiceChannel target, IMessageChannel channel, string query,string queue="")
        {
            // Searches for youtube video then sends it to the Youtube downloader.
            var items = new VideoSearch();
            foreach (var item in items.SearchQuery(query, 1))
            {
                await channel.SendMessageAsync($"I found {item.Url}");
                await YoutubeExDL(guild, target, channel, item.Url,queue);
                break;
            }
            await Task.CompletedTask;
        }
        public async Task YoutubeDL(IGuild guild, IMessageChannel channel, string url, Boolean local=false)
        {
            // Downloads Youtube video then plays it.
            var client = new YoutubeClient();
            YoutubeClient.TryParseVideoId(url, out string id);
            try
            {
                var videoInfo = await client.GetVideoAsync(id);
                var streamInfoSet = await client.GetVideoMediaStreamInfosAsync(id);
                var streamInfo = streamInfoSet.Muxed.WithHighestVideoQuality();
                string fileName = $"{videoInfo.Id}.{streamInfo.VideoQuality}.mp4";
                string filePath = $"{youdownloadpath}{fileName}";
                if (File.Exists(filePath))
                {
                    SendAudioAsync(guild, channel, filePath);
                    return;
                }
                await channel.SendMessageAsync($"Starting download of {videoInfo.Title}");
                await client.DownloadMediaStreamAsync(streamInfo, filePath);
                await channel.SendMessageAsync($"{videoInfo.Title} has finished downloading.");
                if (local)
                {
                    File.Copy(filePath, $"D:\\YouDL\\{fileName}");
                    SendAudioAsync(guild, channel, filePath);
                }
                else
                {
                    ///FTP UPLOADCODE
                    ///GOES HERE
                    ///Like PLZ WRITE IT SOON LEO
                    SendAudioAsync(guild, channel, filePath);
                }
            }
            catch (System.InvalidOperationException)
            {
                await channel.SendMessageAsync("Video was not able to be downloaded.");
            }
            catch (System.NullReferenceException)
            {
                await channel.SendMessageAsync("Video does not exist. (Is it a Live video?)");
            }
        }
        public Task Queue(string app, IGuild guild, IVoiceChannel target, IMessageChannel channel, string query)
        {
            //Adds an item to the queue
            LQueue.Add(new Terrabot.Queue(app, guild, target, channel, query));
            return Task.CompletedTask;
        }
        public async Task YouProcess(IGuild queueguild)
        {
            //Processes one item then deletes it form queue
            Predicate<Terrabot.Queue> guildFinder = (Terrabot.Queue queuegf) => { return queuegf.Guild == queueguild; };
            Terrabot.Queue Line = LQueue.Find(guildFinder);
            if (Line.Equals(null)) { return; }
            string app = Line.App;
            IGuild guild = Line.Guild;
            IVoiceChannel target = Line.Target;
            IMessageChannel channel = Line.Channel;
            string query = Line.Query;
            LQueue.Remove(Line);
            if (app.Equals("you"))
            {
                await JoinAudio(guild, target);
                await SendAudioAsync(guild, channel, query);
                return;
            }
            if (app.Equals("juke"))
            {
                await JoinAudio(guild, target);
                await SendAudioAsync(guild, channel, query);
            }
            return;
        }
        public Task YouClearQueue(IGuild queueguild)
        {
            //Removes all items from a guild.
            Predicate<Terrabot.Queue> guildFinder = (Terrabot.Queue queuegf) => { return queuegf.Guild == queueguild; };
            LQueue.RemoveAll(guildFinder);
            return Task.CompletedTask;
        }
    }
}