use crate::{
    media::{TrackId, TrackInfo},
    FatalError,
};
use flow::{error, ok, Flow};
use rusqlite::Connection;
use serde::Serialize;
use std::collections::HashMap;
use std::{
    path::PathBuf,
    sync::{Arc, Mutex, RwLock},
};
use thiserror::Error;

#[derive(Debug, Error, PartialEq, Serialize)]
pub enum DatabaseError {
    #[error("database is unreadable")]
    DatabaseUnreadable,
    #[error("unhandled database problem: {0}")]
    UnhandledError(String),
}

pub trait MusicIndex: Sync + Send {
    fn add_track(&self, track: TrackInfo) -> Flow<(), FatalError, DatabaseError>;
    fn remove_track(&self, id: &TrackId) -> Flow<(), FatalError, DatabaseError>;
    fn get_track_info(&self, id: &TrackId) -> Flow<Option<TrackInfo>, FatalError, DatabaseError>;
    fn list_tracks<'a>(&'a self) -> Flow<Vec<TrackInfo>, FatalError, DatabaseError>;
}

pub struct MemoryIndex {
    tracks: RwLock<HashMap<TrackId, TrackInfo>>,
}

impl MemoryIndex {
    pub fn new() -> MemoryIndex {
        MemoryIndex {
            tracks: RwLock::new(HashMap::default()),
        }
    }
}

impl MusicIndex for MemoryIndex {
    fn add_track(&self, info: TrackInfo) -> Flow<(), FatalError, DatabaseError> {
        let mut tracks = self.tracks.write().unwrap();
        tracks.insert(info.id.clone(), info);
        ok(())
    }

    fn remove_track(&self, id: &TrackId) -> Flow<(), FatalError, DatabaseError> {
        let mut tracks = self.tracks.write().unwrap();
        tracks.remove(&id);
        ok(())
    }

    fn get_track_info<'a>(
        &'a self,
        id: &TrackId,
    ) -> Flow<Option<TrackInfo>, FatalError, DatabaseError> {
        let track = {
            let tracks = self.tracks.read().unwrap();
            tracks.get(&id).cloned()
        };
        ok(track)
    }

    fn list_tracks<'a>(&'a self) -> Flow<Vec<TrackInfo>, FatalError, DatabaseError> {
        ok(self
            .tracks
            .read()
            .unwrap()
            .values()
            .cloned()
            .collect::<Vec<TrackInfo>>())
    }
}

pub struct ManagedConnection<'a> {
    pool: &'a Database,
    conn: Option<Connection>,
}

impl<'a> Drop for ManagedConnection<'a> {
    fn drop(&mut self) {
        self.pool.r(self.conn.take().unwrap());
    }
}

#[derive(Clone)]
pub struct Database {
    path: PathBuf,
    pool: Arc<Mutex<Vec<Connection>>>,
}

impl Database {
    pub fn new(path: PathBuf) -> Flow<Database, FatalError, DatabaseError> {
        let connection = match Connection::open(path.clone()) {
            Ok(connection) => connection,
            Err(err) => return error(DatabaseError::UnhandledError(err.to_string())),
        };
        ok(Database {
            path,
            pool: Arc::new(Mutex::new(vec![connection])),
        })
    }

    pub fn r(&self, conn: Connection) {
        let mut pool = self.pool.lock().unwrap();
        pool.push(conn);
    }
}

#[cfg(test)]
mod test {
    use super::*;

    fn with_memory_index<F>(f: F)
    where
        F: Fn(&dyn MusicIndex),
    {
        let index = MemoryIndex::new();
        f(&index)
    }

    #[test]
    fn it_saves_and_loads_data() {
        with_memory_index(|index| {
            let info = TrackInfo {
                id: TrackId::from("track_1".to_owned()),
                track_number: None,
                name: None,
                album: None,
                artist: None,
                duration: None,
                filetype: "text/plain".parse::<mime::Mime>().unwrap(),
            };
            index.add_track(info.clone());

            assert_eq!(
                Flow::Ok(Some(info)),
                index.get_track_info(&TrackId::from("track_1".to_owned()))
            );
        });
    }
}
