import {FrameType} from "../../../src/adapter/ezsp/driver/frame";
import {Parser} from "../../../src/adapter/ezsp/driver/parser";
import {SerialDriver} from "../../../src/adapter/ezsp/driver/uart";
import {Writer} from "../../../src/adapter/ezsp/driver/writer";
import {SerialPort} from "../../../src/adapter/serialPort";

const mockSerialPortClose = vi.fn().mockImplementation((cb) => (cb ? cb() : null));
const mockSerialPortFlush = vi.fn().mockImplementation((cb) => cb());
const mockSerialPortAsyncFlushAndClose = vi.fn();
const mockSerialPortPipe = vi.fn();
const mockSerialPortOpen = vi.fn().mockImplementation((cb) => cb());
const mockSerialPortAsyncOpen = vi.fn();
const mockSerialPortConstructor = vi.fn();
const mockSerialPortOnce = vi.fn();
const mockSerialPortSet = vi.fn().mockImplementation((_opts, cb) => cb());
const mockSerialPortWrite = vi.fn((_buffer, cb) => (cb ? cb() : null));
const mockSerialPortIsOpen = false;

vi.mock("../../../src/adapter/serialPort", () => ({
    SerialPort: vi.fn(() => ({
        close: mockSerialPortClose,
        constructor: mockSerialPortConstructor,
        emit: () => {},
        on: () => {},
        once: mockSerialPortOnce,
        open: mockSerialPortOpen,
        pipe: mockSerialPortPipe,
        set: mockSerialPortSet,
        write: mockSerialPortWrite,
        flush: mockSerialPortFlush,
        isOpen: mockSerialPortIsOpen,
        asyncOpen: mockSerialPortAsyncOpen,
        asyncFlushAndClose: mockSerialPortAsyncFlushAndClose,
    })),
}));

vi.mock("../../../src/utils/wait", () => ({
    wait: vi.fn(() => {
        return new Promise<void>((resolve) => resolve());
    }),
}));

let writeBufferSpy;

const mocks = [
    mockSerialPortClose,
    mockSerialPortPipe,
    mockSerialPortConstructor,
    mockSerialPortOpen,
    mockSerialPortOnce,
    mockSerialPortWrite,
    SerialPort,
    mockSerialPortAsyncFlushAndClose,
    mockSerialPortAsyncOpen,
];

