package com.unibuy.smartdevice.devices;

import static com.unibuy.smartdevice.devices.PortTools.match;

import android.content.Context;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.util.Log;

import com.unibuy.smartdevice.exception.Logs;
import com.unibuy.smartdevice.exception.LogsEmptyException;
import com.unibuy.smartdevice.exception.LogsIOException;
import com.unibuy.smartdevice.exception.LogsUnsupportedOperationException;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import com.hoho.android.usbserial.driver.UsbSerialProber;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class UsbDev {
    private Logs logs;
    private Context context;
    private HashMap<String, UsbDevice> deviceList;
    private UsbSerialPort usbSerialPort;
    private static UsbManager usbManager;
    public static void setUsbManager(Context context) {
        usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
    }

    public static UsbManager getUsbManager() {
        return usbManager;
    }

    public HashMap<String, UsbDevice> getDeviceList() {
        return deviceList;
    }

    public UsbDev() throws LogsUnsupportedOperationException, LogsIOException {
        this.logs = new Logs(this.getClass());
        initDeviceList();
    }

    public UsbDev(DeviceType deviceType) throws LogsUnsupportedOperationException, LogsIOException, LogsEmptyException {
        this();
        connectPort(deviceType);
    }

    public UsbDev(SerialPort serialPort) throws LogsUnsupportedOperationException, LogsIOException, LogsEmptyException {
        this();
        connectPort(serialPort);
    }

    private void initDeviceList() throws LogsIOException, LogsUnsupportedOperationException {
        Map<SerialPort, UsbDev> usbDeviceMap = new HashMap<>();

        if (usbManager == null) {
            throw new LogsIOException(logs, "UsbService not fund");
        } else {
            deviceList = usbManager.getDeviceList();

            if (deviceList.isEmpty()) {
                throw new LogsUnsupportedOperationException(logs, "initDeviceList:No USB devices connected");
            }
        }
    }

    private SerialPort getSerialPort(DeviceType deviceType) throws LogsEmptyException {
        int vendorId = 0;
        int productId = 0;

        for (UsbDevice device : deviceList.values()) {
            vendorId = device.getVendorId();
            productId = device.getProductId();

            SerialPort serialPort = SerialPort.getPortsByVendorInfo(vendorId, productId);
            if (serialPort.getDeviceType().equals(deviceType)) {
                logs.debug("getSerialPort vendorId:" + vendorId + " productId:" + productId + " deviceType:" + serialPort.getDeviceType() + " Ok");
                return serialPort;
            } else {
                logs.debug("getSerialPort vendorId:" + vendorId + " productId:" + productId + " deviceType:" + serialPort.getDeviceType() + " No");
            }
        }

        throw new LogsEmptyException(logs, "getSerialPort not found:" + deviceType.name());
    }

    private UsbDevice getDevice(DeviceType deviceType) throws LogsEmptyException {
        return getDevice(getSerialPort(deviceType));
    }

    private UsbDevice getDevice(SerialPort serialPort) throws LogsEmptyException {
        for (UsbDevice device : deviceList.values()) {
            int vendorId = device.getVendorId();
            int productId = device.getProductId();

            logs.info("getDevice vendorId:"+vendorId+" productId:"+productId+" sp-vendorId:" + serialPort.getVendorID() + " sp-productId:" + serialPort.getProductID());

            if (vendorId == serialPort.getVendorID() && productId == serialPort.getProductID()) {
                return device;
            }
        }

        throw new LogsEmptyException(logs, "getDevice Device not found:" + serialPort.getVendorID() + "/" + serialPort.getProductID());
    }

    public void connectPort(DeviceType deviceType) throws LogsEmptyException, LogsIOException {
        SerialPort serialPort = getSerialPort(deviceType);
        connectPort(serialPort);
    }

    public void connectPort(SerialPort serialPort) throws LogsEmptyException, LogsIOException {
        logs.info("connectPort vendor id:" + serialPort.getVendorID() + " product id:" + serialPort.getProductID());

        UsbDevice device = getDevice(serialPort);
        if (UsbSerialProber.getDefaultProber().probeDevice(device) == null) {
            logs.info("connectPort probeDevice(device) is null");
            return;
        }

        for (UsbSerialPort usp : UsbSerialProber.getDefaultProber().probeDevice(device).getPorts()) {
            logs.info("----- connectPort usp -----");
            logs.info("connectPort usp DeviceName:" + usp.getDevice().getDeviceName());
            logs.info("connectPort usp ProductName:" + usp.getDevice().getProductName());
        }

        usbSerialPort = UsbSerialProber.getDefaultProber().probeDevice(device).getPorts().get(0);

        if (usbSerialPort == null) {
            throw new LogsIOException(logs, "connectPort UsbSerialPort is null");
        }

        try {
            usbSerialPort.open(usbManager.openDevice(device));
            int parity = UsbSerialPort.PARITY_NONE;
            if (serialPort.getParity().equals("EVEN")) {
                parity = UsbSerialPort.PARITY_EVEN;
            }
            usbSerialPort.setParameters(serialPort.getBaudRate(), serialPort.getDataBits(), serialPort.getStopBits(), parity);
        } catch (IOException e) {
            throw new LogsIOException(logs, e.getLocalizedMessage());
        }
    }

    public boolean moveToMatch(byte[] matchBytes, int moveLimit, int timeout) throws LogsIOException {
        if (usbSerialPort == null) {
            throw new LogsIOException(logs, "moveToMatch UsbSerialPort is null");
        }

        try {
            byte[] buffer = new byte[1];
            int size = 0;
            int moveCount = 0;
            while (moveCount <= moveLimit) {
                size = usbSerialPort.read(buffer, timeout);
                if (size == 1 && match(buffer[0], matchBytes)) {
                    return true;
                }
                moveCount++;
            }
            return false;
        } catch (Exception e) {
            throw new LogsIOException(logs, e.getLocalizedMessage());
        }
    }

    public byte[] readToMatch(byte[] matchBytes, int readLimit, int timeout) throws LogsIOException {
        if (usbSerialPort == null) {
            throw new LogsIOException(logs, "readToMatch UsbSerialPort is null");
        }

        byte[] buffer = new byte[0];
        try {
            byte[] tmp = new byte[1];
            int size = 0;
            int moveCount = 0;
            while (moveCount <= readLimit) {
                size = usbSerialPort.read(buffer, timeout);
                if (size == 1 && match(tmp[0], matchBytes)) {
                    return PortTools.subtractByteArrays(buffer, matchBytes.length-1);
                }
                buffer = PortTools.concatByteArrays(buffer, tmp);
                moveCount++;
            }
            return buffer;
        } catch (Exception e) {
            throw new LogsIOException(logs, e.getLocalizedMessage());
        }
    }

    public int read(byte[] buffer, int timeout) throws LogsUnsupportedOperationException, LogsIOException {
        if (usbSerialPort == null) {
            throw new LogsIOException(logs, "read UsbSerialPort is null");
        }

        try {
            int size = usbSerialPort.read(buffer, timeout);
            return size;
        } catch (IOException e) {
            throw new LogsIOException(logs, e);
        }
    }

    public void send(byte[] buffer) throws LogsIOException {
        if (usbSerialPort == null) {
            throw new LogsIOException(logs, "send UsbSerialPort is null");
        }

        // 寫資料
        Log.i("send buffer", PortTools.showHex(buffer));
        try {
            usbSerialPort.write(buffer, 1000);
        } catch (IOException e) {
            throw new LogsIOException(logs, e);
        }
    }

    public int transmission(byte[] sendBuffer, byte[] readBuffer, int timeout) throws LogsIOException {
        if (usbSerialPort == null) {
            throw new LogsIOException(logs, "transmission UsbSerialPort is null");
        }

        try {
            Log.i("send buffer", PortTools.showHex(sendBuffer));
            usbSerialPort.write(sendBuffer, 1000);
            // 讀資料
            int size = usbSerialPort.read(readBuffer, timeout);
            Log.i("read buffer", PortTools.showHex(readBuffer));
            return size;
        } catch (IOException e) {
            throw new LogsIOException(logs, e);
        }
    }

    public void closePort() throws LogsIOException {
        if (usbSerialPort != null) {
            try {
                usbSerialPort.close();
            } catch (IOException e) {
                throw new LogsIOException(logs, e);
            }
            usbSerialPort = null;
        }
    }

    public static void getDeviceInfo(Context context) throws LogsUnsupportedOperationException {
        if (usbManager != null) {
            Logs logs = new Logs(UsbDev.class);

            HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();

            if (deviceList.isEmpty()) {
                throw new LogsUnsupportedOperationException(new Logs(context.getClass()), "getDeviceInfo:No USB devices connected");
            }

            for (UsbDevice device : deviceList.values()) {
                logs.info("----- usb device -----");

                int vendorId = device.getVendorId();
                int productId = device.getProductId();

                logs.info("Device Name: " + device.getDeviceName());
                logs.info("Product Name: " + device.getProductName());
                logs.info("Manufacturer Name: " + device.getManufacturerName());
                logs.info("Vendor ID: " + vendorId);
                logs.info("Product ID: " + productId);
                logs.info(SerialPort.getPortsByVendorInfo(vendorId, productId).toString());

//                logs.info("-----2-----");

                // 列出所有介面資訊
//                int interfaceCount = device.getInterfaceCount();
//                logs.info("Number of Interfaces: " + interfaceCount);
//
//                for (int i = 0; i < interfaceCount; i++) {
//                    UsbInterface usbInterface = device.getInterface(i);
//                    logs.info("Interface " + i + ":");
//                    logs.info("  Interface ID: " + usbInterface.getId());
//                    logs.info("  Interface Class: " + usbInterface.getInterfaceClass());
//                    logs.info("  Interface Subclass: " + usbInterface.getInterfaceSubclass());
//                    logs.info("  Interface Protocol: " + usbInterface.getInterfaceProtocol());
//                    logs.info("------------------");
//
//                    for (int j = 0; j < usbInterface.getEndpointCount(); j++) {
//                        UsbEndpoint endpoint = usbInterface.getEndpoint(j);
//                        logs.info("  Endpoint " + j + ":");
//                        logs.info("    Endpoint Address: " + endpoint.getAddress());
//                        logs.info("    Max Packet Size: " + endpoint.getMaxPacketSize());
//                        logs.info("    Endpoint Type: " + endpoint.getType());
//                        logs.info("    Direction: " +
//                                (endpoint.getDirection() == UsbConstants.USB_DIR_IN ? "IN" : "OUT"));
//                        logs.info("---------------------------");
//                    }
//                }
            }
        } else {
            throw new LogsUnsupportedOperationException(new Logs(context.getClass()), "UsbManager is not available");
        }
    }
}
