📄 src/receipt_printer.rs
use std::{fmt::Display, sync::Arc};

use escpos::{
    driver::NetworkDriver,
    errors::PrinterError,
    printer::Printer,
    utils::{BitImageOption, BitImageSize, Protocol},
};
use tokio::sync::{mpsc, oneshot};

pub struct ReceiptPrinter {
    sender: mpsc::UnboundedSender<EscposPrinterRequest>,
}

impl ReceiptPrinter {
    pub const IMAGE_WIDTH_PIXEL: u32 = 72 * 8;
    pub const IMAGE_WIDTH_CM: f32 = 7.2;
    pub const IMAGE_WIDTH_PT: f32 = Self::IMAGE_WIDTH_CM / 0.03527;
    pub const IMAGE_PIXELS_PER_PT: f32 = Self::IMAGE_WIDTH_PIXEL as f32 / Self::IMAGE_WIDTH_PT;

    pub fn init(host: String, port: u16) -> Self {
        let (sender, receiver) = mpsc::unbounded_channel();

        EscposPrinterActor {
            host,
            port,
            printer: None,
            receiver,
        }
        .run();

        Self { sender }
    }

    pub async fn print_image(&self, image: Arc<[u8]>) -> Result<(), ReceiptPrinterError> {
        let (responder, receiver) = oneshot::channel();
        self.sender
            .send(EscposPrinterRequest::PrintImage(image, responder))
            .map_err(ReceiptPrinterError::send)?;
        receiver
            .await
            .map_err(ReceiptPrinterError::receive)?
            .map_err(ReceiptPrinterError::printer)
    }
}

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

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

impl EscposPrinterActor {
    fn run(mut self) {
        tokio::spawn(async move {
            while let Some(request) = self.receiver.recv().await {
                let response_result = match request {
                    EscposPrinterRequest::PrintImage(image, responder) => {
                        responder.send(self.print_image(&image))
                    }
                };
                if let Err(error) = response_result {
                    println!("Could not respond to request: {error:?}");
                }
            }
        });
    }

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

        printer.print_image(image)
    }

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

struct EscposPrinter {
    printer: Printer<NetworkDriver>,
}

impl EscposPrinter {
    fn open(host: &str, port: u16) -> Result<Self, PrinterError> {
        let driver = NetworkDriver::open(host, port, None)?;
        Ok(Self {
            printer: Printer::new(driver, Protocol::default(), None),
        })
    }

    fn print_image(&mut self, image: &[u8]) -> Result<(), PrinterError> {
        self.printer.init()?;
        self.printer.bit_image_from_bytes_option(
            image,
            BitImageOption::new(
                Some(ReceiptPrinter::IMAGE_WIDTH_PIXEL),
                None,
                BitImageSize::Normal,
            )?,
        )?;
        self.printer.print_cut()?;
        Ok(())
    }
}

pub struct ReceiptPrinterError {
    variant: ReceiptPrinterErrorVariants,
}

impl ReceiptPrinterError {
    fn send(send_error: mpsc::error::SendError<EscposPrinterRequest>) -> Self {
        Self {
            variant: ReceiptPrinterErrorVariants::Send(send_error),
        }
    }
    fn receive(recv_error: oneshot::error::RecvError) -> Self {
        Self {
            variant: ReceiptPrinterErrorVariants::Receive(recv_error),
        }
    }
    fn printer(printer_error: PrinterError) -> Self {
        Self {
            variant: ReceiptPrinterErrorVariants::Printer(printer_error),
        }
    }
}

enum ReceiptPrinterErrorVariants {
    Send(mpsc::error::SendError<EscposPrinterRequest>),
    Receive(oneshot::error::RecvError),
    Printer(PrinterError),
}

impl Display for ReceiptPrinterError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match &self.variant {
            ReceiptPrinterErrorVariants::Send(send_error) => send_error.fmt(f),
            ReceiptPrinterErrorVariants::Receive(recv_error) => recv_error.fmt(f),
            ReceiptPrinterErrorVariants::Printer(printer_error) => printer_error.fmt(f),
        }
    }
}