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

use rusqlite::params;

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

use serenity::prelude::*;
use serenity::voice::LockedAudio;
use serenity::{
    framework::standard::{macros::command, Args, CommandResult},
    model::channel::Message,
};

use utilities::prelude::*;

use std::sync::Once;
use std::thread;
use std::time::Duration;

static WATCHER: Once = Once::new();

#[command]
fn play(ctx: &mut Context, msg: &Message, mut args: Args) -> CommandResult {
    if let Err(err) = channel_contains_author(ctx, msg) {
        msg.channel_id.say(&ctx.http, err)?;
        return Ok(());
    }

    let mut data = ctx.data.write();
    let media = match data.get_mut::<Media>() {
        Some(media) => media,
        None => {
            msg.channel_id.say(&ctx.http, "could not find media data")?;
            return Ok(());
        }
    };

    {
        let mut media_lock = media.lock().unwrap();

        if args.len() == 0 {
            if !check_for_continue(media_lock.song_mut()) {
                msg.channel_id
                    .say(&ctx.http, "Must provide a URL to a video or audio")?;
            }
        } else {
            let first_arg = args.current().unwrap();

            if first_arg == "--local" {
                handle_local_request(&mut media_lock, ctx, msg)?;
            } else if first_arg == "--tag" {
                let _dummy = args.single::<String>()?;
                let mut arg_list = args.single::<String>()?;

                for arg in args.iter::<String>() {
                    arg_list += &format!(" {}", arg?.trim());
                }

                handle_tag_request(&mut media_lock, ctx, msg, &arg_list)?
            } else if first_arg.starts_with("http") {
                handle_http_request(&mut media_lock, ctx, msg, first_arg)?;
            } else {
                let mut arg_list = args.single::<String>()?;

                for arg in args.iter::<String>() {
                    arg_list += &format!(" {}", arg?.trim());
                }

                handle_song_request(&mut media_lock, ctx, msg, &arg_list)?
            }
        }

        println!("current song name: {}", media_lock.song_name());
    }

    WATCHER.call_once(|| {
        let media_data = media.clone();
        let message_clone = msg.clone();
        let context_clone = ctx.clone();

        thread::spawn(move || loop {
            {
                let mut media_lock = media_data.lock().unwrap();

                // check if there is a song currently being played
                let mut is_playing = false;

                if let Some(song) = media_lock.song() {
                    let song_lock = song.lock();

                    if !song_lock.finished {
                        is_playing = true;
                    }
                }

                if !is_playing {
                    if let Err(err) =
                        MediaData::next_song(&context_clone, &mut media_lock, &message_clone)
                    {
                        println!("{}", err);
                    }
                }
            }

            thread::sleep(Duration::from_millis(1500));
        });
    });

    Ok(())
}

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

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

fn append_songs(
    media: &mut MediaData,
    ctx: &Context,
    msg: &Message,
    mut source: Vec<Song>,
) -> VerboseResult<()> {
    media.playlist_mut().append(&mut source);

    println!("start playing");
    MediaData::start_playing(ctx, media, msg)?;

    Ok(())
}

fn handle_http_request(
    media: &mut MediaData,
    ctx: &serenity::client::Context,
    msg: &serenity::model::channel::Message,
    url: &str,
) -> VerboseResult<()> {
    let mut names = Vec::new();

    {
        let sql = media.db();

        let mut stmt = match sql.prepare("SELECT name FROM Vulva3 WHERE link = ?") {
            Ok(statement) => statement,
            Err(_) => create_error!("failed preparing data base access"),
        };

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

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

            names.push(name);
        }
    }

    if names.len() > 0 {
        msg.channel_id
            .say(&ctx.http, "song already loaded!")
            .map_err(|err| format!("{}", err))?;

        append_songs(
            media,
            ctx,
            msg,
            names.iter().map(|n| Song { name: n.clone() }).collect(),
        )?;
    } else {
        let source = match youtube_dl(&url) {
            Ok(source) => source,
            Err(why) => {
                println!("Err starting source: {:?}", why);

                msg.channel_id
                    .say(&ctx.http, format!("Error using youtube-dl: {}", why))
                    .map_err(|err| format!("{}", err))?;

                return Ok(());
            }
        };

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

        {
            let sql = media.db();

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

                if sql
                    .execute(
                        "INSERT INTO Vulva3 (name, link)
                        VALUES (?1, ?2)",
                        params![song.name, url],
                    )
                    .is_err()
                {
                    create_error!("failed inserting songs into db");
                }
            }
        }

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

        append_songs(media, ctx, msg, source)?;
    }

    Ok(())
}

fn handle_local_request(
    media: &mut MediaData,
    ctx: &serenity::client::Context,
    msg: &serenity::model::channel::Message,
) -> VerboseResult<()> {
    let mut songs = Vec::new();

    {
        let sql = media.db();

        let mut stmt = match sql.prepare("SELECT name FROM Vulva3") {
            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 name_result in rows {
            let name = match name_result {
                Ok(name) => name,
                Err(_) => create_error!("failed getting name from row"),
            };

            songs.push(Song { name });
        }
    }

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

    append_songs(media, ctx, msg, songs)?;

    Ok(())
}

fn handle_song_request(
    media: &mut MediaData,
    ctx: &serenity::client::Context,
    msg: &serenity::model::channel::Message,
    pattern: &str,
) -> VerboseResult<()> {
    println!("song request ({})", pattern);

    let mut songs = Vec::new();

    {
        let sql = media.db();

        let mut stmt = match sql.prepare(&format!(
            "SELECT name FROM Vulva3 WHERE name LIKE '%{}%'",
            pattern
        )) {
            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 name_result in rows {
            let name = match name_result {
                Ok(name) => name,
                Err(_) => create_error!("failed getting name from row"),
            };

            songs.push(Song { name });
        }
    }

    println!("{:?}", &songs);

    if !songs.is_empty() {
        let mut rng = thread_rng();
        songs.shuffle(&mut rng);

        println!("append songs");

        append_songs(media, ctx, msg, songs)?;
    } else {
        msg.channel_id
            .say(&ctx.http, format!("no song found with pattern {}", pattern))
            .map_err(|err| format!("{}", err))?;
    }

    Ok(())
}

fn handle_tag_request(
    media: &mut MediaData,
    ctx: &serenity::client::Context,
    msg: &serenity::model::channel::Message,
    pattern: &str,
) -> VerboseResult<()> {
    let mut songs = Vec::new();

    {
        let mut stmt = match media.db().prepare(&format!(
            "SELECT name FROM sqlite_master WHERE type='table' AND name='{}'",
            pattern
        )) {
            Ok(statement) => statement,
            Err(_) => create_error!("failed preparing data base access"),
        };

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

        if let None = rows.next() {
            msg.channel_id
                .say(&ctx.http, format!("tag ({}) not found", pattern))
                .map_err(|err| format!("{}", err))?;

            return Ok(());
        }

        let mut stmt = match media.db().prepare(&format!("SELECT name FROM {}", pattern)) {
            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 name_result in rows {
            let name = match name_result {
                Ok(name) => name,
                Err(_) => create_error!("failed getting name from row"),
            };

            songs.push(Song { name });
        }
    }

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

    append_songs(media, ctx, msg, songs)?;

    Ok(())
}
