const fs = require('fs');
const path = require('path');
const moment = require('moment');
const envfile = require('envfile');
const log = require('loglevel');

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

const Pos = require('../models/pos');

class SmartCardServ {
    constructor () {
        // API doc - http://localhost:1001/apidoc/index.html
        this.axios = global.axiosInstances.axiosSclibInstance;
        this.posModel = Pos;

        this.dateTTimeFormat = 'YYYY-MM-DD[T]HH:mm:ss';
        this.dateTimeFormat = 'DD/MM/YYYY HH:mm';
    }

    getSamSerial = async () => {
        try {
            const res = await this.axios.get('getSamSerial');
            if (typeof res == 'undefined' || res.status >= 400) { return (process.env.CLIENT_SAM_ID != '') ? process.env.CLIENT_SAM_ID : null; }
            // update config file for fallback
            if (process.env.CLIENT_SAM_ID == '' || (process.env.CLIENT_SAM_ID != res.data && res.data.length == 14)) {
                let configFile = envfile.parse(fs.readFileSync(path.join(process.cwd(), 'configs', '.env')));
                if (configFile.CLIENT_SAM_ID == '') {
                    configFile.CLIENT_SAM_ID = res.data;
                    await fs.writeFileSync(path.join(process.cwd(), 'configs', '.env'), envfile.stringify(configFile));
                }
            }
            return res.data;
        } catch (err) {
            log.error(err);
        }
    }

    getSamKucLimit = async () => {
        try {
            const res = await this.axios.get('getSamKucLimit');
            if (typeof res == 'undefined' || res.status >= 400) { return null; }
            return res.data;
        } catch (err) {
            log.error(err);
        }
    }

    setNewSamKucLimit = async (limit) => {
        try {
            const res = await this.axios.get('setNewSamKucLimit/'+limit);
            if (typeof res == 'undefined' || res.status >= 400) { return false; }
            return true;
        } catch (err) {
            log.error(err);
        }
    }

    setSamAuthenticationKey = async (samAuthenticationKey) => {
        try {
            const res = await this.axios.get('setSamAuthenticationKey/'+samAuthenticationKey);
            if (typeof res == 'undefined' || res.status >= 400) { return false; }
            return true;
        } catch (err) {
            log.error(err);
        }
    }

    removeSamAuthenticationKey = async () => {
        try {
            const res = await this.axios.get('removeSamAuthenticationKey');
            if (typeof res == 'undefined' || res.status >= 400) { return false; }
            return true;
        } catch (err) {
            log.error(err);
        }
    }

    transformPiccCardData = async (smartCardData, isRead = false) => {
        let cardData = {};

        if (isRead) {
            const scData = Object.values(smartCardData);
            for (let i=0; i<scData.length; i++) {
                let code = scData[i]['code'].charAt(0).toLowerCase() + scData[i]['code'].slice(1);
                if (code.includes('cardId')) {
                    cardData[code] = scData[i]['currentValue'];
                } else if (code.includes('preferredLanguage')) {
                    cardData[code] = Buffer.from(scData[i]['currentValue'], 'hex').toString();
                } else if (code.includes('instanceId')) {
                    cardData[code] = scData[i]['currentValue'].toString('hex').toUpperCase();
                } else {
                    cardData[code] = parseInt(scData[i]['currentValue'].match(/../g).reverse().join(''), 16);
                }
            }
        } else {
            const scData = Object.keys(smartCardData);
            for (let i=0; i<scData.length; i++) {
                let code = scData[i].charAt(0).toLowerCase() + scData[i].slice(1);
                if (code.includes('cardId')) {
                    cardData[code] = smartCardData[scData[i]];
                } else if (code.includes('preferredLanguage')) {
                    cardData[code] = Buffer.from(smartCardData[scData[i]], 'hex').toString();
                } else if (code.includes('instanceId')) {
                    cardData[code] = smartCardData[scData[i]].toString('hex').toUpperCase();
                } else {
                    cardData[code] = parseInt(smartCardData[scData[i]].match(/../g).reverse().join(''), 16);
                }
            }
        }

        let data = {
        // initialize / issue
            cardUid: cardData.cardId,
            cardPrintedId: await this.getCardPrintedId(cardData.cardId),
            cardType: cardData.cardType,
            cardVersion: cardData.cardVersion,
            cardIteration: cardData.cardIteration,
            issuerId: cardData.issuerId,
            transactionCounter: cardData.transactionCounter,
            blockTimestamp: moment.unix(cardData.blockTimestamp).utc().format(this.dateTTimeFormat),
            blockStatus: constantsXml.smartCardBlockStatus[cardData.blockStatus],
            alic: cardData.alic,
            alwc: cardData.alwc,
            cardExpirationTimestamp: moment.unix(cardData.cardExpirationTimestamp).utc().format(this.dateTTimeFormat),
            currencyCode: constantsXml.currencyIsoCode[cardData.currencyCode],
            preferredLanguage: constantsXml.languageCode[cardData.preferredLanguage],
            refundableDeposit: cardData.refundableDeposit,
            riderTypeExternalId: cardData.riderTypeExternalId
        };

        // products contracts
        for (let i=1; i<=5; i++) {
            data['contractId'+i] = cardData['contractId'+i];
            data['instanceId'+i] = cardData['instanceId'+i];
            data['contractValue'+i] = (i == 1) ? cardData['storedValue'] : cardData['contractValue'+i]; // contract 1 = ePurse
            data['contractCategory'+i] = constantsXml.smartCardContractCategory[cardData['contractCategory'+i]];
            data['contractStatus'+i] = constantsXml.smartCardContractStatus[cardData['contractState'+i]];
            data['contractStartTimestamp'+i] = moment.unix(cardData['contractStartTimestamp'+i]).utc().format(this.dateTTimeFormat);
            data['contractExpirationTimestamp'+i] = moment.unix(cardData['contractExpirationTimestamp'+i]).utc().format(this.dateTTimeFormat);
            data['contractStateChangeTimestamp'+i] = moment.unix(cardData['contractStateChangeTimestamp'+i]).utc().format(this.dateTTimeFormat);
        }

        return data;
    }

