📄 src/receipt_printer/receipt_printer_actor.rs
use std::{sync::Arc, time::Duration};

use escpos::errors::PrinterError;
use tokio::{
    sync::{mpsc, oneshot},
    time::timeout,
};

use super::escpos_printer::EscposPrinter;

pub struct EscposPrinterActor {
    host: String,
    port: u16,
    printer: Option<EscposPrinter>,
    receiver: mpsc::UnboundedReceiver<EscposPrinterRequest>,
}

pub enum EscposPrinterRequest {
    PrintImage(Arc<[u8]>, oneshot::Sender<Result<(), PrinterError>>),
}

impl EscposPrinterActor {
    pub fn run(host: String, port: u16, receiver: mpsc::UnboundedReceiver<EscposPrinterRequest>) {
        let mut actor = Self {
            host,
            port,
            printer: None,
            receiver,
        };

        tokio::spawn(async move {
            loop {
                let future = timeout(Duration::from_secs(5), actor.receiver.recv());
                match future.await {
                    Ok(Some(request)) => {
                        let response_result = match request {
                            EscposPrinterRequest::PrintImage(image, responder) => {
                                responder.send(actor.print_image(&image))
                            }
                        };
                        if let Err(error) = response_result {
                            println!("Could not respond to request: {error:?}");
                        }
                    }
                    Err(_) => actor.disconnect_printer(),
                    Ok(None) => return,
                }
            }
        });
    }

    pub fn print_image(&mut self, image: &[u8]) -> Result<(), PrinterError> {
        let printer = self.ensure_printer_connection()?;

        printer.print_image(image)
    }

    fn ensure_printer_connection(&mut self) -> Result<&mut EscposPrinter, PrinterError> {
        let printer = match self.printer.take() {
            Some(printer) => printer,
            None => EscposPrinter::open(&self.host, self.port)?,
        };
        Ok(self.printer.insert(printer))
    }

    fn disconnect_printer(&mut self) {
        _ = self.printer.take();
    }
}