pub mod player;
pub mod util;
pub mod util;

use std::{
	sync::{
		atomic::{self, AtomicUsize},
		Arc,
	},
	time::Duration,
};

use anyhow::{anyhow, Result};
use parking_lot::RwLock;
use player::Event;
use url::Url;

mod plugin;
pub use plugin::*;

mod plugins;
pub use plugins::*;

pub struct PlayerController {
	sender: crossbeam_channel::Sender<Event>,
	state: Arc<RwLock<player::State>>,
	plugins: Vec<Box<dyn SourcePlugin>>,
	_stream: cpal::Stream,
	nb_queued: Arc<AtomicUsize>,
}

impl std::fmt::Debug for PlayerController {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		write!(f, "Player")
	}
}

impl PlayerController {
	pub fn new(
		state: Arc<RwLock<player::State>>,
		sender: crossbeam_channel::Sender<Event>,
		stream: cpal::Stream,
		nb_queued: Arc<AtomicUsize>,
	) -> Result<Self> {
		Ok(Self {
			state,
			sender,
			plugins: vec![Box::new(LocalPlugin), Box::new(SoundcloudPlugin::new()?)],
			_stream: stream,
			nb_queued,
		})
	}

	fn create_source(&self, url: &Url) -> Result<SongSource> {
		for plugin in &self.plugins {
			if let Some(source) = plugin.handle_url(&url) {
				let source = source.map_err(|err| {
					anyhow!(
						"plugin {} failed to handle this song {}",
						plugin.name(),
						err
					)
				})?;
				return Ok(source);
			}
		}
		Err(anyhow!("no plugin could find this song"))
	}

	pub fn queue_song(&mut self, url: Url) -> Result<()> {
		let source = self.create_source(&url)?;
		if let Err(e) = self.sender.send(Event::QueueSong(source)) {
			panic!("{:?}", e);
		}
		Ok(())
	}

	pub fn play(&mut self) -> Result<()> {
		self.sender
			.send(Event::Play)
			.map_err(|_| anyhow!("failed to play"))?;
		Ok(())
	}

	pub fn pause(&mut self) -> Result<()> {
		self.sender
			.send(Event::Pause)
			.map_err(|_| anyhow!("failed to pause"))?;
		Ok(())
	}

	pub fn play_pause(&mut self) -> Result<()> {
		if self
			.state
			.read()
			.get_playing()
			.ok_or(anyhow!("wrong state"))?
			.paused
		{
			self.play()
		} else {
			self.pause()
		}
	}

	pub fn seek(&mut self, position: Duration) -> Result<()> {
		self.sender.send(Event::Seek(position)).unwrap();
		Ok(())
	}

	pub fn state(&self) -> &RwLock<player::State> {
		&self.state
	}

	pub fn nb_queued(&self) -> usize {
		self.nb_queued.load(atomic::Ordering::Relaxed)
	}
}