use serenity;
use serenity::model::id::ChannelId;
use serenity::voice::ffmpeg;
use serenity::voice::LockedAudio;

use std::cell::RefCell;
use std::ops::DerefMut;
use std::sync::{Arc, Mutex, MutexGuard};
use std::thread;
use std::thread::JoinHandle;
use std::time;

use super::prelude::*;

pub struct Song {
    pub name: String,
}

pub struct MediaData {
    playlist: Mutex<RefCell<Vec<Song>>>,
    current_song: Mutex<RefCell<Option<LockedAudio>>>,
    watcher_thread: Mutex<RefCell<Option<JoinHandle<()>>>>,
}

impl MediaData {
    pub fn reset(
        &self,
        ctx: &mut serenity::client::Context,
        msg: &serenity::model::channel::Message,
    ) -> Result<(), String> {
        {
            let playlist = self.playlist_mut();
            playlist.borrow_mut().clear();
        }

        {
            let song = self.song_mut();
            *song.borrow_mut() = None;
        }

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

            let mut manager = manager_lock.lock();
            let guild_id = match guild_id(msg.channel_id) {
                Some(guild_id) => guild_id,
                None => return Ok(()),
            };

            let handler = match handler(guild_id, &mut manager) {
                Some(handler) => handler,
                None => return Ok(()),
            };

            println!("stopped handler");

            handler.stop();
        }

        Ok(())
    }

    pub fn song_mut(&self) -> MutexGuard<RefCell<Option<LockedAudio>>> {
        self.current_song.lock().unwrap()
    }

    pub fn playlist_mut(&self) -> MutexGuard<RefCell<Vec<Song>>> {
        self.playlist.lock().unwrap()
    }

    pub fn reset_thread(&self) {
        let thread_mutex = self.watcher_thread.lock().unwrap();
        *thread_mutex.borrow_mut() = None;
    }

    pub fn start_thread(
        media: &Arc<MediaData>,
        channel_id: ChannelId,
        manager_lock: &Arc<
            serenity::prelude::Mutex<serenity::client::bridge::voice::ClientVoiceManager>,
        >,
    ) {
        let media_clone = media.clone();
        let manager_clone = manager_lock.clone();

        let thread_mutex = media.watcher_thread.lock().unwrap();
        let mut watcher_mut = thread_mutex.borrow_mut();

        if watcher_mut.is_none() {
            *watcher_mut = Some(thread::spawn(move || loop {
                {
                    let song_lock = media_clone.song_mut();
                    let mut borrow = song_lock.borrow_mut();

                    // if there is currently something playing
                    if let Some(ref mut song) = borrow.deref_mut() {
                        let song_lock = song.clone();
                        let audio = song_lock.lock();

                        if audio.finished {
                            let playlist = media_clone.playlist_mut();

                            if !Self::next_song(
                                song,
                                playlist.borrow_mut().deref_mut(),
                                channel_id,
                                &manager_clone,
                            ) {
                                manager_clone
                                    .lock()
                                    .remove(check_option!(guild_id(channel_id)));

                                media_clone.reset_thread();

                                println!("left channel");

                                return;
                            }
                        }

                        continue;
                    }

                    // nothing is playing
                    {
                        let playlist_mutex = media_clone.playlist_mut();
                        let mut playlist = playlist_mutex.borrow_mut();

                        if !playlist.is_empty() {
                            let mut manager = manager_clone.lock();

                            if let Some(handler) =
                                handler(check_option!(guild_id(channel_id)), &mut manager)
                            {
                                let first = playlist.remove(0);

                                let source = match ffmpeg(first.name.clone()) {
                                    Ok(mpeg) => mpeg,
                                    Err(_) => {
                                        playlist.clear();
                                        *borrow = None;
                                        handler.stop();
                                        media_clone.reset_thread();

                                        return;
                                    }
                                };

                                *borrow = Some(handler.play_returning(source));

                                print_error!(
                                    channel_id.say(format!("Playing song: {}", first.name))
                                );
                            } else {
                                return;
                            }
                        } else {
                            manager_clone
                                .lock()
                                .remove(check_option!(guild_id(channel_id)));

                            media_clone.reset_thread();

                            println!("left channel");

                            return;
                        }
                    }
                }

                let two_sec = time::Duration::from_secs(2);
                thread::sleep(two_sec);
            }));
        }
    }

    fn next_song(
        song: &mut LockedAudio,
        playlist: &mut Vec<Song>,
        channel_id: ChannelId,
        manager_lock: &Arc<
            serenity::prelude::Mutex<serenity::client::bridge::voice::ClientVoiceManager>,
        >,
    ) -> bool {
        let mut manager = manager_lock.lock();

        let guild_id = match guild_id(channel_id) {
            Some(id) => id,
            None => return false,
        };

        if let Some(handler) = handler(guild_id, &mut manager) {
            if playlist.is_empty() {
                return false;
            } else {
                handler.stop();

                let first = playlist.remove(0);

                let source = match ffmpeg(first.name.clone()) {
                    Ok(mpeg) => mpeg,
                    Err(_) => {
                        playlist.clear();

                        return false;
                    }
                };

                *song = handler.play_returning(source);

                print_error!(channel_id.say(format!("Playing song: {}", first.name)));

                return true;
            }
        }

        return false;
    }
}

impl Default for MediaData {
    fn default() -> MediaData {
        MediaData {
            playlist: Mutex::new(RefCell::new(Vec::new())),
            current_song: Mutex::new(RefCell::new(None)),
            watcher_thread: Mutex::new(RefCell::new(None)),
        }
    }
}

unsafe impl Send for MediaData {}
unsafe impl Sync for MediaData {}