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

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

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

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

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

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

    fn remove_track(&mut 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<Track>, FatalError, DatabaseError> {
        let track = {
            let tracks = self.tracks.read().unwrap();
            tracks.get(&id).cloned()
        };
        ok(track)
    }
}

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)),
        };
        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);
    }
}