    getPiccCardData = async () => {
        try {
            const res = await this.axios.get('getPiccCardData');
            if (typeof res == 'undefined' || res.status >= 400) { return {}; }
            return await this.transformPiccCardData(res.data, true);
        } catch (err) {
            log.error(err);
        }
    }

    getPiccCardDataCode = async (code) => {
        try {
            const res = await this.axios.get('getPiccCardData/'+code);
            if (typeof res == 'undefined' || res.status >= 400) { return {}; }
            return res.data;
        } catch (err) {
            log.error(err);
        }
    }

    writePiccCardData = async (code, data, type, items) => {
        try {
            let cdata;
            switch (type) {
                case 'BYTE':
                    const bb = Buffer.alloc(1 * items);
                    if (bb.length == 1) {
                        bb.writeUint8(data);
                    } else if (bb.length == 2) {
                        bb.writeUInt16LE(data);
                    }
                    cdata = bb.toString('hex').toUpperCase();
                break;
                case 'SHORT':
                    const bs = Buffer.alloc(2 * items);
                    bs.writeUInt16LE(data);
                    cdata = bs.toString('hex').toUpperCase();
                break;
                case 'INTEGER':
                    const bi = Buffer.alloc(4 * items);
                    bi.writeUInt32LE(data);
                    cdata = bi.toString('hex').toUpperCase();
                break;
                case 'HEX':
                    cdata = data;
                break;
            }

            let writeValues = {
                code: code,
                data: cdata
            };

            const res = await this.axios.post('writePiccCardData', writeValues);
            if (typeof res == 'undefined' || res.status >= 400) { return false; }

            return true;
        } catch (err) {
            log.error(err);
        }
    }

    commitPiccCardData = async () => {
        try {
            const res = await this.axios.get('commitPiccCardData');
            if (typeof res == 'undefined' || res.status >= 400) { return false; }
            return true;
        } catch (err) {
            log.error(err);
        }
    }

    disconnectPicc = async () => {
        try {
            const res = await this.axios.get('disconnectPicc');
            if (typeof res == 'undefined' || res.status >= 400) { return false; }
            return true;
        } catch (err) {
            log.error(err);
        }
    }

    initializePersonalizedCard = async (initialize) => {
        try {
            let initializeValues = {
                injectKeys: initialize.injectKeys,
                riderTypeExternalId: initialize.riderTypeExternalId,
                preferredLanguage: initialize.preferredLanguage,
                cardExpirationTimestamp: initialize.cardExpirationTimestamp,
                cardId: initialize.cardId,
                cardType: initialize.cardType,
                cardVersion: initialize.cardVersion,
                issuerId: initialize.issuerId,
                currencyCode: initialize.currencyCode
            };

            const res = await this.axios.post('initializePersonalizedCard', initializeValues);
            if (typeof res == 'undefined' || res.status >= 400) { return {}; }

            return await this.getPiccCardData();
        } catch (err) {
            log.error(err);
        }
    }

    issuePersonalizedCard = async (issue) => {
        try {
            let issueValues = {
                riderTypeExpirationTimestamp: issue.riderTypeExpirationTimestamp,
                riderTypeExternalId: issue.riderTypeExternalId,
                preferredLanguage: issue.preferredLanguage,
                refundableDeposit: issue.refundableDeposit,
                terminalId: issue.terminalId,
                merchantId: issue.merchantId,
                ptoReference: issue.ptoReference
            };

            const res = await this.axios.post('issuePersonalizedCard', issueValues);
            if (typeof res == 'undefined' || res.status >= 400) { return {}; }

            return await this.getPiccCardData();
        } catch (err) {
            log.error(err);
        }
    }

    loadEpurseContract = async (contract) => {
        try {
            let contractValues = {
                contractId: contract.contractId,
                instanceId: contract.instanceId,
                terminalId: contract.terminalId,
                merchantId: contract.merchantId,
                ptoReference: contract.ptoReference
            };

            const res = await this.axios.post('loadEpurseContract', contractValues);
            if (typeof res == 'undefined' || res.status >= 400) { return {}; }

            return await this.getPiccCardData();
        } catch (err) {
            log.error(err);
        }
    }

    initializeCard = async (initialize) => {
        try {
            let initializeValues = {
                cardExpirationTimestamp: initialize.cardExpirationTimestamp,
                riderTypeExternalId: initialize.riderTypeExternalId,
                preferredLanguage: initialize.preferredLanguage,
                cardId: initialize.cardId,
                cardType: initialize.cardType,
                cardVersion: initialize.cardVersion,
                issuerId: initialize.issuerId,
                currencyCode: initialize.currencyCode
            };

            await this.writePiccCardData('CardExpirationTimestamp', initializeValues.cardExpirationTimestamp, 'INTEGER', 1);
            await this.writePiccCardData('RiderTypeExternalId', initializeValues.riderTypeExternalId, 'BYTE', 1);
            await this.writePiccCardData('PreferredLanguage', initializeValues.preferredLanguage, 'BYTE', 2);
            await this.writePiccCardData('CardId', initializeValues.cardId, 'BYTE', 7);
            await this.writePiccCardData('CardType', initializeValues.cardType, 'BYTE', 1);
            await this.writePiccCardData('CardVersion', initializeValues.cardVersion, 'BYTE', 1);
            await this.writePiccCardData('IssuerId', initializeValues.issuerId, 'SHORT', 1);
            await this.writePiccCardData('CurrencyCode', initializeValues.currencyCode, 'SHORT', 1);

            await this.commitPiccCardData();

            return await this.getPiccCardData();
        } catch (err) {
            log.error(err);
        }
    }

