const fs = require('fs');
const fse = require('fs-extra');
const path = require('path');
const { execSync,spawnSync,spawn } = require('child_process');
const AdmZip = require('adm-zip');
const formData = require('form-data');
const moment = require('moment');
const envfile = require('envfile');
const log = require('loglevel');

const escpos = require('escpos');
escpos.USB = require('escpos-usb');
escpos.Serial = require('escpos-serialport');
escpos.Screen = require('escpos-screen');
const usbDetect = require('usb-detection');
usbDetect.startMonitoring();
const { NFC } = require('nfc-pcsc');

const constants = require('../models/constants');
const constantsXml = require('../models/constants-xml');

const HelpersCtrl = require('./helpers');
const Pos = require('../models/pos');
const PosCtrl = require('./pos');
const SmartCardServ = require('../services/smart-card');
const KmsProxyServ = require('../services/kms-proxy');
const PaymentGatewayServ = require('../services/payment-gateway');
const InvoiceServ = require('../services/invoice');
const FinancialTransactionServ = require('../services/financial-transaction');

class PeripheralsCtrl {
    constructor () {
        this.axios = global.axiosInstances.axiosPosInstance;

        this.helpersCtrl = new HelpersCtrl();
        this.posModel = Pos;
        this.posCtrl = new PosCtrl();
        this.smartCardServ = new SmartCardServ();
        this.kmsProxyServ = new KmsProxyServ();
        this.paymentGatewayServ = new PaymentGatewayServ();
        this.invoiceServ = new InvoiceServ();
        this.financialTransactionServ = new FinancialTransactionServ();
        this.dateTimeFormat = 'DD/MM/YY HH:mm';
        this.partialPrint = false;
        this.savePrintData = false;
        this.removeExpiredContracts = (['kuwait'].includes(process.env.CLIENT_NAME)) ? true : false;

        this.init();
        this.peripherals();
        this.cardReader();
        this.installFont();
    }

    // GET PERIPHERALS CONFIG
    init = () => {
        const config = path.join(process.cwd(), 'configs', 'peripherals.json');
        if (!fs.existsSync(config)) {
            log.error("Peripherals config file not found run setup script -> 'npm run setup'");
            process.exit();
        }
        const peripherals = JSON.parse(fs.readFileSync(config));
        global.peripherals = {};
        global.currentPosUtcOffset = this.helpersCtrl.getHHmm(moment().utcOffset());

        // Devices that are not defined in config are not in use
        Object.values(peripherals).filter((f) => process.env.CLIENT_DEVICES.split(',').includes(f.device)).map((device) => {
            global.peripherals[device.device] = device;
        });
    }

    // START STATUS OF PERIPHERALS
    peripherals = () => {
        for (const key of Object.keys(global.peripherals)) {
            if (global.peripherals[key] && global.peripherals[key].using) {
                usbDetect.find(global.peripherals[key].ids[0], global.peripherals[key].ids[1], (err, device) => {
                    global.peripherals[key].changetimestamp = moment.utc().utcOffset(global.currentPosUtcOffset).format(this.dateTimeFormat);
                    global.peripherals[key].status = (device.length > 0 || ['COM6', 'VGA2662'].includes(global.peripherals[key].devicename)) ? 'connected' : 'disconnected';
                    if (key == 'edcterminal') {
                        let edcterminal = (device.length > 0) ? device.filter((f) => f.deviceName.includes('COM')) : [];
                        let edcterminalPath = (edcterminal.length > 0) ? (edcterminal[0].deviceName).match(/COM\d+/g)[0] : global.peripherals[key].devicename;
                        global.peripherals[key].devicename = edcterminalPath;
                    }
                });
                usbDetect.on('change:'+global.peripherals[key].ids[0]+':'+global.peripherals[key].ids[1], (device) => {
                    global.peripherals[key].changetimestamp = moment.utc().utcOffset(global.currentPosUtcOffset).format(this.dateTimeFormat);
                    global.peripherals[key].status = (global.peripherals[key].status == 'connected') ? 'disconnected' : 'connected';
                    log.info(key, global.peripherals[key].status);
                    if (key == 'edcterminal') {
                        let edcterminal = (device.length > 0) ? device.filter((f) => f.deviceName.includes('COM')) : [];
                        let edcterminalPath = (edcterminal.length > 0) ? (edcterminal[0].deviceName).match(/COM\d+/g)[0] : global.peripherals[key].devicename;
                        global.peripherals[key].devicename = edcterminalPath;
                    }
                    global.io.emit('peripherals connected', Object.values(global.peripherals));
                });
            }
        }

        setTimeout(() => {
            // Override peripherals statuses
            if (process.env.POS_DEVELOPMENT_MODE == 'true') {
                const currentTimestamp = moment.utc().utcOffset(global.currentPosUtcOffset).format(this.dateTimeFormat);
                process.env.CLIENT_DEVICES.split(',').map((device) => {
                    if (global.peripherals[device].name == 'None') {
                        global.peripherals[device].name = device;
                    }
                    if (global.peripherals[device].using == false) {
                        global.peripherals[device].ids = [];
                    }
                    global.peripherals[device].status = 'connected';
                    global.peripherals[device].changetimestamp = currentTimestamp;
                });
            }

            global.io.emit('peripherals connected', Object.values(global.peripherals));
        }, 500);

        // LIST ALL PERIPHERALS
        if (process.env.POS_PERIPHERALS_DEBUG == 'true') {
            // find all connected usb peripherals
            usbDetect.find((err, peripherals) => {
                log.info('peripherals found', peripherals);
            });
            // find changed device
            usbDetect.on('change', (device) => {
                log.info('peripheral changed', device.deviceName, [device.vendorId, device.productId]);
            });
        }
    }

