/**
 * Modul modem.
 *
 * Modul ini sedang proses pengalihan ke module:bootstrap
 *
 * @module
 * @deprecated going to move to module:bootstrap
 */

const DEFAULT_SLEEP_AFTER_SEND_SMS_MS = 2000;
const INTERVAL_BEETWEN_SIGNAL_STRENGTH_MS = 30000;
const MAX_LAST_DATA_AGE_MS = 3 * 60 * 1000;
const REGEX_WAIT_FOR_OK_OR_ERROR = /\n(?:OK|ERROR)\r/;
// const REGEX_WAIT_FOR_OK_OR_ERROR_USSD = /\n(?:OK|ERROR)\r/;

const moment = require('moment');
const SerialPort = require('serialport');
const ParserReadline = require('@serialport/parser-readline');
// const ParserDelimiter = require('@serialport/parser-delimiter');

const ParserRegex = require('@serialport/parser-regex');

const config = require('komodo-sdk/config');
const logger = require('komodo-sdk/logger');

const mutex = require('./mutex');
const common = require('./common');
const sms = require('./sms');
const dbCops = require('./db-cops');
const reportSender = require('./report-sender');
const registerModem = require('./register-modem');

const modemInfo = {
    device: config.modem.device,
    manufacturer: null,
    model: null,
    imei: null,
    imsi: null,
    msisdn: null,
    cops: null,
    networkId: null,
    networkName: null,
    signalStrength: null,
    signalStrengthTs: null,
    signalStrengthTsReadable: null,
};

let lastTs = new Date();

let port;

const parserReadLine = new ParserReadline();

const parserWaitForOK = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR });
parserWaitForOK.on('data', () => {
    mutex.releaseLockWaitForCommand();
});

function writeToPort(data) {
    return new Promise((resolve) => {
        port.write(data, (err, bytesWritten) => {
            if (err) logger.warn(`ERROR: ${err.toString()}`);
            logger.verbose(`* OUT: ${data}`);
            resolve(bytesWritten);
        });
    });
}

async function readSMS(slot) {
    const parserCMGR = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR });
    parserCMGR.on('data', (data) => {
        if (data) {
            try {
                reportSender.incomingSMS(sms.extract(data.toString().trim()), modemInfo);
            } catch (e) {
                logger.warn(`Exception on reporting new message. ${e.toString()}`, { smsObj: e.smsObj, dataFromModem: data });

                process.exit(0);
            }
        }
        port.unpipe(parserCMGR);
        mutex.releaseLockWaitForCommand();
    });

    // const parserCMGD = new ParserDelimiter({ delimiter: DELIMITER_WAIT_FOR_OK });
    const parserCMGD = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR });
    parserCMGD.on('data', () => {
        port.unpipe(parserCMGD);
        mutex.releaseLockWaitForCommand();
    });

    logger.info(`Reading SMS on slot ${slot}`);
    await mutex.setLockWaitForCommand();
    port.pipe(parserCMGR);
    await writeToPort(`AT+CMGR=${slot}\r`);
    logger.info(`Finished reading SMS on slot ${slot}`);

    logger.info(`Deleting message on slot ${slot}`);
    await mutex.setLockWaitForCommand();
    port.pipe(parserCMGD);
    await writeToPort(`AT+CMGD=${slot}\r`);
    logger.info('Message processing has completed');
}

function onIncomingSMS(data) {
    const value = common.extractValueFromReadLineData(data);
    if (!value) return;

    const chunks = value.split(',');
    if (!chunks && !chunks[1]) return;

    const slot = chunks[1];

    logger.info(`Incoming SMS on slot ${slot}`);
    readSMS(slot);
}

function onCOPS(data) {
    modemInfo.cops = common.extractValueFromReadLineData(data).trim();
    logger.info(`Connected Network: ${modemInfo.cops}`);

    if (!modemInfo.cops) return;

    [, , modemInfo.networkId] = modemInfo.cops.split(',');

    if (modemInfo.networkId) {
        modemInfo.networkName = dbCops[modemInfo.networkId] || modemInfo.networkId;
    }
}

parserReadLine.on('data', (data) => {
    logger.verbose(`* IN: ${data}`);
    if (data) {
        lastTs = new Date();
        if (data.indexOf('+CSQ: ') === 0) {
            const signalStrength = common.extractValueFromReadLineData(data).trim();
            if (signalStrength) {
                modemInfo.signalStrength = signalStrength;
                modemInfo.signalStrengthTs = new Date();
                modemInfo.signalStrengthTsReadable = moment(modemInfo.signalStrengthTs).format('YYYY-MM-DD HH:mm:ss');
                logger.info(`Signal strength: ${modemInfo.signalStrength}`);
                registerModem(modemInfo);
            }
        } else if (data.indexOf('+CMTI: ') === 0) {
            onIncomingSMS(data);
        } else if (data.indexOf('+COPS: ') === 0) {
            onCOPS(data);
        }
    }
});