    issueCard = async (issue) => {
        try {
            let issueValues = {
                riderTypeExpirationTimestamp: issue.riderTypeExpirationTimestamp,
                riderTypeExternalId: issue.riderTypeExternalId,
                preferredLanguage: issue.preferredLanguage,
                refundableDeposit: issue.refundableDeposit,
                terminalId: issue.terminalId,
                merchantId: issue.merchantId,
                ptoReference: issue.ptoReference
            };

            await this.writePiccCardData('RiderTypeExpirationTimestamp', issueValues.riderTypeExpirationTimestamp, 'INTEGER', 1);
            await this.writePiccCardData('RiderTypeExternalId', issueValues.riderTypeExternalId, 'BYTE', 1);
            await this.writePiccCardData('PreferredLanguage', issueValues.preferredLanguage, 'BYTE', 2);
            await this.writePiccCardData('RefundableDeposit', issueValues.refundableDeposit, 'INTEGER', 1);

            await this.writePiccCardData('TopupLogTerminalId', issueValues.terminalId, 'SHORT', 1);
            await this.writePiccCardData('TopupLogMerchantNumber', issueValues.merchantId, 'SHORT', 1);
            await this.writePiccCardData('TopupLogPtoRference', issueValues.ptoReference, 'BYTE', 1);

            await this.commitPiccCardData();

            return await this.getPiccCardData();
        } catch (err) {
            log.error(err);
        }
    }

    loadEpurse = async (load) => {
        try {
            let loadValues = {
                contractId: contract.contractId,
                instanceId: contract.instanceId,
                terminalId: contract.terminalId,
                merchantId: contract.merchantId,
                ptoReference: contract.ptoReference
            };

            await this.writePiccCardData('ContractId1', loadValues.contractId, 'INTEGER', 1);
            await this.writePiccCardData('InstanceId1', loadValues.instanceId, 'HEX', 5);

            await this.writePiccCardData('TopupLogTerminalId', loadValues.terminalId, 'SHORT', 1);
            await this.writePiccCardData('TopupLogMerchantNumber', loadValues.merchantId, 'SHORT', 1);
            await this.writePiccCardData('TopupLogPtoRference', loadValues.ptoReference, 'BYTE', 1);

            await this.commitPiccCardData();

            return await this.getPiccCardData();
        } catch (err) {
            log.error(err);
        }
    }

    creditEpurse = async (credit) => {
        try {
            let creditValues = {
                contractId: credit.contractId,
                amount: credit.amount,
                terminalType: credit.terminalType,
                terminalId: credit.terminalId,
                merchantId: credit.merchantId,
                ptoReference: credit.ptoReference
            };

            const res = await this.axios.post('creditEpurse', creditValues);
            if (typeof res == 'undefined' || res.status >= 400) { return {}; }

            // await this.writePiccCardData('ContractId1', creditValues.contractId, 'INTEGER', 1);
            // await this.writePiccCardData('StoredValue', creditValues.amount, 'INTEGER', 1);

            // await this.writePiccCardData('TopupLogTerminalId', creditValues.terminalId, 'SHORT', 1);
            // await this.writePiccCardData('TopupLogTerminalType', creditValues.terminalType, 'BYTE', 1);
            // await this.writePiccCardData('TopupLogMerchantNumber', creditValues.merchantId, 'SHORT', 1);
            // await this.writePiccCardData('TopupLogPtoRference', creditValues.ptoReference, 'BYTE', 1);

            return await this.getPiccCardData();
        } catch (err) {
            log.error(err);
        }
    }

    loadContract = async (contract, contractNo) => {
        try {
            const contractValues = {
                contractId: contract.contractId,
                instanceId: contract.instanceId,
                contractValue: contract.contractValue,
                contractCategory: contract.contractCategory,
                contractStatus: contract.contractStatus,
                contractStartTimestamp: contract.contractStartTimestamp,
                contractExpirationTimestamp: contract.contractExpirationTimestamp,
                contractStateChangeTimestamp: contract.contractStateChangeTimestamp
            };

            await this.writePiccCardData('ContractId'+contractNo, contractValues.contractId, 'INTEGER', 1);
            await this.writePiccCardData('InstanceId'+contractNo, contractValues.instanceId, 'HEX', 5);
            if (contractValues.contractCategory != enumsXml.smartCardContractCategory.StoredValueRelative) {
                await this.writePiccCardData('ContractValue'+contractNo, contractValues.contractValue, 'BYTE', 1);
            } else {
                await this.writePiccCardData('StoredValue', contractValues.contractValue, 'INTEGER', 1);
            }
            await this.writePiccCardData('ContractCategory'+contractNo, contractValues.contractCategory, 'BYTE', 1);
            await this.writePiccCardData('ContractState'+contractNo, contractValues.contractStatus, 'BYTE', 1);
            await this.writePiccCardData('ContractStartTimestamp'+contractNo, contractValues.contractStartTimestamp, 'INTEGER', 1);
            await this.writePiccCardData('ContractExpirationTimestamp'+contractNo, contractValues.contractExpirationTimestamp, 'INTEGER', 1);
            await this.writePiccCardData('ContractStateChangeTimestamp'+contractNo, contractValues.contractStateChangeTimestamp, 'INTEGER', 1);

            await this.commitPiccCardData();

            return await this.getPiccCardData();
        } catch (err) {
            log.error(err);
        }
    }

