use std::{collections::HashMap, sync::Arc, fmt::Display};
use tokio::sync::{mpsc, watch};
use tracing::error;

use crate::actor::workspace::Workspace;


#[derive(Debug)]
enum WorkspaceAction {
	ADD {
		key: String,
		w: Box<Workspace>,
	},
	REMOVE {
		key: String
	},
}

#[derive(Debug, Clone)]
pub struct WorkspacesView {
	watch: watch::Receiver<HashMap<String, Arc<Workspace>>>,
	op: mpsc::Sender<WorkspaceAction>,
}

impl WorkspacesView {
	pub fn borrow(&self) -> watch::Ref<HashMap<String, Arc<Workspace>>> {
		self.watch.borrow()
	}

	pub async fn add(&mut self, w: Workspace) {
		self.op.send(WorkspaceAction::ADD { key: w.id.to_string(), w: Box::new(w) }).await.unwrap();
	}

	pub async fn remove(&mut self, key: String) {
		self.op.send(WorkspaceAction::REMOVE { key }).await.unwrap();
	}
}

#[derive(Debug)]
pub struct StateManager {
	pub workspaces: WorkspacesView,
	pub run: watch::Receiver<bool>,
	run_tx: watch::Sender<bool>,
}

impl Drop for StateManager {
	fn drop(&mut self) {
		self.run_tx.send(false).unwrap_or_else(|e| {
			error!("Could not stop StateManager worker: {:?}", e);
		})
	}
}

impl StateManager {
	pub fn new() -> Self {
		let (tx, rx) = mpsc::channel(32); // TODO quantify backpressure
		let (workspaces_tx, workspaces_rx) = watch::channel(HashMap::new());
		let (run_tx, run_rx) = watch::channel(true);

		let s = StateManager { 
			workspaces: WorkspacesView { watch: workspaces_rx, op: tx },
			run_tx, run: run_rx,
		};

		s.workspaces_worker(rx, workspaces_tx);

		return s;
	}

	fn workspaces_worker(&self, mut rx: mpsc::Receiver<WorkspaceAction>, tx: watch::Sender<HashMap<String, Arc<Workspace>>>) {
		let run = self.run.clone();
		tokio::spawn(async move {
			let mut store = HashMap::new();

			while run.borrow().to_owned() {
				if let Some(event) = rx.recv().await {
					match event {
						WorkspaceAction::ADD { key, w } => {
							store.insert(key, Arc::new(*w)); // TODO put in hashmap
						},
						WorkspaceAction::REMOVE { key } => {
							store.remove(&key);
						},
					}
					tx.send(store.clone()).unwrap();
				} else {
					break
				}
			}
		});
	}

	pub fn view(&self) -> WorkspacesView {
		return self.workspaces.clone();
	}

	/// get a workspace Arc directly, without passing by the WorkspacesView
	pub fn get(&self, key: &String) -> Option<Arc<Workspace>> {
		if let Some(w) = self.workspaces.borrow().get(key) {
			return Some(w.clone());
		}
		return None;
	}
}