package com.dlc.chemical.serial.usb

import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbManager
import android.os.Handler
import android.os.Looper
import com.dlc.chemical.serial.HexDump
import com.dlc.chemical.serial.listener.OnSerialListener
import com.hoho.android.usbserial.driver.*
import com.hoho.android.usbserial.util.SerialInputOutputManager
import com.rc.core.log.RcLog
import java.io.IOException

/**
 * info
 *
 * @author ReiChin_
 */
abstract class AbsUsbSerialManager(private val listener: OnSerialListener? = null) :
    SerialInputOutputManager.Listener {

    companion object {
        private const val DEVICE_VID = 0x067B // 0x0403
        private const val DEVICE_PID = 0x23A3 // 0x6001
        const val SCAN_DEVICE_ID = 0x03EB // 扫码枪设备ID
        private const val BAUD_RATE = 9600

        const val INTENT_ACTION_GRANT_USB: String = "com.dlc.chemical.GRANT_USB"
        const val INTENT_ACTION_STEELYARD_USB: String = "com.dlc.chemical.STEELYARD_USB"

        private const val MESSAGE_DELAY: Long = 200 // 延迟200ms，判断扫码是否完成。
        private const val WRITE_WAIT_MILLIS = 2000
    }

    private val mainLooper: Handler = Handler(Looper.getMainLooper())

    enum class UsbPermission {
        Unknown, Requested, Granted, Denied
    }

    private var usbSerialPort: UsbSerialPort? = null
    private var usbIoManager: SerialInputOutputManager? = null

    var usbPermission = UsbPermission.Unknown
    var connected = false

    abstract fun findUsbDevice(device: UsbDevice): Boolean
    abstract fun intentAction(): String

    open fun findUsbDevice(context: Context, device: UsbDevice): Boolean {
        return device.vendorId == DEVICE_VID && device.productId == DEVICE_PID && findUsbDevice(device)
    }

    fun connect(context: Context) {
        val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
        val device: UsbDevice? = usbManager.deviceList.values.find { findUsbDevice(context, it) }

        if (device == null) {
            listener?.onConnected(false, "connection failed: device not found")
            return
        }
        val driver =
            UsbSerialProber.getDefaultProber().probeDevice(device) ?: customProber().probeDevice(
                device
            )
        if (driver == null) {
            listener?.onConnected(false, "connection failed: no driver for device")
            return
        }
        if (driver.ports.size < 0) {
            listener?.onConnected(false, "connection failed: not enough ports at device")
            return
        }
        usbSerialPort = driver.ports[0]
        val usbConnection = usbManager.openDevice(driver.device)
        if (usbConnection == null && usbPermission == UsbPermission.Unknown && !usbManager.hasPermission(
                driver.device
            )
        ) {
            usbPermission =
                UsbPermission.Requested
            val usbPermissionIntent = PendingIntent.getBroadcast(
                context,
                0,
                Intent(intentAction()),
                0
            )
            usbManager.requestPermission(driver.device, usbPermissionIntent)
            return
        }
        if (usbConnection == null) {
            if (!usbManager.hasPermission(driver.device))
                listener?.onConnected(false, "connection failed: permission denied")
            else listener?.onConnected(false, "connection failed: open failed")
            return
        }

        try {
            usbSerialPort?.open(usbConnection)
            val serialParam = createSerialParam()
            usbSerialPort?.setParameters(serialParam.baudRate, serialParam.dataBits, serialParam.stopBits, serialParam.parity)
            usbIoManager = SerialInputOutputManager(usbSerialPort, this)
            usbIoManager?.start()
            connected = true
            listener?.onConnected(true, "connected")
        } catch (e: Exception) {
            listener?.onConnected(false, "connection failed: ${e.message}")
            disconnect()
        }
    }

    open fun createSerialParam(): SerialParam {
        return SerialParam(BAUD_RATE, UsbSerialPort.DATABITS_8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)
    }

    fun disconnect() {
        connected = false
        usbIoManager?.let {
            it.listener = null
            it.stop()
        }
        usbIoManager = null
        try {
            usbSerialPort?.close()
        } catch (ignored: IOException) {
        }
        usbSerialPort = null
    }

    private fun customProber(): UsbSerialProber {
        val customTable = ProbeTable()
        customTable.addProduct(DEVICE_VID, DEVICE_PID, FtdiSerialDriver::class.java)
        return UsbSerialProber(customTable)
    }

    override fun onNewData(data: ByteArray) {
        mainLooper.post { receive(data) }
    }

    override fun onRunError(e: Exception) {
        mainLooper.post {
            listener?.onConnected(false, "connection lost: ${e.message}")
            disconnect()
        }
    }

    abstract fun dispatchReceiveData(data: ByteArray)
    abstract fun handleReceiveData()

    private fun receive(data: ByteArray) {
        dispatchReceiveData(data)

        mHandler.removeCallbacks(mScanningFishedRunnable)
        mHandler.postDelayed(mScanningFishedRunnable, MESSAGE_DELAY)
    }

    private val mHandler by lazy { Handler(Looper.getMainLooper()) }
    private val mScanningFishedRunnable = Runnable {
        handleReceiveData()
    }

    fun send(data: ByteArray) {
        if (!connected) {
            listener?.onConnected(false, "not connected")
            return
        }
        try {
            RcLog.info("UsbSerial send cmd:${HexDump.toHexString(data)}")
            usbSerialPort?.write(data, WRITE_WAIT_MILLIS)
        } catch (e: Exception) {
            onRunError(e)
        }
    }

}

data class SerialParam(val baudRate: Int, // 波特率
                       val dataBits: Int, // 数据位
                       val stopBits: Int, // 停止位
                       @UsbSerialPort.Parity val parity: Int // 校验码
                       )