    removeExpiredContracts = async (contractPiccCardData) => {
        const removeContractValues = {
            contractId: 0,
            instanceId: '0000000000',
            contractValue: enumsXml.smartCardContractValue.pending,
            contractCategory: enumsXml.smartCardContractCategory.Undefined,
            contractStatus: enumsXml.smartCardContractStatus.invalid,
            contractStartTimestamp: moment.utc(0).format('X'),
            contractExpirationTimestamp: moment.utc(0).format('X'),
            contractStateChangeTimestamp: moment.utc(0).format('X')
        };

        for (let i=2; i<=5; i++) {
            if (((moment.utc(contractPiccCardData['contractExpirationTimestamp'+i]).diff(moment.utc(), 'second') < 0) && ([constantsXml.contractStatusType.pending, constantsXml.contractStatusType.active].includes(contractPiccCardData['contractStatus'+i]))) || ((contractPiccCardData['contractCategory'+i] == constantsXml.smartCardContractType.storedRides) && (contractPiccCardData['ContractValue'+i] <= 0))) {
                const removeContract = await this.removeContract(removeContractValues, i);

                const cardCounters = await this.cardCounters({
                    transactionCounter: contractPiccCardData.transactionCounter + 1,
                    countersTimestamp: moment.utc().format('X')
                });
            }
        }

        return await this.getPiccCardData();
    }

    removeContract = async (contract, contractNo) => {
        try {
            const contractValues = {
                contractId: contract.contractId,
                instanceId: contract.instanceId,
                contractValue: contract.contractValue,
                contractCategory: contract.contractCategory,
                contractStatus: contract.contractStatus,
                contractStartTimestamp: contract.contractStartTimestamp,
                contractExpirationTimestamp: contract.contractExpirationTimestamp,
                contractStateChangeTimestamp: contract.contractStateChangeTimestamp
            };

            await this.writePiccCardData('ContractId'+contractNo, contractValues.contractId, 'INTEGER', 1);
            await this.writePiccCardData('InstanceId'+contractNo, contractValues.instanceId, 'HEX', 5);
            await this.writePiccCardData('ContractValue'+contractNo, contractValues.contractValue, 'BYTE', 1);
            await this.writePiccCardData('ContractCategory'+contractNo, contractValues.contractCategory, 'BYTE', 1);
            await this.writePiccCardData('ContractState'+contractNo, contractValues.contractStatus, 'BYTE', 1);
            await this.writePiccCardData('ContractStartTimestamp'+contractNo, contractValues.contractStartTimestamp, 'INTEGER', 1);
            await this.writePiccCardData('ContractExpirationTimestamp'+contractNo, contractValues.contractExpirationTimestamp, 'INTEGER', 1);
            await this.writePiccCardData('ContractStateChangeTimestamp'+contractNo, contractValues.contractStateChangeTimestamp, 'INTEGER', 1);

            await this.commitPiccCardData();

            return await this.getPiccCardData();
        } catch (err) {
            log.error(err);
        }
    }

    changeRefundableDeposit = async (refundableDeposit) => {
        try {
            await this.writePiccCardData('RefundableDeposit', refundableDeposit, 'INTEGER', 1);

            await this.commitPiccCardData();

            return;
        } catch (err) {
            log.error(err);
        }
    }

    changeRiderTypeExternalId = async (riderTypeId) => {
        try {
            await this.writePiccCardData('RiderTypeExternalId', riderTypeId, 'BYTE', 1);

            await this.commitPiccCardData();

            return;
        } catch (err) {
            log.error(err);
        }
    }

    changeContractExpirationTimestamp = async (contractExpirationTimestamp, contractNo) => {
        try {
            await this.writePiccCardData('ContractExpirationTimestamp'+contractNo, contractExpirationTimestamp, 'INTEGER', 1);
            await this.writePiccCardData('ContractStateChangeTimestamp'+contractNo, moment.utc().format('X'), 'INTEGER', 1);

            await this.commitPiccCardData();

            return;
        } catch (err) {
            log.error(err);
        }
    }

    changeContractValue = async (contractValue, contractNo) => {
        try {
            await this.writePiccCardData('ContractValue'+contractNo, contractValue, 'BYTE', 1);
            await this.writePiccCardData('ContractStateChangeTimestamp'+contractNo, moment.utc().format('X'), 'INTEGER', 1);

            await this.commitPiccCardData();

            return;
        } catch (err) {
            log.error(err);
        }
    }

    issuerId = async (issuerId) => {
        try {
            await this.writePiccCardData('IssuerId', issuerId, 'SHORT', 1);

            await this.commitPiccCardData();

            return;
        } catch (err) {
            log.error(err);
        }
    }

    blockStatus = async (blockStatus) => {
        try {
            const blockStatusValues = {
                blockStatus: blockStatus.blockStatus,
                blockTimestamp: blockStatus.blockTimestamp
            };

            await this.writePiccCardData('BlockStatus', blockStatusValues.blockStatus, 'BYTE', 1);
            await this.writePiccCardData('BlockTimestamp', blockStatusValues.blockTimestamp, 'INTEGER', 1);

            await this.commitPiccCardData();

            return;
        } catch (err) {
            log.error(err);
        }
    }

    cardIteration = async (cardIteration) => {
        try {
            await this.writePiccCardData('CardIteration', cardIteration, 'BYTE', 1);

            await this.commitPiccCardData();

            return;
        } catch (err) {
            log.error(err);
        }
    }

    cardCounters = async (cardCounters) => {
        try {
            const cardCountersValues = {
                transactionCounter: cardCounters.transactionCounter,
                countersTimestamp: cardCounters.countersTimestamp
            };

            if (cardCounters.alic) {
                await this.writePiccCardData('Alic', cardCounters.alic, 'INTEGER', 1);
            }

            if (cardCounters.alwc) {
                await this.writePiccCardData('Alwc', cardCounters.alwc, 'INTEGER', 1);
            }

            await this.writePiccCardData('TransactionCounter', cardCountersValues.transactionCounter, 'INTEGER', 1);
            await this.writePiccCardData('CountersTimestamp', cardCountersValues.countersTimestamp, 'INTEGER', 1);

            await this.commitPiccCardData();

            return;
        } catch (err) {
            log.error(err);
        }
    }

