use crate::{
    database::MusicIndex,
    media::{TrackId, TrackInfo},
    playback::{Playback, PlaybackControl, PlaybackStatus},
    scanner::MusicScanner,
    Error, FatalError,
};
use flow::{ok, return_error, Flow};
use gstreamer::{format::ClockTime, prelude::*, MessageView};
use std::{
    sync::{Arc, Mutex, RwLock},
    thread,
    time::{Duration, Instant},
};
use tokio::{
    sync::mpsc::{channel, Receiver, Sender},
    task::AbortHandle,
};

fn scan_frequency() -> Duration {
    Duration::from_secs(60)
}

#[derive(Clone, Debug)]
pub enum ControlMsg {
    PlayTrack(TrackId),
    Exit,
}

#[derive(Clone, Debug)]
pub enum TrackMsg {
    UpdateInProgress,
    UpdateComplete,
}

#[derive(Clone, Debug)]
pub enum PlaybackMsg {
    PositionUpdate,
    Playing,
    Pausing,
    Stopping,
}

pub struct Core {
    db: Arc<dyn MusicIndex>,
    playback_controller: Option<Playback>,
    next_scan: Arc<RwLock<Instant>>,
    scanner_handle: thread::JoinHandle<()>,
}

impl Core {
    pub fn new(
        db: Arc<dyn MusicIndex>,
        scanner: Arc<dyn MusicScanner>,
    ) -> Result<Core, FatalError> {
        let db = db;

        let next_scan = Arc::new(RwLock::new(Instant::now()));
        let scanner_handle = {
            let db = db.clone();
            let next_scan = next_scan.clone();
            thread::spawn(move || scan_loop(scanner, db, next_scan))
        };

        Ok(Core {
            db,
            playback_controller: None,
            next_scan,
            scanner_handle,
        })
    }

    pub fn list_tracks<'a>(&'a self) -> Flow<Vec<TrackInfo>, FatalError, Error> {
        self.db.list_tracks().map_err(Error::DatabaseError)
    }

    pub fn play_track<'a>(&'a mut self, id: TrackId) -> Result<(), Error> {
        self.stop_playback()?;
        self.playback_controller = Some(Playback::new(id)?);
        Ok(())
    }

    pub fn stop_playback<'a>(&'a mut self) -> Result<(), Error> {
        match self.playback_controller {
            Some(ref controller) => controller.stop()?,
            None => (),
        }
        self.playback_controller = None;
        Ok(())
    }
}

pub fn scan_loop(
    scanner: Arc<dyn MusicScanner>,
    db: Arc<dyn MusicIndex>,
    next_scan: Arc<RwLock<Instant>>,
) {
    loop {
        if Instant::now() >= *next_scan.read().unwrap() {
            let scan_start = Instant::now();
            let mut counter = 0;
            for track in scanner.scan() {
                counter += 1;
                match track {
                    Ok(track) => db.add_track(track),
                    Err(_) => ok(()),
                };
            }
            *next_scan.write().unwrap() = Instant::now() + scan_frequency();
            println!(
                "scanned {} files in {:?}",
                counter,
                Instant::now() - scan_start
            );
        }
        thread::sleep(Duration::from_secs(1));
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::{database::MemoryIndex, media::TrackId, scanner::factories::MockScanner};
    use std::collections::HashSet;

    fn with_example_index<F>(f: F)
    where
        F: Fn(Core),
    {
        let index = MemoryIndex::new();
        let scanner = MockScanner::new();
        match Core::new(Arc::new(index), Arc::new(scanner)) {
            Ok(core) => {
                thread::sleep(Duration::from_millis(10));
                f(core)
            }
            Err(error) => panic!("{:?}", error),
        }
    }

    #[test]
    fn it_lists_tracks() {
        with_example_index(|core| match core.list_tracks() {
            Flow::Ok(tracks) => {
                let track_ids = tracks
                    .iter()
                    .map(|t| t.id.clone())
                    .collect::<HashSet<TrackId>>();
                assert_eq!(track_ids.len(), 5);
                assert_eq!(
                    track_ids,
                    HashSet::from([
                        TrackId::from("/home/savanni/Track 1.mp3".to_owned()),
                        TrackId::from("/home/savanni/Track 2.mp3".to_owned()),
                        TrackId::from("/home/savanni/Track 3.mp3".to_owned()),
                        TrackId::from("/home/savanni/Track 4.mp3".to_owned()),
                        TrackId::from("/home/savanni/Track 5.mp3".to_owned()),
                    ])
                );
            }
            Flow::Fatal(err) => panic!("fatal error: {:?}", err),
            Flow::Err(err) => panic!("error: {:?}", err),
        })
    }
}
