use crate::{media::TrackId, Error, FatalError};
use flow::{ok, return_error, Flow};
use gstreamer::{format::ClockTime, prelude::*, MessageView, StateChangeError};
use std::{
    path::PathBuf,
    sync::mpsc::{channel, Receiver, Sender},
    thread::{self, JoinHandle},
    time::Duration,
};
use urlencoding::encode;

pub enum PlaybackControl {
    PlayTrack(TrackId),
    Stop,
    Exit,
}

pub enum PlaybackStatus {
    Stopped,
}

pub struct Playback {
    handle: JoinHandle<Flow<(), FatalError, Error>>,
    control_tx: Sender<PlaybackControl>,
}

impl Playback {
    pub fn new() -> Playback {
        let (control_tx, control_rx) = channel::<PlaybackControl>();

        let handle = thread::spawn(move || {
            let mut pipeline = None;
            loop {
                match control_rx.recv().unwrap() {
                    PlaybackControl::PlayTrack(id) => match play_track(id) {
                        Flow::Ok(pipeline_) => pipeline = Some(pipeline_),
                        Flow::Fatal(err) => panic!("fatal error: {:?}", err),
                        Flow::Err(err) => panic!("playback error: {:?}", err),
                    },
                    PlaybackControl::Stop => {
                        if let Some(ref pipeline) = pipeline {
                            return_error!(Flow::from(
                                pipeline
                                    .set_state(gstreamer::State::Paused)
                                    .map_err(|_| Error::CannotStop)
                            ));
                        }
                    }
                    PlaybackControl::Exit => return ok(()),
                }
            }
        });

        Self { handle, control_tx }
    }

    pub fn play_track(&self, id: TrackId) {
        self.control_tx
            .send(PlaybackControl::PlayTrack(id))
            .unwrap();
    }
}

fn play_track(id: TrackId) -> Flow<gstreamer::Element, FatalError, Error> {
    let pb = PathBuf::from(id.as_ref());
    let path = pb
        .iter()
        .skip(1)
        .map(|component| encode(&component.to_string_lossy()).into_owned())
        .collect::<PathBuf>();
    let playbin = format!("playbin uri=file:///{}", path.display());
    println!("setting up to play {}", playbin);
    let pipeline = return_error!(Flow::from(
        gstreamer::parse_launch(&playbin).map_err(|err| Error::GlibError(err))
    ));
    println!("ready to play");
    return_error!(Flow::from(
        pipeline
            .set_state(gstreamer::State::Playing)
            .map_err(|_| Error::CannotPlay)
    ));
    println!("playing started");
    ok(pipeline)
}

/*
fn play_track(id: TrackId) -> Flow<(), FatalError, Error> {
    let playbin = format!("playbin uri=file://{}", id.as_ref());
    let pipeline = return_error!(Flow::from(
        gstreamer::parse_launch(&playbin).map_err(|err| Error::GlibError(err))
    ));
    return_error!(Flow::from(
        pipeline
            .set_state(gstreamer::State::Playing)
            .map_err(|_| Error::CannotPlay)
    ));

    let message_handler = {
        let pipeline = pipeline.clone();
        thread::spawn(move || {
            let bus = pipeline.bus().unwrap();
            for msg in bus.iter_timed(gstreamer::ClockTime::NONE) {
                match msg.view() {
                    MessageView::Eos(_) => (),
                    MessageView::Error(err) => {
                        println!(
                            "Error from {:?}: {} ({:?})",
                            err.src().map(|s| s.path_string()),
                            err.error(),
                            err.debug()
                        );
                    }
                    msg => println!("{:?}", msg),
                }
            }
        })
    };

    let query_handler = {
        let pipeline = pipeline.clone();
        thread::spawn(move || loop {
            let position: Option<ClockTime> = pipeline.query_position();
            let duration: Option<ClockTime> = pipeline.query_duration();
            println!("Position {:?} {:?}", position, duration);
            thread::sleep(Duration::from_millis(100));
        })
    };

    ok(())
}
*/