    topUpLogs = async (topUpLog) => {
        try {
            const topUpLogValues = {
                topupLogTransactionTimestamp: topUpLog.topupLogTransactionTimestamp,
                topupLogContractCategory: topUpLog.topupLogContractCategory,
                topupLogContractId: topUpLog.topupLogContractId,
                topupLogContractPointer: topUpLog.topupLogContractPointer,
                topupLogFareItemCategory: topUpLog.topupLogFareItemCategory,
                topupLogFareItemType: topUpLog.topupLogFareItemType,
                topupLogActivityType: topUpLog.topupLogActivityType,
                topupLogTerminalId: topUpLog.topupLogTerminalId,
                topupLogTerminalType: topUpLog.topupLogTerminalType,
                topupLogTransactionId: topUpLog.topupLogTransactionId,
                topupLogAmountCredited: topUpLog.topupLogAmountCredited,
                topupLogMerchantNumber: topUpLog.topupLogMerchantNumber,
                topupLogPtoRference: topUpLog.topupLogPtoRference,
                topupLogTransactionSignature: topUpLog.topupLogTransactionSignature
            };

            await this.writePiccCardData('TopupLogTransactionTimestamp', topUpLogValues.topupLogTransactionTimestamp, 'INTEGER', 1);
            await this.writePiccCardData('TopupLogContractCategory', topUpLogValues.topupLogContractCategory, 'BYTE', 1);
            await this.writePiccCardData('TopupLogContractId', topUpLogValues.topupLogContractId, 'INTEGER', 1);
            await this.writePiccCardData('TopupLogContractPointer', topUpLogValues.topupLogContractPointer, 'BYTE', 1);
            await this.writePiccCardData('TopupLogFareItemCategory', topUpLogValues.topupLogFareItemCategory, 'BYTE', 1);
            await this.writePiccCardData('TopupLogFareItemType', topUpLogValues.topupLogFareItemType, 'BYTE', 1);
            await this.writePiccCardData('TopupLogActivityType', topUpLogValues.topupLogActivityType, 'BYTE', 1);
            await this.writePiccCardData('TopupLogTerminalId', topUpLogValues.topupLogTerminalId, 'SHORT', 1);
            await this.writePiccCardData('TopupLogTerminalType', topUpLogValues.topupLogTerminalType, 'BYTE', 1);
            await this.writePiccCardData('TopupLogTransactionId', topUpLogValues.topupLogTransactionId, 'INTEGER', 1);
            await this.writePiccCardData('TopupLogAmountCredited', topUpLogValues.topupLogAmountCredited, 'INTEGER', 1);
            await this.writePiccCardData('TopupLogMerchantNumber', topUpLogValues.topupLogMerchantNumber, 'SHORT', 1);
            await this.writePiccCardData('TopupLogPtoRference', topUpLogValues.topupLogPtoRference, 'BYTE', 1);
            //await this.writePiccCardData('TopupLogTransactionSignature', topUpLogValues.topupLogTransactionSignature, 'BYTE', 4);

            await this.commitPiccCardData();

            return;
        } catch (err) {
            log.error(err);
        }
    }

