use gstreamer::{prelude::*, ClockTime, MessageType, MessageView};
use std::sync::{Arc, RwLock};

pub struct AudioControl {
    bus: gstreamer::Bus,
    pipeline: gstreamer::Pipeline,
    mixer: gstreamer::Element,
    audio_sink: gstreamer::Element,

    bus_monitor: std::thread::JoinHandle<()>,

    playing: Arc<RwLock<bool>>,
}

impl Default for AudioControl {
    fn default() -> Self {
        let pipeline = gstreamer::Pipeline::new();
        let bus = pipeline.bus().unwrap();

        let mixer = gstreamer::ElementFactory::find("audiomixer")
            .unwrap()
            .load()
            .unwrap()
            .create()
            .build()
            .unwrap();
        pipeline.add(&mixer).unwrap();

        let audio_sink = gstreamer::ElementFactory::find("pulsesink")
            .unwrap()
            .load()
            .unwrap()
            .create()
            .build()
            .unwrap();
        pipeline.add(&audio_sink).unwrap();
        mixer.link(&audio_sink).unwrap();

        let playing = Arc::new(RwLock::new(false));

        let bus_monitor = std::thread::spawn({
            let pipeline_object = pipeline.clone().upcast::<gstreamer::Object>();
            let playing = playing.clone();
            let bus = bus.clone();
            move || loop {
                if let Some(msg) = bus.timed_pop_filtered(
                    ClockTime::NONE,
                    &[
                        MessageType::Error,
                        MessageType::Eos,
                        MessageType::StateChanged,
                    ],
                ) {
                    match msg.view() {
                        MessageView::StateChanged(st) => {
                            if msg.src() == Some(&pipeline_object) {
                                *playing.write().unwrap() = st.current() == gstreamer::State::Playing;
                            }
                        }
                        MessageView::Error(err) => {
                            println!("error: {:?}", err);
                        }
                        MessageView::Eos(_) => {
                            println!("EOS");
                        }
                        _ => {
                            unreachable!();
                        }
                    }
                }
            }
        });

        Self {
            bus,
            pipeline,
            mixer,
            audio_sink,

            bus_monitor,

            playing,
        }
    }
}

impl AudioControl {
    pub fn playing(&self) -> bool {
        *self.playing.read().unwrap()
    }

    pub fn play(&self) {
        let mut playing = self.playing.write().unwrap();
        if !*playing {
            // self.pipeline.set_state(gstreamer::State::Playing).unwrap();
            *playing = true;
        }
    }

    pub fn stop(&self) {
        let mut playing = self.playing.write().unwrap();
        if *playing {
            // self.pipeline.set_state(gstreamer::State::Paused).unwrap();
            *playing = false;
        }
    }

    pub fn add_track(&mut self, path: String) {
        let source = gstreamer::ElementFactory::find("filesrc")
            .unwrap()
            .load()
            .unwrap()
            .create()
            .property("location", path)
            .build()
            .unwrap();
        self.pipeline.add(&source).unwrap();

        let decoder = gstreamer::ElementFactory::find("decodebin")
            .unwrap()
            .load()
            .unwrap()
            .create()
            .build()
            .unwrap();
        self.pipeline.add(&decoder).unwrap();
        source.link(&decoder).unwrap();

        let volume = gstreamer::ElementFactory::find("volume")
            .unwrap()
            .load()
            .unwrap()
            .create()
            .property("mute", false)
            .property("volume", 0.75)
            .build()
            .unwrap();
        self.pipeline.add(&volume).unwrap();
        volume.link(&self.mixer).unwrap();

        decoder.connect_pad_added(move |_, pad| {
            let next_pad = volume.static_pad("sink").unwrap();
            pad.link(&next_pad).unwrap();
        });
    }

    pub fn remove_track(&mut self, path: String) {
        /* Need to run EOS through to a probe on the trailing end of the volume element */
    }
}