use parking_lot;

use serenity;
use serenity::voice::LockedAudio;

use std::cell::RefCell;
use std::fs;
use std::ops::{Deref, DerefMut};
use std::rc::Rc;
use std::sync::{Arc, MutexGuard};

use rand::{seq::SliceRandom, thread_rng};

use super::super::prelude::*;

pub struct Play {
    media: Arc<MediaData>,
}

impl Play {
    pub fn new(media_data: Arc<MediaData>) -> Play {
        Play { media: media_data }
    }

    fn check_for_continue(song_lock: MutexGuard<RefCell<Option<LockedAudio>>>) -> bool {
        match song_lock.borrow_mut().deref_mut() {
            Some(song) => {
                let song_clone = song.clone();
                let mut audio_lock = song_clone.lock();

                audio_lock.play();
                true
            }
            None => false,
        }
    }

    fn check_join_channel(
        manager_lock: &Arc<parking_lot::Mutex<serenity::client::bridge::voice::ClientVoiceManager>>,
        msg: &serenity::model::channel::Message,
    ) {
        let guild = match msg.guild() {
            Some(guild) => guild,
            None => {
                display_error!(msg.channel_id.say("Groups and DMs not supported"));

                return;
            }
        };

        let guild_id = guild.read().id;

        let channel_id = guild
            .read()
            .voice_states
            .get(&msg.author.id)
            .and_then(|voice_state| voice_state.channel_id);

        let connect_to = match channel_id {
            Some(channel) => channel,
            None => {
                display_error!(msg.reply("Not in a voice channel"));

                return;
            }
        };

        let mut manager = manager_lock.lock();

        manager.join(guild_id, connect_to);
    }

    fn append_songs(
        &self,
        ctx: &mut serenity::client::Context,
        msg: &serenity::model::channel::Message,
        mut source: Vec<Song>,
    ) {
        {
            let playlist_mutex = self.media.playlist_mut();
            playlist_mutex.borrow_mut().append(&mut source);
        }

        let manager_lock = ctx.data.lock().get::<VoiceManager>().cloned().unwrap();

        Self::check_join_channel(&manager_lock, msg);

        let check_finished = {
            let media_clone = self.media.clone();
            let channel_id = msg.channel_id;
            let manager_lock_clone = manager_lock.clone();

            Rc::new(move || {
                let callback = match media_clone.next_callback.borrow().deref() {
                    Some(callback) => callback.clone(),
                    None => {
                        println!("next_callback not set!");
                        return;
                    }
                };

                MediaData::next_song(&media_clone, channel_id, &manager_lock_clone, callback);
            })
        };

        MediaData::start_playing(&self.media, check_finished, msg.channel_id, &manager_lock);

        //MediaData::start_thread(&self.media, msg.channel_id, &manager_lock);
    }

    fn handle_http_request(
        &self,
        ctx: &mut serenity::client::Context,
        msg: &serenity::model::channel::Message,
        url: &String,
    ) {
        let source = match youtube_dl(&url) {
            Ok(source) => source,
            Err(why) => {
                println!("Err starting source: {:?}", why);

                print_error!(
                    msg.channel_id
                        .say(format!("Error using youtube-dl: {}", why))
                );

                return;
            }
        };

        let mut info = if source.len() == 1 {
            "Finished downloading song:".to_string()
        } else {
            "Finished downloading songs:".to_string()
        };

        for song in &source {
            info = format!("{}\n\t{}", info, song.name);
        }

        print_error!(msg.channel_id.say(info));

        self.append_songs(ctx, msg, source);
    }

    fn handle_local_request(
        &self,
        ctx: &mut serenity::client::Context,
        msg: &serenity::model::channel::Message,
    ) {
        print_error!(self.media.reset(ctx, msg));

        let files = fs::read_dir("./").unwrap();

        let mut songs = Vec::new();

        for file in files {
            let os_name = file.unwrap().file_name();
            let name = os_name.to_str().unwrap();

            if name != "RMusicBot" && name != "bot.conf" && name != "start.sh" {
                songs.push(Song {
                    name: name.to_string(),
                });
            }
        }

        let mut rng = thread_rng();
        songs.shuffle(&mut rng);

        self.append_songs(ctx, msg, songs);
    }
}

impl serenity::framework::standard::Command for Play {
    #[allow(unreachable_code, unused_mut)]
    fn execute(
        &self,
        mut ctx: &mut serenity::client::Context,
        msg: &serenity::model::channel::Message,
        mut args: serenity::framework::standard::Args,
    ) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
        if !channel_contains_author(ctx, msg) {
            println!(
                "user {} is not in the same voice channel as the bot",
                msg.author.name
            );
            return Ok(());
        }

        if args.len() == 0 {
            if !Self::check_for_continue(self.media.song_mut()) {
                print_error!(msg.channel_id.say("Must provide a URL to a video or audio"));
            }
        } else if args.len() == 1 {
            let arg = match args.single::<String>() {
                Ok(arg) => arg,
                // can't happen, since we tested for length == 1
                Err(_) => return Ok(()),
            };

            if arg == "--local" {
                self.handle_local_request(ctx, msg);
            } else if arg.starts_with("http") {
                self.handle_http_request(ctx, msg, &arg);
            } else {
                print_error!(msg.channel_id.say("Unsupported argument list"));
            }
        } else {
            print_error!(msg.channel_id.say("Unsupported argument list"));
        }

        Ok(())
    }
}