    usageLogs = async (usageLog) => {
        try {
            const usageLogValues = {
                usageLogTransactionTimestamp: usageLog.usageLogTransactionTimestamp,
                usageLogSecondsValid: usageLog.usageLogSecondsValid,
                usageLogTransfersLeft: usageLog.usageLogTransfersLeft,
                usageLogContractCategory: usageLog.usageLogContractCategory,
                usageLogContractId: usageLog.usageLogContractId,
                usageLogContractPointer: usageLog.usageLogContractPointer,
                usageLogActiveTransitType: usageLog.usageLogActiveTransitType,
                usageLogFareItemCategory: usageLog.usageLogFareItemCategory,
                usageLogFareItemType: usageLog.usageLogFareItemType,
                usageLogActivityType: usageLog.usageLogActivityType,
                usageLogBoardingActivity: usageLog.usageLogBoardingActivity,
                usageLogPassangerRiderTypeId1: usageLog.usageLogPassangerRiderTypeId1,
                usageLogNumberOfPassangers1: usageLog.usageLogNumberOfPassangers1,
                usageLogPassangerRiderTypeId2: usageLog.usageLogPassangerRiderTypeId2,
                usageLogNumberOfPassangers2: usageLog.usageLogNumberOfPassangers2,
                usageLogPassangerRiderTypeId3: usageLog.usageLogPassangerRiderTypeId3,
                usageLogNumberOfPassangers3: usageLog.usageLogNumberOfPassangers3,
                usageLogRouteMachineId: usageLog.usageLogRouteMachineId,
                usageLogBoardingStopMachineId: usageLog.usageLogBoardingStopMachineId,
                usageLogJourneyId: usageLog.usageLogJourneyId,
                usageLogJourneySequenceCounter: usageLog.usageLogJourneySequenceCounter,
                usageLogTerminalId: usageLog.usageLogTerminalId,
                usageLogTerminalType: usageLog.usageLogTerminalType,
                usageLogTransactionId: usageLog.usageLogTransactionId,
                usageLogAmountDebited: usageLog.usageLogAmountDebited,
                usageLogMerchantNumber: usageLog.usageLogMerchantNumber,
                usageLogPtoReference: usageLog.usageLogPtoReference,
                usageLogAlightingStopMachineId: usageLog.usageLogAlightingStopMachineId,
                usageLogBoardingZoneStageMachineId: usageLog.usageLogBoardingZoneStageMachineId,
                usageLogAlightingZoneStageMachineId: usageLog.usageLogAlightingZoneStageMachineId,
                usageLogTripDistanceTravelled: usageLog.usageLogTripDistanceTravelled,
                usageLogTripStagesOrZonesTravelled: usageLog.usageLogTripStagesOrZonesTravelled,
                usageLogJourneyDistanceTravelled: usageLog.usageLogJourneyDistanceTravelled,
                usageLogJourneyStagesOrZoneTravelled: usageLog.usageLogJourneyStagesOrZoneTravelled
            };

            await this.writePiccCardData('UsageLogTransactionTimestamp', usageLogValues.usageLogTransactionTimestamp, 'INTEGER', 1);
            await this.writePiccCardData('UsageLogSecondsValid', usageLogValues.usageLogSecondsValid, 'SHORT', 1);
            await this.writePiccCardData('UsageLogTransfersLeft', usageLogValues.usageLogTransfersLeft, 'BYTE', 1);
            await this.writePiccCardData('UsageLogContractCategory', usageLogValues.usageLogContractCategory, 'BYTE', 1);
            await this.writePiccCardData('UsageLogContractId', usageLogValues.usageLogContractId, 'INTEGER', 1);
            await this.writePiccCardData('UsageLogContractPointer', usageLogValues.usageLogContractPointer, 'BYTE', 1);
            await this.writePiccCardData('UsageLogActiveTransitType', usageLogValues.usageLogActiveTransitType, 'BYTE', 1);
            await this.writePiccCardData('UsageLogFareItemCategory', usageLogValues.usageLogFareItemCategory, 'BYTE', 1);
            await this.writePiccCardData('UsageLogFareItemType', usageLogValues.usageLogFareItemType, 'BYTE', 1);
            await this.writePiccCardData('UsageLogActivityType', usageLogValues.usageLogActivityType, 'BYTE', 1);
            await this.writePiccCardData('UsageLogBoardingActivity', usageLogValues.usageLogBoardingActivity, 'BYTE', 1);
            await this.writePiccCardData('UsageLogPassangerRiderTypeId1', usageLogValues.usageLogPassangerRiderTypeId1, 'BYTE', 1);
            await this.writePiccCardData('UsageLogNumberOfPassangers1', usageLogValues.usageLogNumberOfPassangers1, 'BYTE', 1);
            await this.writePiccCardData('UsageLogPassangerRiderTypeId2', usageLogValues.usageLogPassangerRiderTypeId2, 'BYTE', 1);
            await this.writePiccCardData('UsageLogNumberOfPassangers2', usageLogValues.usageLogNumberOfPassangers2, 'BYTE', 1);
            await this.writePiccCardData('UsageLogPassangerRiderTypeId3', usageLogValues.usageLogPassangerRiderTypeId3, 'BYTE', 1);
            await this.writePiccCardData('UsageLogNumberOfPassangers3', usageLogValues.usageLogNumberOfPassangers3, 'BYTE', 1);
            await this.writePiccCardData('UsageLogRouteMachineId', usageLogValues.usageLogRouteMachineId, 'SHORT', 1);
            await this.writePiccCardData('UsageLogBoardingStopMachineId', usageLogValues.usageLogBoardingStopMachineId, 'INTEGER', 1);
            await this.writePiccCardData('UsageLogJourneyId', usageLogValues.usageLogJourneyId, 'INTEGER', 1);
            await this.writePiccCardData('UsageLogJourneySequenceCounter', usageLogValues.usageLogJourneySequenceCounter, 'BYTE', 1);
            await this.writePiccCardData('UsageLogTerminalId', usageLogValues.usageLogTerminalId, 'SHORT', 1);
            await this.writePiccCardData('UsageLogTerminalType', usageLogValues.usageLogTerminalType, 'BYTE', 1);
            await this.writePiccCardData('UsageLogTransactionId', usageLogValues.usageLogTransactionId, 'INTEGER', 1);
            await this.writePiccCardData('UsageLogAmountDebited', usageLogValues.usageLogAmountDebited, 'INTEGER', 1);
            await this.writePiccCardData('UsageLogMerchantNumber', usageLogValues.usageLogMerchantNumber, 'SHORT', 1);
            await this.writePiccCardData('UsageLogPtoReference', usageLogValues.usageLogPtoReference, 'BYTE', 1);
            await this.writePiccCardData('UsageLogAlightingStopMachineId', usageLogValues.usageLogAlightingStopMachineId, 'INTEGER', 1);
            await this.writePiccCardData('UsageLogBoardingZoneStageMachineId', usageLogValues.usageLogBoardingZoneStageMachineId, 'SHORT', 1);
            await this.writePiccCardData('UsageLogAlightingZoneStageMachineId', usageLogValues.usageLogAlightingZoneStageMachineId, 'SHORT', 1);
            await this.writePiccCardData('UsageLogTripDistanceTravelled', usageLogValues.usageLogTripDistanceTravelled, 'SHORT', 1);
            await this.writePiccCardData('UsageLogTripStagesOrZonesTravelled', usageLogValues.usageLogTripStagesOrZonesTravelled, 'BYTE', 1);
            await this.writePiccCardData('UsageLogJourneyDistanceTravelled', usageLogValues.usageLogJourneyDistanceTravelled, 'SHORT', 1);
            await this.writePiccCardData('UsageLogJourneyStagesOrZoneTravelled', usageLogValues.usageLogJourneyStagesOrZoneTravelled, 'BYTE', 1);

            await this.commitPiccCardData();

            return;
        } catch (err) {
            log.error(err);
        }
    }

    getCardPrintedId = async (cardUid) => {
        try {
            let cardPrintedId = this.parseDecString(cardUid.match(/[a-fA-F0-9]{2}/g).reverse().join('')).toString().padStart(15, '0').slice(-15);

            return await this.getLuhnValidId(cardPrintedId);
        } catch (err) {
            log.error(err);
        }
    }