describe("UART", () => {
    let serialDriver;
    beforeAll(() => {
        vi.useFakeTimers();
    });

    afterAll(() => {
        vi.useRealTimers();
        vi.restoreAllMocks();
    });

    beforeEach(() => {
        for (const mock of mocks) {
            // @ts-ignore
            mock.mockClear();
        }

        // @ts-ignore; make sure we always get a new instance
        serialDriver = new SerialDriver();
        writeBufferSpy = vi.spyOn(Writer.prototype, "writeBuffer").mockImplementation((buffer) => {
            if (buffer[0] === 0x1a) {
                serialDriver.waitress.resolve({sequence: -1});
            }
        });
        vi.spyOn(Writer.prototype, "pipe").mockImplementation(vi.fn());
    });

    afterEach(() => {
        writeBufferSpy.mockRestore();
    });

    it("Connect", async () => {
        await serialDriver.connect({path: "/dev/ttyACM0"});

        expect(SerialPort).toHaveBeenCalledTimes(1);
        expect(SerialPort).toHaveBeenCalledWith({
            path: "/dev/ttyACM0",
            baudRate: 115200,
            rtscts: false,
            autoOpen: false,
            parity: "none",
            stopBits: 1,
            xon: true,
            xoff: true,
        });

        expect(mockSerialPortPipe).toHaveBeenCalledTimes(1);
        expect(mockSerialPortAsyncOpen).toHaveBeenCalledTimes(1);
        expect(mockSerialPortOnce).toHaveBeenCalledTimes(1);
        expect(writeBufferSpy).toHaveBeenCalledTimes(1);
    });

    it("Send data", async () => {
        await serialDriver.connect("/dev/ttyACM0", {});
        // send 8 frames
        serialDriver.sendDATA(Buffer.from([1, 2, 3]));
        serialDriver.sendDATA(Buffer.from([1, 2, 3]));
        serialDriver.sendDATA(Buffer.from([1, 2, 3]));
        serialDriver.sendDATA(Buffer.from([1, 2, 3]));
        serialDriver.sendDATA(Buffer.from([1, 2, 3]));
        serialDriver.sendDATA(Buffer.from([1, 2, 3]));
        serialDriver.sendDATA(Buffer.from([1, 2, 3]));
        serialDriver.sendDATA(Buffer.from([1, 2, 3]));
        expect(writeBufferSpy).toHaveBeenCalledTimes(2);
        // send another 2 frame - not counted, until resolve 8 promices
        serialDriver.sendDATA(Buffer.from([1, 2, 3]));
        serialDriver.sendDATA(Buffer.from([1, 2, 3]));
        expect(writeBufferSpy).toHaveBeenCalledTimes(2);
    });

    it("Receive data", () => {
        const parsed = [];
        const parser = new Parser();
        parser.on("parsed", (result) => parsed.push(result));
        // send 4 frames
        const buffer0 = Buffer.from([0xc1, 0x02, 0x0b, 0x0a, 0x52, 0x7e]);
        const buffer1 = Buffer.from([
            0x22, 0x5b, 0xb1, 0xa9, 0x0d, 0x2a, 0xc1, 0xd8, 0x19, 0x53, 0x4a, 0x14, 0xaa, 0xe9, 0x87, 0x49, 0xfc, 0xfa, 0x26, 0x7d, 0x5e, 0xc5, 0xaa,
            0xc8, 0x7e,
        ]);
        const buffer2 = Buffer.from([
            0x32, 0x5b, 0xb1, 0xa9, 0x7d, 0x31, 0x2a, 0x15, 0xb6, 0x58, 0x8d, 0x4a, 0x06, 0xab, 0x55, 0x93, 0x49, 0x9c, 0x45, 0x7b, 0x7d, 0x38, 0x39,
            0xa4, 0x98, 0x74, 0xf1, 0xd7, 0x26, 0x88, 0xfc, 0x6b, 0x2f, 0xf6, 0xe9, 0xc5, 0xde, 0x6b, 0x8f, 0xfb, 0xd8, 0xf9, 0x7e,
        ]);
        const buffer3 = Buffer.from([0xa2, 0x74, 0x58, 0x7e]);
        parser._transform(buffer0, "", () => {});
        parser._transform(buffer1, "", () => {});
        parser._transform(buffer2, "", () => {});
        parser._transform(buffer3, "", () => {});
        expect(parsed.length).toBe(4);
        expect(parsed[0].type).toBe(FrameType.RSTACK);
        expect(parsed[1].type).toBe(FrameType.DATA);
        expect(parsed[2].type).toBe(FrameType.DATA);
        expect(parsed[3].type).toBe(FrameType.NAK);
    });

    it("Message in two chunks", () => {
        const parsed = [];
        const parser = new Parser();
        parser.on("parsed", (result) => parsed.push(result));
        const buffer1 = Buffer.from([0x22, 0x5b, 0xb1, 0xa9, 0x0d, 0x2a, 0xc1, 0xd8, 0x19, 0x53, 0x4a, 0x14]);
        parser._transform(buffer1, "", () => {});
        expect(parsed.length).toBe(0);
        const buffer2 = Buffer.from([0xaa, 0xe9, 0x87, 0x49, 0xfc, 0xfa, 0x26, 0x7d, 0x5e, 0xc5, 0xaa, 0xc8, 0x7e]);
        parser._transform(buffer2, "", () => {});
        expect(parsed.length).toBe(1);
        expect(parsed[0].type).toBe(FrameType.DATA);
    });

    it("Two messages in one chunk", () => {
        const parsed = [];
        const parser = new Parser();
        parser.on("parsed", (result) => parsed.push(result));
        const buffer1 = Buffer.from([
            0x22, 0x5b, 0xb1, 0xa9, 0x0d, 0x2a, 0xc1, 0xd8, 0x19, 0x53, 0x4a, 0x14, 0xaa, 0xe9, 0x87, 0x49, 0xfc, 0xfa, 0x26, 0x7d, 0x5e, 0xc5, 0xaa,
            0xc8, 0x7e, 0x32, 0x5b, 0xb1, 0xa9, 0x7d, 0x31, 0x2a, 0x15, 0xb6, 0x58, 0x8d, 0x4a, 0x06, 0xab, 0x55, 0x93, 0x49, 0x9c, 0x45, 0x7b, 0x7d,
            0x38, 0x39, 0xa4, 0x98, 0x74, 0xf1, 0xd7, 0x26, 0x88, 0xfc, 0x6b, 0x2f, 0xf6, 0xe9, 0xc5, 0xde, 0x6b, 0x8f, 0xfb, 0xd8, 0xf9, 0x7e,
        ]);
        parser._transform(buffer1, "", () => {});
        expect(parsed.length).toBe(2);
        expect(parsed[0].type).toBe(FrameType.DATA);
        expect(parsed[1].type).toBe(FrameType.DATA);
    });
});