async function simpleSubCommand(cmd, callback) {
    const parser = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR });
    parser.on('data', (data) => {
        port.unpipe(parser);
        mutex.releaseLockWaitForSubCommand();

        if (data) {
            if (callback) callback(null, data.toString().trim());
        }
    });

    return new Promise(async (resolve) => {
        await mutex.setLockWaitForSubCommand();
        port.pipe(parser);
        writeToPort(cmd);

        await mutex.setLockWaitForSubCommand();
        mutex.releaseLockWaitForSubCommand();

        resolve();
    });
}

function readManufacturer() {
    return new Promise((resolve) => {
        simpleSubCommand('AT+CGMI\r', (err, result) => {
            modemInfo.manufacturer = result;
            logger.info(`Manufacturer: ${result}`);
            resolve(result);
        });
    });
}

function readModel() {
    return new Promise((resolve) => {
        simpleSubCommand('AT+CGMM\r', (err, result) => {
            modemInfo.model = result;
            logger.info(`Model: ${result}`);
            resolve(result);
        });
    });
}

function readIMEI() {
    return new Promise((resolve) => {
        simpleSubCommand('AT+CGSN\r', (err, result) => {
            modemInfo.imei = result;
            logger.info(`IMEI: ${result}`);
            resolve(result);
        });
    });
}

function readIMSI() {
    return new Promise((resolve) => {
        simpleSubCommand('AT+CIMI\r', (err, result) => {
            modemInfo.imsi = result;
            logger.info(`IMSI: ${result}`);

            if (result) {
                /*
                modemInfo.msisdn = msisdn[result];
                if (modemInfo.msisdn) {
                    logger.info(`MSISDN: ${modemInfo.msisdn}`);
                    registerModem(modemInfo);
                }
                */
            } else {
                logger.warn(`IMSI not detected. Please insert a sim card to your modem. Terminating  ${config.modem.device}.`);
                process.exit(2);
            }
            resolve(result);
        });
    });
}

function readCOPS() {
    return new Promise((resolve) => {
        simpleSubCommand('AT+COPS?\r', (err, result) => {
            resolve(result);
        });
    });
}

function deleteInbox() {
    return new Promise((resolve) => {
        simpleSubCommand('AT+CMGD=0,4\r', (err, result) => {
            resolve(result);
        });
    });
}

async function querySignalStrength() {
    const parser = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR });
    parser.on('data', () => {
        port.unpipe(parser);
        mutex.releaseLockWaitForCommand();
    });

    if (mutex.tryLockWaitForCommand()) {
        port.pipe(parser);
        await writeToPort('AT+CSQ\r');
    }
}

function registerModemToCenterPeriodically() {
    registerModem(modemInfo);

    setInterval(() => {
        registerModem(modemInfo);
    }, 60 * 1000);
}

async function registerSignalStrengthBackgroundQuery() {
    logger.info('Registering background signal strength query');

    querySignalStrength();

    setInterval(() => {
        querySignalStrength();
    }, config.interval_beetwen_signal_strength_ms || INTERVAL_BEETWEN_SIGNAL_STRENGTH_MS);
}

async function sendSMS(destination, msg) {
    if (typeof destination !== 'string' || typeof msg !== 'string' || !destination.trim() || !msg.trim()) return;

    // const parser = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR });
    const parser = new ParserReadline({ delimiter: '\r\n' });
    parser.on('data', () => {
        port.unpipe(parser);
        mutex.releaseLockWaitForSubCommand();
    });

    logger.verbose('Waiting for command lock to send message');
    await mutex.setLockWaitForCommand();

    logger.info('Preparing to send message', { destination, msg });

    const correctedDestination = `+${destination}`.replace(/^0/, '62').replace(/^\++/, '+');

    logger.verbose('Waiting for lock before set SMS to text mode');
    await mutex.setLockWaitForSubCommand();
    port.pipe(parser);
    await writeToPort('AT+CMGF=1\r');

    logger.verbose('Waiting for lock before writing message');
    await mutex.setLockWaitForSubCommand();
    port.pipe(parser);
    await writeToPort(`AT+CMGS="${correctedDestination}"\r`);
    await writeToPort(msg);
    await writeToPort(Buffer.from([0x1A]));

    await mutex.setLockWaitForSubCommand();
    mutex.releaseLockWaitForSubCommand();

    logger.info('Message has been sent');

    setTimeout(() => {
        logger.verbose('Releasing command lock');
        mutex.releaseLockWaitForCommand();
    }, config.sleep_after_send_sms_ms || DEFAULT_SLEEP_AFTER_SEND_SMS_MS);
}