    parseDecString = (str) => {
        function add(x, y) {
            var c = 0, r = [];
            var x = x.split('').map(Number);
            var y = y.split('').map(Number);
            while (x.length || y.length) {
                var s = (x.pop() || 0) + (y.pop() || 0) + c;
                r.unshift(s < 10 ? s : s - 10);
                c = s < 10 ? 0 : 1;
            }
            if (c) r.unshift(c);
            return r.join('');
        }

        var dec = '0';
        str.split('').forEach(function(chr) {
            var n = parseInt(chr, 16);
            for (var t = 8; t; t >>= 1) {
                dec = add(dec, dec);
                if (n & t) dec = add(dec, '1');
            }
        });
        return dec;
    }

    getLuhnValidId = async (cardNumber) => {
        if (/[^0-9-\s]+/.test(cardNumber)) {
            return false;
        }

        let sum = 0;
        let isDoubled = true;

        for (let i = cardNumber.length - 1; i >= 0; i--) {
            let digit = parseInt(cardNumber.charAt(i), 10);

            if (isDoubled) {
                if ((digit *= 2) > 9) {
                    digit -= 9
                }
            }
            sum += digit;
            isDoubled = !isDoubled;
        }

        return cardNumber.concat((sum * 9) % 10).toString();
    }

    getCardUid = async (cardPrintedId) => {
        try {
            let cardUid = parseInt(cardPrintedId.slice(0,-1), 10).toString(16).padStart(14, '0').match(/[a-fA-F0-9]{2}/g).reverse().join('').toUpperCase();

            return cardUid;
        } catch (err) {
            log.error(err);
        }
    }

    getTransformedPiccCardData = async (cardData) => {
        let data = {
            cardUid: cardData.cardId,
            cardPrintedId: await this.getCardPrintedId(cardData.cardId),
            cardType: cardData.cardType,
            cardVersion: cardData.cardVersion,
            cardIteration: cardData.cardIteration,
            issuerId: cardData.issuerId,
            transactionCounter: cardData.transactionCounter,
            blockTimestamp: moment.unix(cardData.blockTimestamp).utc().format(this.dateTTimeFormat),
            blockStatus: constantsXml.smartCardBlockStatus[cardData.blockStatus],
            alic: cardData.alic,
            alwc: cardData.alwc,
            cardExpirationTimestamp: moment.unix(cardData.cardExpirationTimestamp).utc().format(this.dateTTimeFormat),
            currencyCode: constantsXml.currencyIsoCode[cardData.currencyCode],
            preferredLanguage: constantsXml.languageCode[cardData.preferredLanguage],
            refundableDeposit: cardData.refundableDeposit,
            riderTypeExternalId: cardData.riderTypeExternalId
        };

        for (let i=1; i<=5; i++) {
            data['contractId'+i] = 0;
            data['instanceId'+i] = '0000000000';
            data['contractValue'+i] = 0;
            data['contractCategory'+i] = constantsXml.contractType.undefined;
            data['contractStatus'+i] = constantsXml.contractStatusType.invalid;
            data['contractStartTimestamp'+i] = moment.utc(0).format(this.dateTTimeFormat);
            data['contractExpirationTimestamp'+i] = moment.utc(0).format(this.dateTTimeFormat);
            data['contractStateChangeTimestamp'+i] = moment.utc(0).format(this.dateTTimeFormat);
        }

        return data;
    }

    securityElementTransactionCounter = async () => {
        try {
            const device = await this.posModel.findOne({});

            let securityElementTransactionCounter = (device.securityElementTransactionCounter == undefined) ? 1 : device.securityElementTransactionCounter+1;

            await this.posModel.findOneAndUpdate({ deviceId: device.deviceId }, {
                securityElementTransactionCounter: securityElementTransactionCounter
            }, { upsert: true });

        } catch (err) {
            log.error(err);
        }
    }

    demoCardData = async () => {
        let data = {
            cardUid: '5925105C0B8660',
            cardPrintedId: '1689811115472256',
            cardType: 10,
            cardVersion: 1,
            cardIteration: 1,
            issuerId: 10,
            transactionCounter: 1,
            blockTimestamp: moment.utc(0).format(this.dateTTimeFormat),
            blockStatus: constantsXml.blockStatusType.notBlocked,
            alic: 0,
            alwc: 0,
            cardExpirationTimestamp: moment.utc().add(10, 'years').format(this.dateTTimeFormat),
            currencyCode: global.currentPosData.currencyCode,
            preferredLanguage: constantsXml.languageCode.en,
            refundableDeposit: 0,
            riderTypeExternalId: 1,
            contractId1: 5,
            instanceId1: '0001000000',
            contractValue1: 1000,
            contractCategory1: constantsXml.contractType.ePurse,
            contractStatus1: constantsXml.contractStatusType.active,
            contractStartTimestamp1: moment.utc().subtract(10, 'days').startOf('day').format(this.dateTTimeFormat),
            contractExpirationTimestamp1: moment.utc().add(10, 'years').format(this.dateTTimeFormat),
            contractStateChangeTimestamp1: moment.utc().subtract(10, 'days').startOf('day').format(this.dateTTimeFormat)
        };

        // products contracts
        for (let i=2; i<=5; i++) {
            data['contractId'+i] = 0;
            data['instanceId'+i] = '0000000000';
            data['contractValue'+i] = 0;
            data['contractCategory'+i] = constantsXml.contractType.undefined;
            data['contractStatus'+i] = constantsXml.contractStatusType.invalid;
            data['contractStartTimestamp'+i] = moment.utc(0).format(this.dateTTimeFormat);
            data['contractExpirationTimestamp'+i] = moment.utc(0).format(this.dateTTimeFormat);
            data['contractStateChangeTimestamp'+i] = moment.utc(0).format(this.dateTTimeFormat);
        }

        return data;
    }

