import { NativeEventEmitter, NativeModules, Platform, type EmitterSubscription } from 'react-native'
import type { NPrinter } from './NPrinter'
import type { INPrinterControllerCallback } from './INPrinterControllerCallback'
import type { NPrintInfo } from './NPrintInfo'
import { NResultString } from './constants/NResultString'
import { NResult } from './constants/NResult'

const LINKING_ERROR =
    `The package 'react-native-nemonic-sdk' doesn't seem to be linked. Make sure: \n\n` +
    Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
    '- You rebuilt the app after installing the package\n' +
    '- You are not using Expo Go\n'

const NPrinterControllerModule = NativeModules.NPrinterControllerModule
    ? NativeModules.NPrinterControllerModule
    : new Proxy(
        {},
        {
            get() {
                throw new Error(LINKING_ERROR)
            },
        }
    )

export class NPrinterController {

    public static MAX_IMAGE_SIZE = 196603

    private static callback?: INPrinterControllerCallback

    private eventEmitter: NativeEventEmitter
    private eventDisconnectedListener?: EmitterSubscription
    private eventPrintProgressListener?: EmitterSubscription
    private eventPrintCompleteListener?: EmitterSubscription

    constructor() {
        this.eventEmitter = new NativeEventEmitter(NPrinterControllerModule)
    }

    public setCallback(callback?: INPrinterControllerCallback) {
        NPrinterController.callback = callback
    }

    public getBleConnectDelayOffsetForAndroid(): Promise<number> {
        return NPrinterControllerModule.getBleConnectDelayOffset()
    }

    public setBleConnectDelayOffsetForAndroid(msec: number) {
        const params = {
            'msec': msec
        }

        NPrinterControllerModule.setBleConnectDelayOffset(params)
    }

    public getBleSendRetryDelayOffsetForAndroid(): Promise<number> {
        return NPrinterControllerModule.getBleSendRetryDelayOffset()
    }

    public setBleSendRetryDelayOffsetForAndroid(msec: number) {
        const params = {
            'msec': msec
        }

        NPrinterControllerModule.setBleSendRetryDelayOffset(params)
    }

    public getDefaultConnectDelayForiOS(): Promise<number> {
        return NPrinterControllerModule.getDefaultConnectDelay()
    }

    public getConnectDelayForiOS(): Promise<number> {
        return NPrinterControllerModule.getConnectDelay()
    }

    public setConnectDelayForiOS(msec: number) {
        const params = {
            'msec': msec
        }

        NPrinterControllerModule.setConnectDelay(params)
    }

    public async connect(printer: NPrinter, queueLabel: string = ''): Promise<number> {
        this.registerEvent()

        const params = {
            'name': printer.getName(),
            'macAddress': printer.getMacAddress(),
            'type': printer.getType(),
            'queueLabel': queueLabel
        }
        var result = await NPrinterControllerModule.connect(params)

        if (result != 0) {
            this.unregisterEvent()
        }

        return result
    }

    public disconnect() {
        this.unregisterEvent()
        NPrinterControllerModule.disconnect()
    }

    public getConnectState(): Promise<number> {
        return NPrinterControllerModule.getConnectState()
    }

    public cancel() {
        NPrinterControllerModule.cancel()
    }

    public setPrintTimeout(enableAuto: boolean, manualTime: number) {
        const params = {
            enableAuto: enableAuto,
            manualTime: manualTime
        }

        NPrinterControllerModule.setPrintTimeout(params)
    }

    public print(printInfo: NPrintInfo): Promise<number> {
        let images = printInfo.getImages()

        for (const image of images) {
            if (image.length > NPrinterController.MAX_IMAGE_SIZE) {
                return new Promise(resolve => {
                    resolve(NResult.EXCEED_MAX_SIZE)
                })
            }
        }

        const params = {
            images: images,
            imageUrls: printInfo.getImageUrls(),
            quality: printInfo.getPrintQuality(),
            copies: printInfo.getCopies(),
            isLastPageCut: printInfo.isLastPageCutEnable(),
            enableDither: printInfo.isEnableDither(),
            isCheckPrinterStatus: printInfo.isCheckCartridgeTypeEnable(),
            isCheckCartridgeType: printInfo.isCheckCartridgeTypeEnable(),
            isCheckPower: printInfo.isCheckPowerEnable()
        }

        return NPrinterControllerModule.print(params)
    }

    public setTemplate(image: number[], withPrint: boolean, enableDither: boolean): Promise<number> {
        if (image.length > NPrinterController.MAX_IMAGE_SIZE) {
            return new Promise(resolve => {
                resolve(NResult.EXCEED_MAX_SIZE)
            })
        }

        const params = {
            image: image,
            withPrint: withPrint,
            enableDither: enableDither
        }

        return NPrinterControllerModule.setTemplate(params)
    }

    public setTemplateWithUint8Array(image: Uint8Array, withPrint: boolean, enableDither: boolean): Promise<number> {
        let data: number[] = []

        for (let i = 0; i < image.length; i++) {
            data[i] = image[i] as number
        }

        return this.setTemplate(data, withPrint, enableDither)
    }

    public setTemplateWithUrl(imageUrl: string, withPrint: boolean, enableDither: boolean): Promise<number> {
        const params = {
            imageUrl: imageUrl,
            withPrint: withPrint,
            enableDither: enableDither
        }

        return NPrinterControllerModule.setTemplateWithUrl(params)
    }

    public clearTemplate(): Promise<number> {
        return NPrinterControllerModule.clearTemplate()
    }

    public getPrinterStatus(): Promise<number> {
        return NPrinterControllerModule.getPrinterStatus()
    }

    public getCartridgeType(): Promise<number> {
        return NPrinterControllerModule.getCartridgeType()
    }

    public async getPrinterName(): Promise<NResultString> {
        let resultParams = await NPrinterControllerModule.getPrinterName()
        let result = this.getResultString(resultParams)
        return result
    }

    public getBatteryLevel(): Promise<number> {
        return NPrinterControllerModule.getBatteryLevel()
    }

    public getBatteryStatus(): Promise<number> {
        return NPrinterControllerModule.getBatteryStatus()
    }

    private disconnected() {
        NPrinterController.callback?.disconnected()
    }

    private printProgress(params: any) {
        var index = params.index as number
        var total = params.total as number
        var result = params.result as number
        NPrinterController.callback?.printProgress(index, total, result)
    }

    private printComplete(params: any) {
        var result = params.result as number
        NPrinterController.callback?.printComplete(result)
    }

    private registerEvent() {
        this.eventDisconnectedListener = this.eventEmitter.addListener('disconnected', this.disconnected)
        this.eventPrintProgressListener = this.eventEmitter.addListener('printProgress', this.printProgress)
        this.eventPrintCompleteListener = this.eventEmitter.addListener('printComplete', this.printComplete)
    }

    private unregisterEvent() {
        this.eventDisconnectedListener?.remove()
        this.eventPrintProgressListener?.remove()
        this.eventPrintCompleteListener?.remove()
    }

    private getResultString(params: any): NResultString {
        let result = params.result as number
        let value = params.value as string

        let resultString = new NResultString()
        resultString.setResult(result)
        resultString.setValue(value)

        return resultString
    }
}