    // CARD READER
    cardReader = () => {
        if (global.peripherals.cardreader && global.peripherals.cardreader.using) {
            let nfc = new NFC();
            nfc.on('reader', (reader) => {
            	reader.autoProcessing = false;
            	reader.on('card', async (card) => {
                    let msg = card.type;
                    if (!/\bSAM\b/i.test(reader.reader.name)) { // [Identiv uTrust 4711 F CL Reader 0, Identiv uTrust 4711 F SAM Reader 0]
                        log.info('Card', msg, 'connected');
                    }

                    if (process.env.CLIENT_SECURE_ACCESS_MODULE == 'demo') {
                        const sc = await this.smartCardServ.demoCardData();
                        global.io.emit('card connected', {msg: sc, uid: sc.cardUid});
                        return;
                    }

                    if (process.env.CLIENT_SECURE_ACCESS_MODULE == 'true') {
                        if (typeof process.env.SECURE_ACCESS_MODULE_KEY == 'undefined') {
                            const samSerial = (process.env.CLIENT_SAM_ID != '') ? process.env.CLIENT_SAM_ID : await this.smartCardServ.getSamSerial();
                            process.env.SECURE_ACCESS_MODULE_KEY = (process.env.CLIENT_SAM_KEY != '') ? process.env.CLIENT_SAM_KEY : await this.kmsProxyServ.authenticateSam(samSerial);
                        }

                        await this.smartCardServ.setSamAuthenticationKey(process.env.SECURE_ACCESS_MODULE_KEY);
                        await this.smartCardServ.securityElementTransactionCounter();
                    }

                    if (!/\bSAM\b/i.test(reader.reader.name)) {
                        const uid = await this.cardReaderTransmit(reader, card, new Buffer.from([0xFF, 0xCA, 0x00, 0x00, 0x00]), 12, true);                     // Get data
                        if (uid) {
                            if (process.env.CLIENT_SECURE_ACCESS_MODULE == 'true') {
                                let c = 0;
                                msg = await this.smartCardServ.getPiccCardData();
                                while (Object.keys(msg).length === 0 && c < 5) {
                                    await new Promise((r) => setTimeout(r, 500));
                                    msg = await this.smartCardServ.getPiccCardData(); c++;
                                }
                                if (this.removeExpiredContracts) {
                                    // msg = await this.smartCardServ.removeExpiredContracts(msg);
                                    for (let i=2; i<=5; i++) {
                                        if (((moment.utc(msg['contractExpirationTimestamp'+i]).diff(moment.utc(), 'second') < 0) && ([constantsXml.contractStatusType.pending, constantsXml.contractStatusType.active].includes(msg['contractStatus'+i]))) || ((msg['contractCategory'+i] == constantsXml.smartCardContractType.storedRides) && (msg['ContractValue'+i] <= 0))) {
                                            await this.axios.post('ticket/removeContractSmartCard', {smartCardContent: msg, contractSlot: i, reason: {id: null, name: null}});
                                        }
                                    }
                                    msg = await this.smartCardServ.getPiccCardData();
                                }
                            }
                            // log.info('card connected', {msg: msg, uid: uid});
                            global.io.emit('card connected', {msg: msg, uid: uid});
                            return;
                        }
                    }

                    if (process.env.CLIENT_SECURE_ACCESS_MODULE != 'true') {
                        const emvSelect = await this.cardReaderTransmit(reader, card, new Buffer.from([0x00, 0xA4, 0x09, 0x00, 0x02, 0x10, 0x00]), 40, false);  // Application selection
                        const emvInit = await this.cardReaderTransmit(reader, card, new Buffer.from([0x00, 0xA4, 0x09, 0x00, 0x02, 0x10, 0x03]), 40, false);    // Initiate application processing
                        const emvUid = await this.cardReaderTransmit(reader, card, new Buffer.from([0x00, 0xB0, 0x00, 0x00, 0x10]), 40, false);                 // Read application data | Get data
                        if (emvUid) {
                            // log.info('card connected', {msg: msg, uid: emvUid});
                            global.io.emit('card connected', {msg: msg, uid: emvUid});
                            return;
                        }
                    }
            	});

            	reader.on('card.off', async (card) => {
                    log.info('Card', card.type, 'disconnected');
                    if (process.env.CLIENT_SECURE_ACCESS_MODULE == 'true') {
                        await this.smartCardServ.disconnectPicc();
                    }
                    global.io.emit('card removed', card.type);
                });

                reader.on('error', (err) => {
                    log.error('Card reader error occurred', err);
                });

            	reader.on('end', () => {
                    log.info('Card reader device removed');
                });
            });

            nfc.on('error', (err) => {
                if (!(err.toString()).includes('The Smart Card Resource Manager is not running.')) {
                    log.error('Card reader error', err);
                }
            });
        }
    }

    cardReaderTransmit = async (reader, card, command, commandLength, returnsUid) => {
        try {
            let uid = null;
            const response = await reader.transmit(command, commandLength);
            if (response.length < 2) { log.info('Invalid response length, expected minimal length was 2 bytes.'); return null; }
            if (response.slice(-2).readUInt16BE(0) !== 0x9000) { log.info('Response not OK.'); return null; }
            if (returnsUid) {
                uid = response.slice(0, -2).toString('hex');
                let card_atr = card.atr.toString('hex').slice(0, 14);
            } else {
                let asciiUid = '';
                for (x = 0; x < response.length; x += 2) {
                    asciiUid += String.fromCharCode(parseInt(response.substr(x, 2), 16));
                }
                uid = parseInt(asciiUid).toString(16);
            }
            if (uid) {
                if (['kuwait', 'sharjah'].includes(process.env.CLIENT_NAME)) {
                    uid = uid.padStart(14, '0'); // uid = (card_atr.length >= 8) ? card_atr.replace(/^.{8}/, id) : id.padStart(14, '0');
                }
                return uid.toUpperCase();
            }
            return uid;
        } catch (err) {
            log.error('Error', err); return null;
        }
    }