    demoCardDataApi = async () => {
        let data = {
            readOnly: {
                uid: '5925105C0B8660',
                printedId: '1689811115472256',
                type: 'Permanent Smart Card',
                expiryDate: moment.utc().add(10, 'years').format(this.dateTTimeFormat),
                alic: 0,
                alwc: 0
            },
            transactionCounter: 1,
            riderType: 1,
            blockStatus: {
                status: constantsXml.blockStatusType.notBlocked,
                lastChangeTime: moment.utc(0).format(this.dateTTimeFormat)
            },
            storedValue: 1000,
            pendingStoredValue: 0,
            refundableDeposit: 0,
            contracts: [{
                type: 'e-purse',
                id: 1,
                contractId: 5,
                instanceId: '0001000000',
                status: constantsXml.contractStatusType.active,
                operationalStatus: 'LOADED',
                reasonId: null,
                reasonDescription: null,
                productName: 'E-Purse',
                expiryTime: moment.utc().add(10, 'years').format(this.dateTimeFormat)
            }],
            issuingDateTime: null,
            cardHolderFirstName: null,
            cardHolderLastName: null,
            endUserUid: 0,
            endUserName: null,
            autoload: {
                autoloadStoredValue: false,
                reloadAmount: 0,
                autoloadPeriodPass: false
            },
            cardIteration: 1,
            version: 10,
            smartCardTypeExternalId: 1,
            smartCardProfileExternalId: 1,
            issuerId: 10
        };

        return data;
    }

    demoCardProfilesApi = async () => {
        let data = [
            {
                id: 1,
                externalId: '1',
                name: 'Permanent Smart Card',
                versionId: 1,
                versionName: 'Anonymous Profile Card',
                description: 'Anonymous Plastic Card',
                cardTechnology: constantsXml.smartCardPhysicalType.mifareDesfireEv3,
                cardPhysicalForm: 'plastic',
                mediaPurposeType: constants.mediaPurposeType.anonymous
            },
            {
                id: 2,
                externalId: '2',
                name: 'Permanent Smart Card',
                versionId: 2,
                versionName: 'Personalized Profile Card',
                description: 'Personalized Plastic Card',
                cardTechnology: constantsXml.smartCardPhysicalType.mifareDesfireEv3,
                cardPhysicalForm: 'plastic',
                mediaPurposeType: constants.mediaPurposeType.personalized
            }
        ];

        return data;
    }

    demoCardProfiles = async () => {
        let data = [
            {
                externalId: '1',
                cardType: 'Permanent Smart Card',
                cardVersionShortNumber: 1,
                cardVersion: 'Anonymous Profile Card',
                description: 'Anonymous Plastic Card',
                cardTechnology: constantsXml.smartCardPhysicalType.mifareDesfireEv3,
                physicalCardType: 'plastic',
                mediaPurposeType: constants.mediaPurposeType.anonymous
            },
            {
                externalId: '2',
                cardType: 'Permanent Smart Card',
                cardVersionShortNumber: 2,
                cardVersion: 'Personalized Profile Card',
                description: 'Personalized Plastic Card',
                cardTechnology: constantsXml.smartCardPhysicalType.mifareDesfireEv3,
                physicalCardType: 'plastic',
                mediaPurposeType: constants.mediaPurposeType.personalized
            }
        ];

        return data;
    }

    demoCardPurchasesApi = async () => {
        let data = [
            {
                productType: constantsXml.contractType.storedRides,
                cost: 400,
                timestamp: moment.utc().subtract(10, 'days').add(10, 'minutes').format(this.dateTTimeFormat),
                deviceType: constantsXml.deviceType.vehicleTicketingMachine
            },
            {
                productType: constantsXml.contractType.periodPass,
                cost: 600,
                timestamp: moment.utc().subtract(20, 'days').add(20, 'minutes').format(this.dateTTimeFormat),
                deviceType: constantsXml.deviceType.mobileTicketingMachine
            },
            {
                productType: constantsXml.contractType.ePurse,
                cost: 1000,
                timestamp: moment.utc().subtract(30, 'days').add(30, 'minutes').format(this.dateTTimeFormat),
                deviceType: constantsXml.deviceType.officePos
            },
            {
                productType: constantsXml.contractType.periodPass,
                cost: 1500,
                timestamp: moment.utc().subtract(40, 'days').add(40, 'minutes').format(this.dateTTimeFormat),
                deviceType: constantsXml.deviceType.networkWebPortal
            }
        ];

        return data;
    }

    demoCardRidesApi = async () => {
        let data = [
            {
                tripId: '1',
                timestamp: moment.utc().subtract(10, 'days').add(10, 'minutes').format(this.dateTTimeFormat),
                routeName: 'Route name',
                busId: '1234',
                fareProductType: constantsXml.smartCardContractType.periodPass,
                remainingValue: 0,
                remainingRides: 0,
                remainingDays: 10,
                actualBoardingStopName: 'Boarding stop name',
                validationStatusType: constantsXml.validationStatus.validationSuccessful
            },
            {
                tripId: '2',
                timestamp: moment.utc().subtract(20, 'days').add(20, 'minutes').format(this.dateTTimeFormat),
                routeName: 'Route name',
                busId: '4321',
                fareProductType: constantsXml.smartCardContractType.storedRides,
                remainingValue: 1000,
                remainingRides: 0,
                remainingDays: 0,
                actualBoardingStopName: 'Boarding stop name',
                validationStatusType: constantsXml.validationStatus.validationSuccessful
            }
        ];

        return data;
    }

}

module.exports = SmartCardServ;
