import { app } from 'electron'
import { writeFileSync, unlinkSync } from 'fs'
import { spawnSync } from 'child_process'
import Path from 'path'
import { dirSync } from 'tmp'
import UUID from 'uuid/v4'
import { 
    printDirect, 
    getPrinters, 
    getDefaultPrinterName, 
    getJob, 
    getSupportedPrintFormats
} from 'printer'
import { readSettings } from './settings'

const REQUEST_PRINTER_NAME = 'request_printer_name'
const SHOW_PRINT_STATUS = 'show_print_status'
const DOWNLOAD_DATA = 'download_data'

const printQueue = []
const settings = readSettings()

/**
 * 
 * @param {*} id 
 */
const removeRequestQueued = id => {
    let index = -1

    for (let i = 0; i < printQueue.length; i++) {
        if (printQueue[i].id === id) {
            index = i
            break
        }
    }

    printQueue.splice(index, 1) !== 0
}

/**
 * 
 * @param {*} id 
 */
const getRequestQueued = id => {
    let request = null

    for (let r of printQueue) {
        if (r.id === id) {
            return r;
        }
    }

    return request
}

/**
 * 
 * @param {*} mimeType 
 */
const getFormat = mimeType => {
    if (mimeType.indexOf('application/pdf') != -1 && process.platform != 'win32') {
        return 'PDF'
    }

    if (mimeType.indexOf('text/plain') != -1) {
        return 'RAW'
    }

    return 'RAW'
}

/**
 * 
 */
const redirectToSpawn = (buffer, printer) => {
    const fileId = UUID()
    const tmpDir = dirSync()
    const tmpPath = Path.join(tmpDir.name, `${fileId}.pdf`)

    writeFileSync(tmpPath, buffer)

    const execBin = Path.join(app.getAppPath(), 'bin', 'SumatraPDF.exe');

    spawnSync(execBin, [
        '-print-to',
        printer,
        '-silent',
        tmpPath
    ])

    unlinkSync(tmpPath)
    tmpDir.removeCallback()
}

/**
 * 
 * @param {*} socket 
 * @param {*} request 
 */
const doPrint = (socket, request) => {
    if (!request.data && request.id) {
        let requestQueued = getRequestQueued(request.id)
        
        if (!requestQueued) {
            socket.emit(SHOW_PRINT_STATUS, {
                status: 'failed'
            })
            return
        }

        if (!request.printer) {
            removeRequestQueued(request.id)
            socket.emit(SHOW_PRINT_STATUS, {
                status: 'canceled'
            })
            return
        }

        request.data = requestQueued.data
    }
    
    if (!request.printer) {
        request.printer = getDefaultPrinterName()
    }

    const mimeTypeRegex = request.data.match(/data:[a-zA-Z0-9]+\/[a-zA-Z0-9-.+]+.*,/)

    if (!mimeTypeRegex) {
        socket.emit(SHOW_PRINT_STATUS, {
            status: 'unknown'
        })
        return
    }

    const data = request.data.replace(mimeTypeRegex[0], '')

    if (request.printer == 'download') {
        socket.emit(DOWNLOAD_DATA, data)
        return
    }

    let format = getFormat(mimeTypeRegex[0])
    let supportedFormats = getSupportedPrintFormats(request.printer)

    if (settings.print.alwaysPdf) {
        format = 'PDF'
        supportedFormats.push('PDF')
    }

    if (!supportedFormats.includes(format)) {
        socket.emit(SHOW_PRINT_STATUS, {
            status: 'error',
            printer: request.printer,
            cause: {
                id: 'unsupported_format',
                error: null
            }
        })

        return;
    }

    let buffer = Buffer.from(data, 'base64')

    if (process.platform == 'win32') {
        redirectToSpawn(buffer, request.printer)
        
        socket.emit(SHOW_PRINT_STATUS, {
            status: 'printing',
            printer: request.printer
        })

        return
    }

    printDirect({
        data: buffer,
        printer: request.printer,
        docname: UUID(),
        type: format,
        options: {
            'fit-to-page': true
        },
        success: jobId => {
            const job = getJob(request.printer, jobId)
            
            if (job.status.includes('PRINTING')) {
                socket.emit(SHOW_PRINT_STATUS, {
                    status: 'printing',
                    printer: request.printer
                })
            }

            if (job.status.includes('PRINTED') || job.status.length == 0) {
                socket.emit(SHOW_PRINT_STATUS, {
                    status: 'printed',
                    printer: request.printer
                })
            }

        },
        error: e => {
            socket.emit(SHOW_PRINT_STATUS, {
                status: 'error',
                printer: request.printer,
                cause: {
                    id: 'print_error',
                    error: e
                }
            })
        }
    })
}

/**
 * 
 * @param {*} socket 
 * @param {*} request 
 */
const printProcessor = (socket, request) => {
    if (!request.print_directly) {
        request.id = UUID()
        printQueue.push(request)

        socket.emit(REQUEST_PRINTER_NAME, {
            id: request.id,
            printers: getPrinters()
        })
        return
    }

    doPrint(socket, request)
}

/**
 * 
 * @param {*} socket 
 */
export const printHandler = socket => {
    socket.on('request_print', request => printProcessor(socket, request))
}
