use serenity;
use serenity::model::channel::Message;
use serenity::prelude::*;
use serenity::voice::ffmpeg;
use serenity::voice::LockedAudio;

use parking_lot::lock_api::{MutexGuard, RawMutex};
use serenity::client::bridge::voice::ClientVoiceManager;
use serenity::prelude::Mutex;
use std::sync::{Arc, Mutex as StdMutex};

use super::prelude::*;

use std::fs;

use rusqlite::{params, Connection};

use utilities::prelude::*;

#[derive(Debug)]
pub struct Song {
    pub name: String,
}

pub struct MediaData {
    volume: f32,
    playlist: Vec<Song>,
    current_song: Option<LockedAudio>,
    song_name: String,

    sql_data_base: Connection,

    pub voice_manager: Arc<Mutex<ClientVoiceManager>>,
}

impl MediaData {
    pub fn new(
        file: &str,
        voice_manager: Arc<Mutex<ClientVoiceManager>>,
        volume: f32,
    ) -> Result<MediaData, String> {
        let connection = match Connection::open(file) {
            Ok(file) => file,
            Err(_) => return Err(format!("can't open {}", file)),
        };

        match connection.execute(
            "CREATE TABLE IF NOT EXISTS Vulva3 (
                name            TEXT PRIMARY KEY,
                link            TEXT NOT NULL
            )",
            params![],
        ) {
            Ok(_) => (),
            Err(err) => {
                println!("{}", err);
                return Err(format!("can't create table"));
            }
        };

        Ok(MediaData {
            volume,
            playlist: Vec::new(),
            current_song: None,
            song_name: String::new(),

            sql_data_base: connection,

            voice_manager,
        })
    }

    pub fn reset(
        &mut self,
        ctx: &serenity::client::Context,
        msg: &serenity::model::channel::Message,
    ) -> Result<(), String> {
        self.playlist.clear();
        self.current_song = None;
        self.song_name = String::new();

        let mut manager = self.voice_manager.lock();

        let guild_id = guild_id(ctx, msg)?;

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

        println!("stopped handler");

        handler.stop();
        manager.remove(guild_id);

        Ok(())
    }

    pub fn db(&self) -> &Connection {
        &self.sql_data_base
    }

    pub fn song(&self) -> &Option<LockedAudio> {
        &self.current_song
    }

    pub fn song_mut(&mut self) -> &mut Option<LockedAudio> {
        &mut self.current_song
    }

    pub fn playlist(&self) -> &Vec<Song> {
        &self.playlist
    }

    pub fn playlist_mut(&mut self) -> &mut Vec<Song> {
        &mut self.playlist
    }

    pub fn song_name(&self) -> &String {
        &self.song_name
    }

    pub fn song_name_mut(&mut self) -> &mut String {
        &mut self.song_name
    }

    pub fn start_playing(
        ctx: &Context,
        mediadata: &mut MediaData,
        msg: &Message,
    ) -> VerboseResult<()> {
        // check if there is already playing
        let already_started = mediadata.song().is_some();

        // if there isnt already a song playing, start a new one
        if !already_started {
            println!("next song");
            Self::next_song(ctx, mediadata, msg)?;
        }

        Ok(())
    }

    fn check_for_next(&mut self, ctx: &Context, msg: &Message) -> VerboseResult<String> {
        println!("check for next");

        while !self.playlist().is_empty() {
            let first = self.playlist_mut().remove(0).name;

            println!("check: {} ({})", &first, self.playlist().len());

            if fs::metadata(&first).is_err() {
                msg.channel_id
                    .say(
                        &ctx.http,
                        format!(
                            "\"{}\" doesn't exist locally anymore and will be removed from data base",
                            &first
                        ),
                    )
                    .map_err(|err| format!("{}", err))?;

                let sql = self.db();

                // check all tables for this entry
                let mut stmt =
                    match sql.prepare("SELECT name FROM sqlite_master WHERE type='table'") {
                        Ok(statement) => statement,
                        Err(_) => create_error!("failed preparing data base access"),
                    };

                let rows =
                    match stmt.query_map(params![], |row| row.get(0) as rusqlite::Result<String>) {
                        Ok(rows) => rows,
                        Err(_) => create_error!("failed querying rows"),
                    };

                for row in rows {
                    let table_name = match row {
                        Ok(name) => name,
                        Err(_) => create_error!("failed getting name from row"),
                    };

                    if let Err(_) = sql.execute(
                        &format!("DELETE FROM {} WHERE name = {}", table_name, first),
                        params![],
                    ) {
                        println!("{} does not contain {}", table_name, first);
                    }
                }
            } else {
                return Ok(first);
            }
        }

        println!("no song found!");
        create_error!("no suitable song found!")
    }

    fn check_for_channel<T: RawMutex>(
        manager: &mut MutexGuard<T, ClientVoiceManager>,
        ctx: &Context,
        msg: &Message,
    ) -> VerboseResult<()> {
        println!("check for channel!");

        let guild = guild(ctx, msg)?;
        let guild_id = guild.read().id;

        println!("got guild id");

        let author_channel_id = match guild
            .read()
            .voice_states
            .get(&msg.author.id)
            .and_then(|voice_state| voice_state.channel_id)
        {
            Some(channel) => channel,
            None => create_error!("author is not in a voice channel!"),
        };

        println!("got author channel");

        match manager.get(guild_id) {
            Some(handler) => {
                // check if the bot is in a channel
                if let None = handler.channel_id {
                    println!("handler had no channel");
                    manager.join(guild_id, author_channel_id);
                }
            }
            None => {
                println!("manager had no handler");
                manager.join(guild_id, author_channel_id);
            }
        }

        Ok(())
    }

    pub fn next_song(ctx: &Context, mediadata: &mut MediaData, msg: &Message) -> VerboseResult<()> {
        println!("start next song");
        let voice_manager = mediadata.voice_manager.clone();
        let mut manager = voice_manager.lock();

        println!("got manager lock");

        let guild_id = guild_id(ctx, msg)?;

        println!("got guild id");

        let mut need_to_leave = false;

        if mediadata.playlist().is_empty() {
            need_to_leave = true;
            *mediadata.song_mut() = None;
            *mediadata.song_name_mut() = String::new();

            if let Some(handler) = handler(guild_id, &mut manager) {
                handler.stop();
            }
        } else {
            let first = mediadata.check_for_next(ctx, msg)?;

            Self::check_for_channel(&mut manager, ctx, msg)?;

            let handler = {
                match handler(guild_id, &mut manager) {
                    Some(handler) => handler,
                    None => {
                        println!("failed getting handler");
                        create_error!("error getting handler");
                    }
                }
            };

            println!("got handler");

            let source = match ffmpeg(first.clone()) {
                Ok(mpeg) => mpeg,
                Err(_) => create_error!(format!("failed loading: {}", &first)),
            };

            handler.stop();

            let song = handler.play_returning(source);

            {
                let mut song_lock = song.lock();
                song_lock.volume(mediadata.volume);
            }

            *mediadata.song_mut() = Some(song);
            *mediadata.song_name_mut() = first.clone();

            msg.channel_id
                .say(&ctx.http, format!("Playing song: {}", first))
                .map_err(|err| format!("{}", err))?;
        }

        if need_to_leave {
            manager.remove(guild_id);
        }

        println!("successfully started next song");
        Ok(())
    }
}

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

pub struct Media;

impl TypeMapKey for Media {
    type Value = Arc<StdMutex<MediaData>>;
}
