package com.ffii.core.utils

import org.apache.pdfbox.pdmodel.PDDocument
import org.apache.pdfbox.rendering.PDFRenderer
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import java.io.File
import java.net.Socket
import org.slf4j.LoggerFactory

class BrotherPrinterUtil {

    companion object {
        private const val SOCKET_TIMEOUT_MS = 120_000  // 2 min for large jobs; Brother may be slow to accept
        private const val BROTHER_DPI = 200           // Lower DPI = smaller job; some Brother models buffer better at 200

        private val log = LoggerFactory.getLogger(BrotherPrinterUtil::class.java)

        /**
         * Sends a PDF to a Brother (e.g. DCP-1610W) by rendering to PCL raster over port 9100.
         * Uses socket timeout and flush-per-page so the printer has time to accept data.
         */
        fun printToBrother(pdfFile: File, printerIp: String, printerPort: Int = 9100, printQty: Int = 1) {
            if (!pdfFile.exists()) throw IllegalArgumentException("File not found: ${pdfFile.absolutePath}")

            log.info("Brother print: file=${pdfFile.name} size=${pdfFile.length()} ip=$printerIp port=$printerPort copies=$printQty")

            PDDocument.load(pdfFile).use { document ->
                val renderer = PDFRenderer(document)
                val totalPages = document.numberOfPages

                Socket(printerIp, printerPort).use { socket ->
                    socket.soTimeout = SOCKET_TIMEOUT_MS
                    val os = socket.getOutputStream()

                    // 1. PJL header (optional; some Brother prefer minimal PJL)
                    os.write(getPjlHeader(printQty))
                    os.flush()

                    repeat(printQty) { copyIndex ->
                        for (pageIndex in 0 until totalPages) {
                            val image = renderer.renderImageWithDPI(pageIndex, BROTHER_DPI.toFloat(), org.apache.pdfbox.rendering.ImageType.BINARY)
                            val pclData = convertImageToPcl(image, BROTHER_DPI)
                            os.write(pclData)
                            os.write("\u000C".toByteArray(Charsets.US_ASCII))  // Form feed
                            os.flush()  // Flush per page so Brother receives incrementally
                        }
                    }

                    os.write("\u001B%-12345X".toByteArray(Charsets.US_ASCII))
                    os.flush()
                    log.info("Brother print job sent successfully")
                }
            }
        }

        private fun getPjlHeader(qty: Int): ByteArray {
            val pjl = StringBuilder()
            pjl.append("\u001B%-12345X@PJL\r\n")
            pjl.append("@PJL SET COPIES=1\r\n")  // We send copies in app; some Brother ignore COPIES or mis-handle it
            pjl.append("@PJL ENTER LANGUAGE=PCL\r\n")
            return pjl.toString().toByteArray(Charsets.US_ASCII)
        }

        /**
         * Converts a BufferedImage into PCL raster graphics for Brother.
         * Uses given DPI (e.g. 200) to keep job size smaller for printers with limited buffer.
         */
        private fun convertImageToPcl(image: BufferedImage, dpi: Int): ByteArray {
            val out = ByteArrayOutputStream()
            val width = image.width
            val height = image.height

            // PCL reset (Brother often needs clean state)
            out.write("\u001BE".toByteArray(Charsets.US_ASCII))       // Esc E = reset
            // PCL: Set resolution and start raster
            out.write("\u001B*t${dpi}R".toByteArray(Charsets.US_ASCII))
            out.write("\u001B*r1A".toByteArray(Charsets.US_ASCII)) 

            for (y in 0 until height) {
                val rowBytes = (width + 7) / 8
                val data = ByteArray(rowBytes)
                
                for (x in 0 until width) {
                    // Check if pixel is dark (threshold)
                    val color = image.getRGB(x, y)
                    val isBlack = (color and 0xFF) < 128 
                    if (isBlack) {
                        data[x / 8] = (data[x / 8].toInt() or (0x80 ushr (x % 8))).toByte()
                    }
                }
                
                // PCL: Transfer Raster Data (Esc * b # W [data])
                out.write("\u001B*b${rowBytes}W".toByteArray())
                out.write(data)
            }

            // PCL: End Raster Graphics
            out.write("\u001B*rB".toByteArray())
            return out.toByteArray()
        }
    }
}