    // GET STATUS OF PERIPHERALS
    status = async (req, res) => {
        try {
            log.info('status API');
            // show peripherals
            const filteredPeripherals = Object.values(global.peripherals);
            global.io.emit('peripherals connected', filteredPeripherals);
            res.status(200).json(filteredPeripherals);
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    // PRINT WITH THERMAL PRINTER
    print = async (req, res) => {
        try {
            log.info('print API');
            if (process.env.POS_DEVELOPMENT_MODE == 'true') {
                log.info('\n', req.body.text, req.body.text2, '\n');

                if (process.env.POS_PRINT_IMAGE == 'true') {
                    await this.printImage(path.join(process.cwd(), '../PosFrontend/POSFrontend/assets/client/'+process.env.CLIENT_NAME+'/receipt-logo.png'), req.body.text, req.body.qrcode, req.body.text2, req.body.qrcode2, req.body.text3);
                }
            }
            if (global.peripherals.receiptprinter && global.peripherals.receiptprinter.using && global.peripherals.receiptprinter.status == 'connected') {
                const logo_path = path.join(process.cwd(), '../PosFrontend/POSFrontend/assets/client/'+process.env.CLIENT_NAME+'/receipt-logo.png');
                if ((global.peripherals.receiptprinter.name).includes('Zebra')) {
                    // install zebra printing libraries ( npm install qz-tray@2.2.3 jsrsasign@11.1.0 pngjs@7.0.0 zpl-image@0.2.0 )
                    const image = await this.printImage(logo_path, req.body.text, req.body.qrcode, req.body.text2, req.body.qrcode2, req.body.text3);
                    const imageData = Buffer.from(image[0].split(',')[1], 'base64');
                    // paper dimensions 3.150 x 3149.606 (80mm x 80m)
                    await this.qzTray(global.peripherals.receiptprinter.devicename, {size: {width: 3.150}, units: 'in', density: '203', interpolation: 'nearest-neighbor'}, [{type: 'pixel', format: 'image', flavor: 'base64', data: imageData}]);
                } else {
                    const usbDevice = await escpos.USB.getDevice(global.peripherals.receiptprinter.ids[2], global.peripherals.receiptprinter.ids[3]);
                    const usbPrinter = await escpos.Printer.create(usbDevice);

                    let image_paths = [logo_path];
                    if (process.env.POS_PRINT_IMAGE == 'true') {
                        // install print libraries ( npm install canvas@3.1.2 qrcode@1.5.4 sharp@0.34.3 )
                        image_paths = (this.partialPrint) ? await this.printImageParts(logo_path, req.body.text, req.body.qrcode, req.body.text2, req.body.qrcode2, req.body.text3, 500) : await this.printImage(logo_path, req.body.text, req.body.qrcode, req.body.text2, req.body.qrcode2, req.body.text3);
                    }

                    await usbPrinter.hardware('init');
                    await usbPrinter.align('ct');

                    for (let i = 0; i < image_paths.length; i++) {
                        const image = await this.loadImage(escpos, image_paths[i]);
                        await usbPrinter.raster(image);
                    }

                    if (process.env.POS_PRINT_IMAGE != 'true') {
                        let encode = (['mok'].includes(process.env.CLIENT_NAME)) ? 'cp852' : 'cp1256';
                        let character = 0;
                        switch (global.peripherals.receiptprinter.devicename) {
                            case 'E-PoS':
                                character = 34;
                            break;
                            case 'WINTEC':
                                character = (['mok'].includes(process.env.CLIENT_NAME)) ? 18 : 33;
                            break;
                            case 'Epson':
                                character = 50;
                            break;
                        }
                        await usbPrinter.encode(encode);
                        await usbPrinter.setCharacterCodeTable(character);
                        await usbPrinter.text(req.body.text);
                        if (req.body.qrcode != '') {
                            await this.qrImage(usbPrinter, req.body.qrcode, 5);
                        }
                        if (req.body.text2 != '') {
                            await usbPrinter.text(req.body.text2);
                        }
                        if (req.body.qrcode2 != '') {
                            await this.qrImage(usbPrinter, req.body.qrcode2, 3);
                        }
                        if (req.body.text3 != '') {
                            await usbPrinter.text(req.body.text3);
                        }
                    }

                    await usbPrinter.cut();
                    await usbPrinter.close();
                }
            }
            res.status(200).json({"print": req.body});
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    installFont = () => {
        if (process.env.POS_PRINT_IMAGE == 'true') {
            if (!fs.existsSync('C:/Windows/Fonts/KawkabMono-Light.ttf') || !fs.existsSync('C:/Windows/Fonts/KawkabMono-Bold.ttf')) {
                fse.copySync(path.join('C:/PointOfSales/POSBackend/assets/fonts/kawkab/KawkabMono-Light.ttf'), path.join('C:/Windows/Fonts/KawkabMono-Light.ttf'));
                fse.copySync(path.join('C:/PointOfSales/POSBackend/assets/fonts/kawkab/KawkabMono-Bold.ttf'), path.join('C:/Windows/Fonts/KawkabMono-Bold.ttf'));
                execSync('REG ADD \"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts\" \/v \"Kawkab Mono Light (TrueType)\" \/t REG_SZ \/f \/d \"KawkabMono-Light.ttf\"');
                execSync('REG ADD \"HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts\" \/v \"Kawkab Mono Bold (TrueType)\" \/t REG_SZ \/f \/d \"KawkabMono-Bold.ttf\"');
            }
        }
    }

    printImage = async (logo, text, qr, text2, qr2, text3) => {
        const { registerFont, loadImage, createCanvas } = require('canvas');
        const QRCode = require('qrcode');

        const currency = global.currentPosData.currencyCode;
        const currencySymbol = (global.currentPosData.showCurrencySymbol) ? await this.loadSvgImage(path.join(process.cwd(), '../PosFrontend/POSFrontend/assets/icon/currency/'+currency+'.svg')) : null;

        //registerFont(path.join(process.cwd(), 'assets/fonts/kawkab/KawkabMono-Light.ttf'), { family: 'Kawkab Mono', weight: 'normal' });
        //registerFont(path.join(process.cwd(), 'assets/fonts/kawkab/KawkabMono-Bold.ttf'), { family: 'Kawkab Mono', weight: 'bold' });

        const options = {fontFamily: 'Kawkab Mono', fontSize: 17, lineHeight: 26, fontWidth: 17 * 0.9}; // {fontFamily: 'Arial', fontSize: 26, lineHeight: 34};
        const logoImage = (logo != '') ? await loadImage(logo) : {width: 0, height: 0};
        const qrcode = (qr != '') ? await QRCode.toDataURL(qr, {scale: 5}) : '';
        const qrImage = (qrcode != '') ? await loadImage(qrcode) : {width: 0, height: 0};
        const qrcode2 = (qr2 != '') ? await QRCode.toDataURL(qr2, {scale: 3}) : '';
        const qrImage2 = (qrcode2 != '') ? await loadImage(qrcode2) : {width: 0, height: 0};
        const rowLength = 48;
        const doubleSize = 1.5;
        let scale = 1;
        let width = 600;
        let height = 0;
        let x = 0;
        let y = 0;
        let maxWidth = width - x - x;
        var paragraphs = [].concat(text.split("\n"), qrcode, text2.split("\n"), qrcode2, text3.split("\n"));
        var doubleParagraphs = paragraphs.filter((f) => f.includes('\u001b!\u0010'));
        var textLines = [];

        const canvas = createCanvas(width * scale, (logoImage.height + (options.lineHeight * paragraphs.length) + ((options.fontSize * doubleSize) * doubleParagraphs.length) + qrImage.height + qrImage2.height) * scale);
        const context = canvas.getContext('2d');
        context.scale(scale, scale);

        if (logo != '') {
            context.drawImage(logoImage, (width - x - logoImage.width) / 2, 5, logoImage.width, logoImage.height);
            y += logoImage.height + 15;
        }

        for (var p = 0; p < paragraphs.length; p++) {
            var fontStyle = (paragraphs[p].includes('\u001bE\u0001')) ? 'bold' : '';
            if (['sharjah'].includes(process.env.CLIENT_NAME)) {
                fontStyle = '';
            }
            var fontSize = (paragraphs[p].includes('\u001b!\u0010')) ? 'double' : '';
            fontStyle += (paragraphs[p].includes('\u001dB\u0001\u001b!0')) ? 'inverted' : '';
            var trimedText = paragraphs[p].replace(/\u001bE\u0000/g, '').replace(/\u001bE\u0001/g, '').replace(/\u001b!\u0010/g, '').replace(/\u001b!\u0000/g, '').replace(/\u001dB\u0001/g, '').replace(/\u001dB\u0000/g, '').replace(/\u001b!0/g, '').replace(/ /g, '\xA0');
            if (trimedText.length > rowLength && !trimedText.startsWith('data:image')) {
                textLines.push({style: fontStyle, size: fontSize, text: trimedText.slice(0, rowLength)});
                var spaces = Math.abs((rowLength - trimedText.slice(rowLength, trimedText.length).length) / 2);
                trimedText = '\xA0'.repeat(spaces) + trimedText.slice(rowLength, trimedText.length) + '\xA0'.repeat(spaces);
            }
            textLines.push({style: fontStyle, size: fontSize, text: trimedText});
        }

        for (var tl = 0; tl < textLines.length; tl++) {
            if (textLines[tl].text.startsWith('data:image')) {
                if (textLines[tl].text == qrcode) {
                    context.drawImage(qrImage, (width - x - qrImage.width) / 2, y - 20, qrImage.width, qrImage.height);
                    y += qrImage.height;
                } else if (textLines[tl].text == qrcode2) {
                    context.drawImage(qrImage2, (width - x - qrImage2.width) / 2, y - 20, qrImage2.width, qrImage2.height);
                    y += qrImage2.height;
                }
            }  else {
                if (textLines[tl].style == 'inverted') {
                    textLines[tl].style = '';
                    textLines[tl].size = 'double';

                    context.fillStyle = 'black';
                    context.fillRect(x, y - (options.lineHeight * doubleSize), width, (options.lineHeight * doubleSize) * 2);
                    context.fillStyle = 'white';
                    context.textBaseline = 'middle';
                }
                context.font = textLines[tl].style + ' ' + (textLines[tl].size ? options.fontSize * doubleSize : options.fontSize) + 'px' + ' "' + options.fontFamily + '"';
                if (textLines[tl].size) {
                    textLines[tl].text.replace(/\xA0/g, '');
                    context.textAlign = 'center';
                    x = canvas.width / 2;
                }
                if (textLines[tl].text.includes(currency) && currencySymbol) {
                    const text = textLines[tl].text.split(currency);
                    const currencyText = context.measureText(currency);
                    const currencyValue = text[(global.currentPosData.currencyPrefix) ? 1 : 0].replace(' ', '');
                    if (textLines[tl].size) {
                        if (global.currentPosData.currencyPrefix) {
                            context.drawImage(currencySymbol, x - currencyText.width - options.fontSize, y - options.fontSize - (doubleSize * 3), options.fontSize * doubleSize, options.fontWidth * doubleSize);
                        }
                        context.fillText(currencyValue, x - currencyText.width + options.fontSize + ((currencyValue.length * options.fontSize) / 2), y);
                        if (!global.currentPosData.currencyPrefix) {
                            context.drawImage(currencySymbol, x - currencyText.width - options.fontSize, y - options.fontSize - (doubleSize * 3), options.fontSize * doubleSize, options.fontWidth * doubleSize);
                        }
                    } else {
                        if (global.currentPosData.currencyPrefix) {
                            context.drawImage(currencySymbol, (canvas.width / 2) - currencyText.width - options.fontSize, y - options.fontWidth, options.fontSize, options.fontWidth);
                        }
                        context.fillText(currencyValue, (canvas.width / 2) - options.fontSize - options.fontSize, y);
                        if (!global.currentPosData.currencyPrefix) {
                            context.drawImage(currencySymbol, (canvas.width / 2) - currencyText.width - options.fontSize, y - options.fontWidth, options.fontSize, options.fontWidth);
                        }
                    }
                } else {
                    context.fillText(textLines[tl].text, x, y);
                }
                if (textLines[tl].size) {
                    context.fillStyle = 'black';
                    context.textBaseline = 'alphabetic';
                    context.textAlign = 'left';
                    x = 0;
                }
                y += (textLines[tl].size ? options.lineHeight * doubleSize : options.lineHeight);
            }
        }

        const image = canvas.toDataURL();
        const imageData = Buffer.from(image.split(',')[1], 'base64');

        const invoiceNumber = (logo != '') ? text.split("\n")[6].replace(/\u001bE\u0000/g, '').replace(/\u001bE\u0001/g, '').replace(/ /g, '') : text.split("\n")[15].replace(/\u001dB\u0001\u001b!0/g, '').replace(/\u001b!\u0000\u001dB\u0000/g, '').replace(/ /g, '');
        const fareItemIssuedNumber = text2.split("\n")[0].replace(/\u001bE\u0000/g, '').replace(/\u001bE\u0001/g, '').replace(/ /g, '');
        const fileName = (/^\d+$/.test(fareItemIssuedNumber)) ? fareItemIssuedNumber : invoiceNumber;

        if (global.currentPosData.invoiceUpload) {
            const zip = new AdmZip();
            zip.addFile(fileName+'.png', imageData);

            await this.uploadInvoice(zip.toBuffer(), fileName); // should we zip invoice + tickets ??
        }

        if (process.env.POS_DEVELOPMENT_MODE == 'true') {
            fs.writeFileSync(path.join(process.cwd(), 'assets', 'print_'+process.env.CLIENT_NAME+'_'+fileName+'.png'), imageData);

            if (this.savePrintData) {
                fs.writeFileSync(path.join(process.cwd(), 'test', 'assets', 'print_'+process.env.CLIENT_NAME+'_'+fileName+'.json'), JSON.stringify({text: text, qrcode: qr, text2: text2, qrcode2: qr2, text3: text3}, null, 4));
            }
        }

        return [image];
    }

    printImageParts = async (logo, text, qr, text2, qr2, text3, partHeight) => {
        const sharp = require('sharp');

        const image_path = await this.printImage(logo, text, qr, text2, qr2, text3);

        const image = await sharp(Buffer.from(image_path[0].split(',')[1], 'base64')).resize({width: 600, fit: 'contain'}).toBuffer();
        const metadata = await sharp(image).metadata();

        let imageParts = [];
        const numberOfParts = Math.floor(metadata.height / partHeight);

        for (let p = 0; p <= numberOfParts; p++) {
            let partT = partHeight * p;
            let partH = ((metadata.height - partT) > partHeight) ? partHeight : metadata.height - partT;
            let partI = await sharp(image).extract({left: 0, top: partT, width: metadata.width, height: partH}).png().toBuffer();

            if (process.env.POS_DEVELOPMENT_MODE == 'true') {
                fs.writeFileSync(path.join(process.cwd(), 'assets', 'print_'+process.env.CLIENT_NAME+'_part_'+p+'.png'), partI);
            }

            imageParts.push('data:image/png;base64,'+partI.toString('base64'));
        }

        return imageParts;
    }

    loadImage = async(escpos, image_path) => {
        return new Promise((resolve, reject) => {
            escpos.Image.load(image_path, (image) => {
                resolve(image);
            });
        });
    }

    loadSvgImage = async(image_path) => {
        const { createCanvas, Image } = require('canvas');

        return new Promise((resolve, reject) => {
            const canvas = createCanvas(0, 0);
            const ctx = canvas.getContext('2d');
            const img = new Image();
            img.onload = () => ctx.drawImage(img, 0, 0);
            img.onerror = err => { throw err }
            img.src = image_path;
            resolve(img);
        });
    }

    qrImage = async (printer, qrcode, size) => {
        return new Promise((resolve, reject) => {
            printer.qrimage(qrcode, { type: 'png', size: size }, (err) => {
                if (err) {
                    reject(err);
                }
                resolve(this);
            });
        });
    }

    uploadInvoice = async (file_buffer, uploadInvoice) => {
        let formContent = new formData();
        formContent.append('file', file_buffer, uploadInvoice+'.zip');

        const uploadType = (/^\d+$/.test(uploadInvoice)) ? 'invoiceUploadByFareItemIssuedNumber' : 'invoiceUploadByOrderId';
        const invoiceUpload = await this.invoiceServ[uploadType](uploadInvoice, formContent);

        if (invoiceUpload) {
            if (process.env.POS_DEVELOPMENT_MODE == 'true') {
                fs.writeFileSync(path.join(process.cwd(), 'assets', 'upload_'+uploadInvoice+'.zip'), file_buffer);
            }
            log.info('Uploaded', uploadType, 'upload_'+uploadInvoice+'.zip');
        }
    }

    downloadInvoice = async (downloadInvoice) => {
        const downloadType = (/^\d+$/.test(downloadInvoice)) ? 'invoiceDownloadByFareItemIssuedNumber' : 'invoiceDownloadByOrderId';
        const invoiceDownload = await this.invoiceServ[downloadType](downloadInvoice);

        if (invoiceDownload) {
            fs.writeFileSync(path.join(process.cwd(), 'assets', 'download_'+downloadInvoice+'.zip'), invoiceDownload);
            log.info('Downloaded', downloadType, 'download_'+downloadInvoice+'.zip');
        }
    }

    // GET STATUS OF THERMAL PRINTER PAPER
    paper = async (req, res) => {
        try {
            log.info('paperStatus API');
            var printer_status = '';
            if (global.peripherals.receiptprinter && global.peripherals.receiptprinter.using && global.peripherals.receiptprinter.status == 'connected') {
                const { SerialPort } = require('serialport');
                const serialDevice = new SerialPort({path: 'COM3', baudRate: 38400, autoOpen: false});

                serialDevice.open((err) => {
                    if (err) {
                        return log.error('Error opening port: ', err.message);
                    }
                    //serialDevice.write("\x10\x04\x01"); // PrinterStatus
                    //serialDevice.write("\x10\x04\x02"); // OfflineCauseStatus
                    //serialDevice.write("\x10\x04\x03"); // ErrorCauseStatus
                    serialDevice.write("\x10\x04\x04"); // RollPaperSensorStatus
                });

                serialDevice.on('readable', () => {
                    var status = serialDevice.read().readInt8(0);
                    switch (status) {
                        case 18:
                            printer_status = 'Paper adequate';//
                        break;
                        case 30:
                            printer_status = 'Paper near-end is detected by the paper roll near-end sensor';
                        break;
                        case 114:
                            printer_status = 'Paper roll end detected by paper roll sensor';//
                        break;
                        case 126:
                            printer_status = 'Both sensors detect that the printer is out of paper';
                        break;
                    };
                    serialDevice.close((err) => {
                        log.info(printer_status);
                    });
                });

                /*const serialDevice = new escpos.Serial('COM3');
                const serialPrinter = new escpos.Printer(serialDevice);

                serialDevice.open(() => {
                    serialPrinter
                        .getStatus('RollPaperSensorStatus', (status) => {
                            switch (status.byte) {
                                case 18:
                                    printer_status = 'Paper adequate';//
                                break;
                                case 30:
                                    printer_status = 'Paper near-end is detected by the paper roll near-end sensor';
                                break;
                                case 114:
                                    printer_status = 'Paper roll end detected by paper roll sensor';//
                                break;
                                case 126:
                                    printer_status = 'Both sensors detect that the printer is out of paper';
                                break;
                            };
                            log.info(printer_status);
                        })
                        .close();
                });*/
            }
            res.status(200).json({"paperStatus": printer_status});
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    // DOCUMENT SCANNER
    scan = async (req, res) => {
        try {
            log.info('scan API');
            if (global.peripherals.documentscanner && global.peripherals.documentscanner.using && global.peripherals.documentscanner.status == 'connected') {
                const startScan = spawn('cscript.exe', ['wia-scan.vbs', req.body.scanner.split('-')[1]], {cwd: path.join(process.cwd(), 'libs', 'document-scanner')});
                startScan.on('close', (code) => {
                    if (parseInt(code) !== 0) { console.log(`startScan exited with code ${code}`); }
                    let image = path.join(process.cwd(), 'libs', 'document-scanner', 'img.jpg');
                    if (fs.existsSync(image)) {
                        fs.readFile(image, (err, img) => {
                            if (err) { return log.error(err); }
                            fs.unlinkSync(image);
                            res.status(200).json({"scan": "data:image/jpeg;base64," + img.toString('base64')});
                        });
                    }
                });
            }
            if (process.env.POS_DEVELOPMENT_MODE == 'true') {
                let image = path.join(process.cwd(), '../PosFrontend/POSFrontend/assets/client/'+process.env.CLIENT_NAME+'/receipt-logo.png');
                if (fs.existsSync(image)) {
                    fs.readFile(image, (err, img) => {
                        if (err) { return log.error(err); }
                        res.status(200).json({"scan": "data:image/png;base64," + img.toString('base64')});
                    });
                }
            }
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    // SHOW ON CUSTOMER DISPLAY
    display = async (req, res) => {
        try {
            log.info('display API');
            if (global.peripherals.customerdisplay && global.peripherals.customerdisplay.using && global.peripherals.customerdisplay.status == 'connected') {
                const serialDevice = new escpos.Serial(global.peripherals.customerdisplay.devicename);
                const serialScreen = new escpos.Screen(serialDevice);

                serialDevice.open(() => {
                    serialScreen
                        .clear()
                        .text(req.body.text)
                        .close();
                });
            }
            res.status(200).json({"display": req.body.text});
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    // OPEN CASH DRAWER
    openCashdrawer = async (req, res) => {
        try {
            log.info('cashdrawer API');
            if (global.peripherals.receiptprinter && global.peripherals.receiptprinter.using && global.peripherals.receiptprinter.status == 'connected') {
                const usbDevice = await escpos.USB.getDevice(global.peripherals.receiptprinter.ids[2], global.peripherals.receiptprinter.ids[3]);
                const usbPrinter = await escpos.Printer.create(usbDevice);

                await usbPrinter.cashdraw(2);
                await usbPrinter.cashdraw(5);
                await usbPrinter.close();
            }
            res.status(200).json({"openCashdrawer": "successful"});
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    // CARD PRINTER
    loadCardTemplate = async (req, res) => {
        try {
            log.info('loadCardTemplate API');
            let template = (!fs.existsSync(path.join(process.cwd(), 'assets', 'templates', 'template_'+process.env.CLIENT_NAME+'.json'))) ? path.join(process.cwd(), 'assets', 'templates', 'template_default.json') : path.join(process.cwd(), 'assets', 'templates', 'template_'+process.env.CLIENT_NAME+'.json');
            fs.readFile(template, (err, template) => {
                if (err) { return log.error(err); }
                res.status(200).json(JSON.parse(template));
            });
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    saveCardTemplate = async (req, res) => {
        try {
            log.info('saveCardTemplate API');
            fs.writeFile(path.join(process.cwd(), 'assets', 'templates', 'template_'+process.env.CLIENT_NAME+'.json'), req.body.template, (err) => {
                if (err) { return log.error(err); }
                res.status(200).json({"saveCardTemplate": "successful"});
            });
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    previewCard = async (req, res) => {
        try {
            log.info('previewcard API');
            if (req.body.value) {
                this.axios.post('user/search', req.body).then((user) => {
                    return this.axios.get('user/get?id='+user.data[0].id);
                })
                .then((account) => {
                    this.createCard(res, 'preview', account);
                })
                .catch((e) => {
                    this.createCard(res, 'preview', '');
                });
            } else {

                this.createCard(res, 'preview', '');
            }
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    printCard = async (req, res) => {
        try {
            log.info('printcard API');
            if (req.body.hasOwnProperty('value')) {
                this.axios.get('user/get?id='+req.body.value).then((account) => {
                    this.createCard(res, 'print', account);
                })
                .catch((e) => {
                    this.createCard(res, 'print', '');
                });
            } else {
                var account = {
                    data: {
                        id: req.body.smartCardData.idnum,
                        image: req.body.smartCardData.photo,
                        name: req.body.smartCardData.name,
                        shortId: req.body.smartCardData.idnum
                    }
                };
                if (process.env.POS_DEVELOPMENT_MODE == 'true') {
                    this.createCard(res, '', account);
                } else {
                    this.createCard(res, 'print', account);
                }
            }
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    createCard = async (response, type, account) => {
        // install libraries ( npm install fabric@5.3.0 )
        const sharp = require('sharp');
        const fabric = require('fabric').fabric;
        let canvas = new fabric.StaticCanvas(null, {width: 1014, height: 639});
        let template = (!fs.existsSync(path.join(process.cwd(), 'assets', 'templates', 'template_'+process.env.CLIENT_NAME+'.json'))) ? path.join(process.cwd(), 'assets', 'templates', 'template_default.json') : path.join(process.cwd(), 'assets', 'templates', 'template_'+process.env.CLIENT_NAME+'.json');
        let templateData = JSON.parse(fs.readFileSync(template));
        let placeholders = ['{Name}', '{IDnum}'];
        let imagesize = [200, 237];
        let imageradius = [15, 15];
        let imagefit = 'cover';
        let imagemask = Buffer.from('<svg><rect x="0" y="0" width="'+imagesize[0]+'" height="'+imagesize[1]+'" rx="'+imageradius[0]+'" ry="'+imageradius[1]+'"/></svg>');
        let accountData = (account) ? { id: account.data.id, shortId: account.data.shortId, name: account.data.name, image: Buffer.from(account.data.image, 'base64') } : { id: 123456789, shortId: 123456789, name: 'Full Name', image: path.join(process.cwd(), 'assets', 'templates', 'user.png') };

        await sharp(accountData.image).resize(imagesize[0], imagesize[1], {fit: imagefit/*, background: {r: 255, g: 255, b: 255, alpha: 1}*/}).composite([{input: imagemask, blend: 'dest-in', gravity: 'center'}]).toBuffer().then(async (accountImage) => {
            let placedata = [accountData.name, accountData.shortId+''];
            for (const [key, value] of Object.entries(templateData.objects)) {
                if (value.type == 'ErasableText' && placeholders.includes(value.text)) {
                    value.text = placedata[placeholders.indexOf(value.text)];
                    let textFill = value.styles[0][0];
                    for (let i=0; i<(value.text).length; i++) {
                        value.styles[0][i] = textFill;
                    }
                }
                if (value.type == 'ErasableImage' && key == 0) {
                    value.src = 'data:image/png;base64,' + accountImage.toString('base64');
                }
            }
            for (const [key, value] of Object.entries(templateData.objects)) {
                if (value.type == 'ErasableImage') { value.type = 'Image'; }
                if (value.type == 'PRect') { value.type = 'Rect'; }
                if (value.type == 'ErasableText') { value.type = 'Text'; }
            }
            await canvas.loadFromJSON(templateData, async () => {
                canvas.renderAll();
                let stream = canvas.createPNGStream();
                let image = '';
                stream.on('readable', () => {
                    image += stream.read().toString('base64');
                });
                stream.on('end', async () => {
                    if (log.getLevel() <= log.levels.DEBUG) {
                        log.info('Debug enabled, save '+type+' file.');
                        fs.writeFile(path.join(process.cwd(), 'assets', 'templates', type+'card.png'), image, {encoding: 'base64'}, (err) => {
                            if (err) { log.error('err', err); }
                        });
                    }
                    if (type == 'preview') {
                        fs.writeFile(path.join(process.cwd(), 'assets', 'templates', type+'card'+accountData.id+'.png'), image, {encoding: 'base64'}, (err) => {
                            if (err) { log.error('err', err); }
                        });
                    }
                    if (type == 'print') {
                        // card dimensions 3.370 x 2.125 ( 85.60mm x 53.98mm ) -> double size used 6.740 x 4.250 ( 171.20mm x 107.96mm )
                        await this.qzTray(global.peripherals.cardprinter.devicename, {size: {width: 6.740, height: 4.250}, units: 'in', density: '300', interpolation: 'nearest-neighbor'}, [{type: 'pixel', format: 'image', flavor: 'base64', data: image}]);
                    }
                    response.status(200).json({[type+"Card"]: image, [type+"Id"]: accountData.id});
                });
            });
        });
    }

    qzTray = async (printer, options, data) => {
        // install qz libraries ( npm install qz-tray@2.2.3 jsrsasign@11.1.0 )
        const credentials = (!fs.existsSync(path.join(process.cwd(), 'libs', 'qz-tray', 'qz-tray-credentials.json'))) ? (log.error("Credentials file not found run setup script -> 'npm run setup'"), res.json({"printCard": "failed"}), process.exit()) : JSON.parse(fs.readFileSync(path.join(process.cwd(), 'libs', 'qz-tray', 'qz-tray-credentials.json')));
        const qz = require('qz-tray');
        const rs = require('jsrsasign');
        qz.api.setPromiseType(function promise (resolver) { return new Promise(resolver); });
        qz.api.setWebSocketType(require('ws'));
        qz.security.setCertificatePromise((resolve, reject) => { resolve(credentials.certificate); });
        qz.security.setSignatureAlgorithm('SHA512');
        qz.security.setSignaturePromise((toSign) => { return (resolve, reject) => { try { let sig = new rs.KJUR.crypto.Signature({"alg": "SHA512withRSA"}); sig.init(rs.KEYUTIL.getKey(credentials.privatekey)); sig.updateString(toSign); resolve(rs.stob64(rs.hextorstr(sig.sign()))); } catch (err) { log.error(err); reject(err); } }; });
        qz.websocket.connect().then(() => {
            return qz.print(qz.configs.create(printer, options), data);
        })
        .then(() => {
            qz.websocket.disconnect();
        })
        .catch((e) => {
            log.error(e);
        });
    }

    // EDC TERMINAL
    edcTerminal = async (req, res) => {
        try {
            log.info('edcTerminal API');
            var edc_terminal = '';
            var edc_terminal_timeout = 100000;
            if (global.peripherals.edcterminal && global.peripherals.edcterminal.using && global.peripherals.edcterminal.status == 'connected') {
                const { SerialPort } = require('serialport');
                const serialDevice = new SerialPort({path: global.peripherals.edcterminal.devicename, baudRate: 9600, dataBits: 8, stopBits: 1, parity: 'none', autoOpen: false});

                const timeout = setTimeout(() => {
                    log.info('edcTerminal API timeout');
                    edc_terminal = {response_code: 'EBT', detailed_exception: 'TIMEOUT'};
                    serialDevice.close();
                    res.status(200).json({"edcTerminal": edc_terminal});
                }, edc_terminal_timeout);

                serialDevice.open(async (err) => {
                    if (err) {
                        return log.error('Error opening port: ', err.message);
                    }

                    let enquiry = await this.paymentGatewayServ.writeCommand(serialDevice, new Buffer.from(constants.edcTerminalCommands.ENQ, 'hex'));
                    if (enquiry == constants.edcTerminalCommands.ACK) {
                        var tlvMessage = await this.paymentGatewayServ.tagLengthValueData('tlv', req.body);
                        var tlvCommand = await this.paymentGatewayServ.parseTagLengthValueCommand(tlvMessage);
                        var tlvCommandBuffer = await this.paymentGatewayServ.hexStringToArrayBuffer(tlvCommand);

                        let saleRequest = await this.paymentGatewayServ.writeCommand(serialDevice, tlvCommandBuffer);
                        if (saleRequest == constants.edcTerminalCommands.ACK) {

                            let endOfTransmission = await this.paymentGatewayServ.writeCommand(serialDevice, new Buffer.from(constants.edcTerminalCommands.EOT, 'hex'));
                            if (endOfTransmission == constants.edcTerminalCommands.ENQ) {

                                let saleResponseMsg = await this.paymentGatewayServ.writeCommand(serialDevice, new Buffer.from(constants.edcTerminalCommands.ACK, 'hex'));
                                if (saleResponseMsg) {

                                    let saleResponse = await this.paymentGatewayServ.writeCommand(serialDevice, new Buffer.from(constants.edcTerminalCommands.ACK, 'hex'));
                                    if (saleResponse == constants.edcTerminalCommands.EOT) {
                                        var tlvListBuffer = await this.paymentGatewayServ.hexStringToTlv(saleResponseMsg);
                                        var tlvList = await this.paymentGatewayServ.parseTagLengthValueList(tlvListBuffer);
                                        var tlvJson = await this.paymentGatewayServ.tagLengthValueData('json', tlvList);

                                        log.info('edcTerminal API response', saleResponseMsg);
                                        edc_terminal = tlvJson;
                                        serialDevice.close();
                                        clearTimeout(timeout);
                                        res.status(200).json({"edcTerminal": edc_terminal});
                                    }
                                }
                            }
                        }
                    }
                });

                serialDevice.on('error', (err) => {
                    log.info('serialDevice error: ', err);
                });
            } else {
                res.status(200).json({"edcTerminal": edc_terminal});
            }
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    // LUGGAGE PRINTER
    printLuggage = async (req, res) => {
        try {
            log.info('printluggage API');
            if (process.env.POS_DEVELOPMENT_MODE == 'true') {
                log.info('\n', req.body.text, '\n');

                if (process.env.POS_PRINT_IMAGE == 'true') {
                    await this.printImage(path.join(process.cwd(), '../PosFrontend/POSFrontend/assets/client/'+process.env.CLIENT_NAME+'/receipt-logo.png'), req.body.text, req.body.qrcode, '', '', '');
                }
            }
            if (global.peripherals.luggageprinter && global.peripherals.luggageprinter.using && global.peripherals.luggageprinter.status == 'connected') {
                const logo_path = path.join(process.cwd(), '../PosFrontend/POSFrontend/assets/client/'+process.env.CLIENT_NAME+'/receipt-logo.png');
                if ((global.peripherals.luggageprinter.name).includes('Zebra')) {
                    // install zebra printing libraries ( npm install qz-tray@2.2.3 jsrsasign@11.1.0 pngjs@7.0.0 zpl-image@0.2.0 )
                    const image = (this.partialPrint) ? await this.printImageParts(logo_path, req.body.text, req.body.qrcode, '', '', '', 500) : await this.printImage(logo_path, req.body.text, req.body.qrcode, '', '', '');
                    const imageData = Buffer.from(image[0].split(',')[1], 'base64');
                    // label dimensions 3.937 x 5.906 (100mm x 150mm) -> padding top 0.295, padding left 0.591 (7.5mm, 15mm)
                    await this.qzTray(global.peripherals.luggageprinter.devicename, {size: {width: 3.937, height: 5.906}, units: 'in', density: '203', interpolation: 'nearest-neighbor'}, [{type: 'pixel', format: 'image', flavor: 'base64', data: imageData}]);
                } else {
                    const usbDevice = await escpos.USB.getDevice(global.peripherals.luggageprinter.ids[2], global.peripherals.luggageprinter.ids[3]);
                    const usbPrinter = await escpos.Printer.create(usbDevice);

                    let image_paths = [logo_path];
                    if (process.env.POS_PRINT_IMAGE == 'true') {
                        // install print libraries ( npm install canvas@3.1.2 qrcode@1.5.4 sharp@0.34.3 )
                        image_paths = (this.partialPrint) ? await this.printImageParts(logo_path, req.body.text, req.body.qrcode, '', '', '', 500) : await this.printImage(logo_path, req.body.text, req.body.qrcode, '', '', '');
                    }

                    await usbPrinter.hardware('init');
                    await usbPrinter.align('ct');

                    for (let i = 0; i < image_paths.length; i++) {
                        const image = await this.loadImage(escpos, image_paths[i]);
                        await usbPrinter.raster(image);
                    }

                    if (process.env.POS_PRINT_IMAGE != 'true') {
                        let encode = (['mok'].includes(process.env.CLIENT_NAME)) ? 'cp852' : 'cp1256';
                        let character = 0;
                        switch (global.peripherals.luggageprinter.devicename) {
                            case 'E-PoS':
                                character = 34;
                            break;
                            case 'WINTEC':
                                character = (['mok'].includes(process.env.CLIENT_NAME)) ? 18 : 33;
                            break;
                            case 'Epson':
                                character = 50;
                            break;
                        }
                        await usbPrinter.encode(encode);
                        await usbPrinter.setCharacterCodeTable(character);
                        await usbPrinter.text(req.body.text);
                        if (req.body.qrcode != '') {
                            await this.qrImage(usbPrinter, req.body.qrcode, 5);
                        }
                    }

                    await usbPrinter.cut();
                    await usbPrinter.close();
                }
            }
            res.status(200).json({"print": req.body});
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    // CHANGE STATUS OF DEVICE
    block = async (req, res) => {
        try {
            log.info('block API');
            global.io.emit('notify frontend', req.body.status);
            res.status(200).json({"deviceStatus": req.body.status});
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }

    // SCRIPTS
    scripts = async (req, res) => {
        log.info('scripts API');
        const scripts = req.body;
        try {
            if (scripts.serialPortModuleFix == 'true') {
                let serialPortModuleFix = this.helpersCtrl.serialPortModuleFix();
                log.info('serialPortModuleFix', serialPortModuleFix);
            }

            if (scripts.softwarePkgSwitch == 'true') {
                let softwarePkgFolder = fs.readdirSync(path.join(process.cwd(), process.env.FOLDER_DISTRIBUTION)).filter((folder) => folder.startsWith('pos_software_pkg_'));
                let softwarePkgVersion = (softwarePkgFolder.length) ? softwarePkgFolder[0].split('pos_software_pkg_')[1] : '';
                let softwarePkgSwitch = false;

                if (softwarePkgFolder.length) {
                    softwarePkgSwitch = {pkg: softwarePkgVersion, swv: scripts.softwareVersion};
                    log.info('softwarePkgSwitch', softwarePkgSwitch);
                    await this.posCtrl.softwarePkgSwitch(softwarePkgVersion);
                } else {
                    log.info('softwarePkgSwitch', softwarePkgSwitch);
                }
            }

            if (scripts.addToDatabase == 'true') {
                const pos = await this.posModel.findOne({}).lean();
                let posData = {};
                let posDataRemove = {};
                let addToDatabaseData = false;

                if (pos.securityElementId == undefined) { // transactionCounter
                    posData.securityElementId = '';
                }
                if (pos.securityElementTransactionCounter == undefined) {
                    posData.securityElementTransactionCounter = 0;
                }
                if (pos.invoiceCounter == undefined) {
                    posData.invoiceCounter = 0;
                }
                if (pos.invoiceDate == undefined) {
                    posData.invoiceDate = '';
                }
            // device
                let deviceVersion = (['oman'].includes(process.env.CLIENT_NAME)) ? 'E-PoS XtremeII' : 'Wintec AnyPOS600';
                if (pos.deviceVersion != deviceVersion) {
                    posData.deviceVersion = deviceVersion;
                }
            // software
                if (pos.appVersion != scripts.softwareVersion) {
                    posData.appVersion = scripts.softwareVersion;
                }
            // modules
                if (pos.hasPeriodPass != undefined) { // hasTicketLuggage
                    posDataRemove.hasPeriodPass = '';
                }
                if (pos.hasCardHolders == undefined) { // hasAccounts
                    posData.hasCardHolders = false;
                }
                if (pos.hasChangeRiderType == undefined) { // hasSmartCards
                    posData.hasChangeRiderType = false;
                }
                if (pos.hasSmartCardRecreate == undefined) {
                    posData.hasSmartCardRecreate = false;
                }
                if (pos.hasSalesOrder) { // hasReservation
                    posData.hasSalesOrder = false;
                }
            // account modules
                if (pos.hasAccountPeriodPass != undefined) { // hasAccountTicketLuggage
                    posDataRemove.hasAccountPeriodPass = '';
                }
            // settings
                if (pos.cashFloat == undefined) { // hasAccountSmartcard
                    posData.cashFloat = true;
                }
                if (pos.invoiceUpload == undefined) { // website
                    posData.invoiceUpload = false;
                }
                if (pos.hasDiscount == undefined) {
                    posData.hasDiscount = false;
                }
                if (pos.bilingualPrint == undefined) { // displayName
                    posData.bilingualPrint = true;
                }
            // currency
                if (pos.currencyPrefix == undefined) { // riderTypeIcon
                    posData.currencyPrefix = false;
                }
                if (pos.showCurrencySymbol == undefined) { // currencyDecimal
                    posData.showCurrencySymbol = false;
                }

                if (Object.keys(posData).length !== 0) {
                    await this.posModel.findOneAndUpdate({ deviceId: global.currentPosData.deviceId }, posData);
                    addToDatabaseData = {old: Object.fromEntries(Object.entries(pos).filter(([k, v]) => Object.keys(posData).includes(k))), new: posData};
                }
                if (Object.keys(posDataRemove).length !== 0) {
                    await this.posModel.findOneAndUpdate({ deviceId: global.currentPosData.deviceId }, {$unset: {posDataRemove}});
                }
                log.info('addToDatabase', addToDatabaseData);
            }

            if (scripts.removeFromDatabase == 'true') {
                let removeFromDatabase = await this.posCtrl.distributionRemove();
                log.info('removeFromDatabase', removeFromDatabase);
            }

            if (scripts.upgradeDatabase == 'true') {
                let upgradeDatabase = true;
                // create db backup ( mongodump --out=backup )
                execSync('mongodump --out=backup', {cwd: path.join(process.cwd())});
                // stop mongodb service
                execSync('net stop MongoDB');
                // uninstall mongodb ( choco uninstall mongodb mongodb.install --yes )
                spawnSync('choco', ['uninstall', 'mongodb', 'mongodb.install', '--yes']);
                // remove old db data
                fs.rmSync(path.join('C:/ProgramData/MongoDB/data/db'), { recursive: true });
                // install mongodb ( choco install mongodb --version 8.2.3 --yes )
                spawnSync('choco', ['install', 'mongodb', '--version', '8.2.3', '--yes']);
                // restore db backup ( mongorestore --dir=backup )
                execSync('mongorestore --dir=backup', {cwd: path.join(process.cwd())});
                // remove backup data
                fs.rmSync(path.join(process.cwd(), 'backup'), { recursive: true });
                log.info('upgradeDatabase', upgradeDatabase);
            }

            if (scripts.addToPeripheralsFile == 'true') {
                let peripheralsFile = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'configs', 'peripherals.json')));
                let peripherals = false;

                if (!peripheralsFile.hasOwnProperty('edcterminal')) {
                    let edcTerminal = {device: "edcterminal", name: "None", devicename: "None", ids: [], using: false, changetimestamp: "", status: "disconnected"};
                    if (['hafil'].includes(process.env.CLIENT_NAME)) {
                        edcTerminal.name = "Newland N910 Pro";
                        edcTerminal.devicename = "COM15";
                        edcTerminal.ids = [7694, 36907, "0x1E0E", "0x902B"];
                        edcTerminal.using = true;
                    }

                    peripheralsFile.edcterminal = edcTerminal;
                    peripherals = true;
                }

                if (!peripheralsFile.hasOwnProperty('luggageprinter')) {
                    let luggagePrinter = {device: "luggageprinter", name: "None", devicename: "None", ids: [], using: false, changetimestamp: "", status: "disconnected"};
                    if (['hafil'].includes(process.env.CLIENT_NAME)) {
                        luggagePrinter.name = "WINTEC WIN91PT";
                        luggagePrinter.devicename = "WINTEC";
                        luggagePrinter.ids = [8137, 8214, "0x1FC9", "0x2016"];
                        luggagePrinter.using = true;
                    }

                    peripheralsFile.luggageprinter = luggagePrinter;
                    peripherals = true;
                }

                if (peripherals) {
                    fs.writeFileSync(path.join(process.cwd(), 'configs', 'peripherals.json'), JSON.stringify(peripheralsFile, null, 4));
                }
                log.info('addToPeripheralsFile', peripherals);
            }

            if (scripts.addToEnvFile == 'true') {
                let configFile = envfile.parse(fs.readFileSync(path.join(process.cwd(), 'configs', '.env')));
                let config = false;

                if (!configFile.hasOwnProperty('DOTENV_CONFIG_QUIET')) {
                    configFile.DOTENV_CONFIG_QUIET = true;
                    config = true;
                }
            // CLIENT
                if (configFile.hasOwnProperty('CLIENT_API_INTEGRATION_SERVICE')) {
                    delete configFile.CLIENT_API_INTEGRATION_SERVICE;
                    configFile.CLIENT_DEVICE_MNEMONIC = 'POS';
                    config = true;
                }
                if (!configFile.hasOwnProperty('CLIENT_RESERVATION_MODULE')) {
                    configFile.CLIENT_RESERVATION_MODULE = false;
                    config = true;
                }
                if (!configFile.hasOwnProperty('CLIENT_FISCAL_VERIFICATION')) {
                    configFile.CLIENT_FISCAL_VERIFICATION = false;
                    config = true;
                }
            // POS
                if (!configFile.hasOwnProperty('POS_PRINT_IMAGE')) {
                    configFile.POS_PRINT_IMAGE = false;
                    config = true;
                }
                if (!configFile.hasOwnProperty('POS_DATABASE_PURGE_INTERVAL')) {
                    configFile.POS_DATABASE_PURGE_INTERVAL = 0;
                    config = true;
                }

                if (config) {
                    fs.writeFileSync(path.join(process.cwd(), 'configs', '.env'), envfile.stringify(configFile));
                }
                log.info('addToEnvFile', config);
            }

            if (scripts.deleteData == 'true') {
                let deleteData = true;
                const folders = [
                    { path: path.join(process.cwd(), process.env.FOLDER_ACTION_LIST), action: 'recursive' },
                    { path: path.join(process.cwd(), process.env.FOLDER_DISTRIBUTION), action: 'recursive' },
                    { path: path.join(process.cwd(), process.env.FOLDER_DOCUMENTS), action: 'recursive' },
                    { path: path.join(process.cwd(), process.env.FOLDER_TRANSACTIONS_CORRUPTED), action: 'files' },
                    { path: path.join(process.cwd(), process.env.FOLDER_TRANSACTIONS_SENT), action: 'recursive' },
                    { path: path.join(process.cwd(), process.env.FOLDER_TRANSACTIONS_UNKNOWN), action: 'files' },
                    { path: path.join(process.cwd(), process.env.FOLDER_TRANSACTIONS_UNSENT), action: 'files' }
                ];
                const fileTypes = ['OPER', 'SUMM'];
                for (let i=0; i<folders.length; i++) {
                    switch (folders[i].action) {
                        case 'recursive':
                            fse.emptyDirSync(folders[i].path);
                        break;
                        case 'files':
                            const files = fs.readdirSync(folders[i].path);
                            for (let f=0; f<files.length; f++) {
                                if (fileTypes.some((type) => files[f].startsWith(type))) {
                                    fs.unlinkSync(path.join(folders[i].path, files[f]));
                                } else {
                                    fs.renameSync(path.join(folders[i].path, files[f]), path.join(process.cwd(), '../POSFiles', files[f]));
                                }
                            }
                        break;
                    }
                }
                log.info('deleteData', deleteData);
            }

            if (scripts.registerBusinessPremise == 'true') {
                const premiseData = {
                    timestamp: moment.utc().utcOffset(global.currentPosData.utcOffset),
                    businessPremiseId: global.currentPosData.locationExternalId.toString(),
                    validityDate: moment.utc().subtract(1, 'days').utcOffset(global.currentPosData.utcOffset)
                };
                log.info(premiseData);
                let registerBusinessPremise = await this.financialTransactionServ.fiscalVerificationPremise(premiseData);
                log.info('registerBusinessPremise', registerBusinessPremise);
            }

            res.status(200).json({"scripts": scripts});
        } catch (err) {
            log.error(err);
            res.status(400).json({ error: err.message });
        }
    }
}

module.exports = PeripheralsCtrl;