/**
 * Ekseksusi kode USSD.
 *
 * Pilihan includeCUSD2:
 * -1: sebelum
 * 0: tidak (default)
 * 1: sesudah
 * 2: sebelum dan sesudah
 *
 * @param  {string} code - Kode USSD
 * @param  {number} [includeCUSD2=0] - Apakah ingin otomatis memasukkan CUSD=2
 */
function executeUSSD(code, includeCUSD2) {
    return new Promise(async (resolve) => {
        const parserMain = new ParserReadline({ delimiter: '\r\n' });
        // const parserMain = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR });
        parserMain.on('data', (data) => {
            if (!data || !data.toString().trim()) return;

            if (data.toString().trim() === 'OK') return;

            port.unpipe(parserMain);
            mutex.releaseLockWaitForSubCommand();
            resolve(data);
        });

        const parserCUSD2 = new ParserReadline({ delimiter: '\r\n' });
        // const parserCUSD2 = new ParserRegex({ regex: REGEX_WAIT_FOR_OK_OR_ERROR });
        parserCUSD2.on('data', () => {
            port.unpipe(parserCUSD2);
            mutex.releaseLockWaitForSubCommand();
        });

        logger.verbose('Waiting for command lock to execute USSD');
        await mutex.setLockWaitForCommand();

        if (includeCUSD2 === -1 || includeCUSD2 === 2) {
            await mutex.setLockWaitForSubCommand();
            logger.info('Terminating existing USSD session');
            port.pipe(parserCUSD2);
            await writeToPort('AT+CUSD=2\r');
        }

        await mutex.setLockWaitForSubCommand();
        logger.info(`Executing USSD code "${code}"`);
        port.pipe(parserMain);
        await writeToPort(`AT+CUSD=1,"${code}",15\r`);

        if (includeCUSD2 === 1 || includeCUSD2 === 2) {
            await mutex.setLockWaitForSubCommand();
            logger.info('Terminating existing USSD session');
            port.pipe(parserCUSD2);
            await writeToPort('AT+CUSD=2\r');
        }

        await mutex.setLockWaitForSubCommand();
        mutex.releaseLockWaitForSubCommand();

        mutex.releaseLockWaitForCommand();
    });
}

function init() {
    port = new SerialPort(config.modem.device, { baudRate: 115200 }, (err) => {
        if (err) {
            logger.warn(`Error opening modem. ${err}. Terminating modem ${config.modem.device}.`);
            process.exit(1);
        }

        registerModem(modemInfo);
    });
    port.pipe(parserReadLine);

    setInterval(() => {
        if ((new Date() - lastTs) > MAX_LAST_DATA_AGE_MS) {
            logger.warn(`No data for more than ${MAX_LAST_DATA_AGE_MS} ms. Modem might be unresponsive. Terminating modem ${config.modem.device}.`);
            process.exit(0);
        }
    }, 30 * 1000);

    port.on('open', async () => {
        await mutex.setLockWaitForCommand();

        logger.info('Modem opened');
        await writeToPort('\r');
        await simpleSubCommand('AT\r');

        logger.info('Initializing modem to factory set');
        await simpleSubCommand('AT&F\r');

        logger.info('Disabling echo');
        await simpleSubCommand('ATE0\r');

        logger.info('Set to text mode');
        await simpleSubCommand('AT+CMGF=1\r');

        logger.info('Set message indication');
        await simpleSubCommand('AT+CNMI=1,1,2,1,1\r');

        await readCOPS();

        await readManufacturer();
        await readModel();
        await readIMEI();
        await readIMSI();

        if (!config.disable_delete_inbox_on_startup) {
            logger.info('Deleting existing messages');
            await deleteInbox();
        }

        mutex.releaseLockWaitForCommand();
        logger.verbose('Init completed');

        registerModemToCenterPeriodically();
        registerSignalStrengthBackgroundQuery();
    });
}

init();

exports.modemInfo = modemInfo;
exports.sendSMS = sendSMS;
exports.executeUSSD = executeUSSD;