#!/usr/bin/env python

import logging
from logging.handlers import TimedRotatingFileHandler
import ConfigParser
import json
from os import getpid
import os
import re
import signal
import sys

config = ConfigParser.RawConfigParser()
config.read('config.ini')

PORT = config.get('globals','PORT')
BAUDRATE = config.getint('globals', 'BAUDRATE')
PIN = None # SIM card PIN (if any)
PIN_TRX = config.get('globals', 'PIN_TRX')

BASE_CHIPINFO = config.get('globals', 'BASE_CHIPINFO')
CHIPINFO = BASE_CHIPINFO

AAA_HOST = config.get('globals', 'AAA_HOST')
CITY = config.get('globals', 'CITY')
PRODUCTS = config.get('globals', 'PRODUCTS')

REDIS_HOST = config.get('globals', 'REDIS_HOST')
REDIS_PORT = config.getint('globals', 'REDIS_PORT')
REDIS_TTL = config.getint('globals', 'REDIS_TTL')
REDIS_DISABLE_PULL_TTL = config.getint('globals', 'REDIS_DISABLE_PULL_TTL')

PULL_INTERVAL = config.getint('globals', 'PULL_INTERVAL')
SLEEP_AFTER_TOPUP = config.getint('globals', 'SLEEP_AFTER_TOPUP')
SLEEP_BETWEEN_BALANCE_N_TOPUP = config.getint('globals', 'SLEEP_BETWEEN_BALANCE_N_TOPUP')
TOPUP_USSD_TIMEOUT = config.getint('globals', 'TOPUP_USSD_TIMEOUT')
SLEEP_AFTER_USSD_ERROR = 180

MIN_SIGNAL_STRENGTH = 0
try:
    MIN_SIGNAL_STRENGTH = config.getint('globals', 'MIN_SIGNAL_STRENGTH')
except:
    pass
if not MIN_SIGNAL_STRENGTH:
    MIN_SIGNAL_STRENGTH = 13
    
MIN_BALANCE = config.getint('globals', 'MIN_BALANCE')

PULL_COUNT = 0
MSISDN = ''
BALANCE = 0

DISABLE_SEM=0
DISABLE_SEM_ON_TRX=60

TERMINATING=False

LAST_REQUEST_ID = None
LAST_SN = None
LAST_PRODUCT = ''

PULSA = 0
MASA_AKTIF = ""

logger = None

from gsmmodem.modem import GsmModem
from gsmmodem.exceptions import TimeoutException

from time import sleep
from time import strftime

import redis
import requests

import sate24 
import xltunai

redis_client = redis.StrictRedis(host=REDIS_HOST, port=REDIS_PORT, db=0)

def signalHandler(signum, frame):
    global TERMINATING
    
    if signum == signal.SIGINT or signum == signal.SIGTERM:
        logger.info('Signal ({0}) received, waiting for next loop to terminate. Terminating...'.format(signum))
        TERMINATING = True
    
def createDenyProductFile(product):
    f = open('{0}.deny'.format(product), 'w')
    f.write(product)
    f.close()
    
def handleSms(sms):
    global CHIPINFO
    global DISABLE_SEM
    global PRODUCTS
    global LAST_PRODUCT
    global LAST_REQUEST_ID
    global LAST_SN
    
    try:
        logger.info(u'Incoming SMS from: {0}; Time: {1}; Message: {2}'.format(sms.number, sms.time, sms.text))
    except:
        logger.info(u'Incoming SMS from: {0}; Time: {1}; Message: BINARY'.format(sms.number, sms.time))
    
    try:
        epic_key = 'epic.msgin.gw:' + BASE_CHIPINFO
        redis_client.publish(epic_key + '.message', u'{0}: {1} ({2})'.format(sms.number, sms.text, CHIPINFO))
    except:
        logger.warning('Fail to publish epic message to redis')
    
    if not xltunai.isValidSender(sms.number):
        logger.info('Ignoring incoming SMS from invalid sender')
        return
        
    if sms.text.find('Terimakasih, transaksi CASH IN ke akun') >= 0:
        logger.info('handleSms: CASH IN, aktivasi pull jika non aktif')
        
        LAST_SN = xltunai.getSNFromCashInMessage(sms.text)
        logger.info('Override LAST_SN: {0}'.format(LAST_SN))
        
        enablePull()
        return
    
    if sms.text.find('Maaf, transaksi gagal') >= 0:
        pushTopupStatus(LAST_REQUEST_ID, '40', sms.text)
        createDenyProductFile('ALL');
        PRODUCTS = sate24.removeProduct(PRODUCTS, LAST_PRODUCT)
        return
    
    if sms.text.find('PIN yang Anda masukkan salah. Silahkan ulangi kembali') >= 0:
        pushTopupStatus(LAST_REQUEST_ID, '91', sms.text)
        return
        
    if sms.text.find('Maaf, saldo XL-Tunai anda tidak cukup') >= 0:
        pushTopupStatus(LAST_REQUEST_ID, '40', sms.text)
        return
    
    if sms.text.find('Maaf, layanan ini hanya untuk nomor tujuan prabayar XL') >= 0:
        pushTopupStatus(LAST_REQUEST_ID, '14', sms.text)
        return
    
    if sms.text.find('Mohon maaf, nomor yang Anda masukkan tidak valid') >= 0:
        pushTopupStatus(LAST_REQUEST_ID, '14', sms.text)
        return
    
    if sms.text.find('Mohon maaf, transaksi Anda melebihi limit nominal bulanan') >= 0:
        pushTopupStatus(LAST_REQUEST_ID, '40', sms.text)
        
        logger.info('Monthly limit for "{0}" detected, removing from product list'.format(LAST_PRODUCT))
        PRODUCTS = sate24.removeProduct(PRODUCTS, LAST_PRODUCT)
        logger.warning('Monthly limit for "{0}" exceeded. New active products: "{1}"'.format(LAST_PRODUCT, PRODUCTS))
        createDenyProductFile(LAST_PRODUCT)
        return
    
    if sms.text.find('Maaf, transaksi Anda masih dalam proses') >= 0:
        pushTopupStatus(LAST_REQUEST_ID, '68', sms.text)
        return
    
    if sms.text.find('Maaf, saat ini sistem sedang proses maintenance') >= 0:
        pushTopupStatus(LAST_REQUEST_ID, '91', sms.text)
        return
    
    if sms.text.find('Anda terima uang XLTunai') >= 0:
        LAST_SN = xltunai.getSNFromReceiveTransferMessage(sms.text)
        logger.info('Override LAST_SN: {0}'.format(LAST_SN))
        return
    
    if sms.text.find('Kirim uang ke ') == 0:
        LAST_SN = xltunai.getSNFromSentTransferMessage(sms.text)
        logger.info('Override LAST_SN: {0}'.format(LAST_SN))
        return
    
    destination = xltunai.getDestinationFromMessage(sms.text)
    if destination == '':
        logger.warning('handleSms: gagal parsing nomor tujuan')
        return
        
    nominal = xltunai.getNominalFromMessage(sms.text)
    if nominal == '':
        logger.warning('handleSms: gagal parsing nominal')
        return
    
    sn = xltunai.getSNFromMessage(sms.text)
    if sn == '':
        logger.warning('handleSms: gagal parsing SN')
    
    
    DISABLE_SEM = 0
    
    response_code = xltunai.getResponseCodeByMessage(sms.text)
    
    request_id = getRequestIdByNominalDestination(nominal, destination)
    if not request_id:
        logger.info('Unknown request id for nominal:{0} destination:{1}'.format(nominal, destination))
        return
        
    pushTopupStatus(request_id, response_code, sms.text)
    try:
        deleteMultipleStoredSms(2)
    except:
        #logger.warning('Failed on delete SMS')
        pass
    

def getRequestIdByNominalDestination(nominal, destination):
    global CHIPINFO
    
    redis_key = sate24.keyByNominalDestination(CHIPINFO, nominal, destination)
    #return redis_client.spop(redis_key)
    return redis_client.rpop(redis_key)
    
def pushTopupStatus(request_id, response_code, _message, dontparsesn = False):
    global BALANCE
    global CHIPINFO
    global LAST_SN
    
    redis_key = sate24.keyByRequestId(CHIPINFO, request_id) + '.response_code'
    if response_code == '00':
        redis_client.set(redis_key, response_code)
    else:
        redis_response_code = redis_client.get(redis_key)
        if redis_response_code == '00':
            logger.info('Ignoring message from success trx ')
            return
        
    message = "{0} -- Prev balance: {1}".format(_message, BALANCE)
    
    timestamp = strftime('%Y%m%d%H%M%S')
    
    if response_code == '00' and not dontparsesn:
        sn = xltunai.getSNFromMessage(message)
        LAST_SN = sn
        message = 'SN={0};{1}'.format(sn, message)
        
    push_message = CHIPINFO + '$' + message
    
    payload = {
        'trans_id': request_id,
        'trans_date': timestamp,
        'resp_code': response_code,
        'ussd_msg': push_message
    }
    
    url = AAA_HOST + '/topup'
    
    try:
        logger.info('Sending topup status to AAA')
        logger.info(payload)
        r = requests.get(url, params=payload)
    except:
        logger.warning('Error sending topup status to AAA')
    
    try:
        # publish echi topup report ke redis
        echi_key = 'echi.topup_report.gw:' + BASE_CHIPINFO
        
        redis_client.publish(echi_key + '.json', json.dumps(payload))
        
        echi_message = timestamp + ' - ' + response_code + ' - ' + message
        redis_client.publish(echi_key + '.message', echi_message)
    except:
        logger.warning('Error publishing topup report to redis')
        
def getIsDisableRedisKey():
    return CHIPINFO + '.pulldisable'
    
def isPullEnable():
    redis_key = getIsDisableRedisKey()
    result = 'FALSE'
    
    try:
        result = redis_client.get(redis_key)
        redis_client.expire(redis_key, REDIS_DISABLE_PULL_TTL)
    except:
        return False
        
    if not result:
        return True
    
    return result == 'FALSE'

def enablePull():
    logger.info('Enabling Pull on products {0}'.format(PRODUCTS))
    redis_key = getIsDisableRedisKey()
    try:
        redis_client.set(redis_key, 'FALSE')
        redis_client.expire(redis_key, REDIS_DISABLE_PULL_TTL)
    except:
        return

def disablePull():
    global DISABLE_SEM
    global DISABLE_SEM_ON_TRX
    
    logger.info('Disabling Pull')
    redis_key = getIsDisableRedisKey()
    try:
        redis_client.set(redis_key, 'TRUE')
        redis_client.expire(redis_key, REDIS_DISABLE_PULL_TTL)
    except:
        return
            
def adviceLastSN(requestId, modem):
    global DISABLE_SEM
    global LAST_SN
    global TOPUP_USSD_TIMEOUT
    
    if not LAST_SN:
        message = 'Tidak ada trx sebelumnya untuk perbandingan SN. Silahkan bandingkan prev balance dengan trx berikutnya di chip yang sama'
        pushTopupStatus(requestId, '68', message)
        DISABLE_SEM = 0
        return
    
    ussd_command = xltunai.getHistoryUSSDCommand()
    
    logger.info(u'Executing advice {0}'.format(ussd_command))
    try:
        response = modem.sendUssd(ussd_command, TOPUP_USSD_TIMEOUT) 
        
        message = response.message.strip()
        logger.info(u'USSD response: {0}'.format(message))
        
        lastSNFromHistory = xltunai.getLastSNFromHistoryMessage(message)
        lastTrxTypeFromHistory = xltunai.getLastTrxTypeFromMessage(message)
        
        if response.sessionActive:
            response.cancel()
        
        if not lastSNFromHistory:
            if DISABLE_SEM:
                logger.info('Failed to get last sn from history, retrying in 15 secs')
                sleep(15)
                adviceLastSN(requestid, modem)
                
        elif lastTrxTypeFromHistory and lastTrxTypeFromHistory.find('RELOAD') < 0:
            topupMessage = "Topup gagal berdasarkan advice. Trx terakhir adalah P2P Transfer."
            pushTopupStatus(requestId, '40', topupMessage)
            DISABLE_SEM = 0
                
        elif lastSNFromHistory == LAST_SN:
            topupMessage = "Topup gagal berdasarkan advice. {0} = {1}. {2}".format(lastSNFromHistory, LAST_SN, message)
            pushTopupStatus(requestId, '40', topupMessage)
            DISABLE_SEM = 0
            
        else:
            topupMessage = "SN={0}; Topup berhasil berdasarkan advice. {1}".format(lastSNFromHistory, message)
            LAST_SN = lastSNFromHistory
            pushTopupStatus(requestId, '00', topupMessage, True)
            DISABLE_SEM = 0
        
    except:
        message = "USSD Error: Something wrong when executing USSD advice. Signal strength: {0}".format(modem.signalStrength)
        logger.warning(message)
        pushTopupStatus(requestId, '68', message)
        
        sleep(60)
        adviceLastSN(requestId, modem)
        #waitForResultSmsAfterUssdError(modem, task['requestId'], message)
        return

    
    
def topupTask(task, modem):
    global LAST_REQUEST_ID
    global LAST_PRODUCT
    
    if not task:
        return
    
    if task['status'] != 'OK':
        return
        
    LAST_REQUEST_ID = task['requestId']
    LAST_PRODUCT = task['product'].upper()
    
    redis_key = sate24.keyByRequestId(CHIPINFO, task['requestId'])
    redis_client.set(redis_key, task)
    redis_client.expire(redis_key, REDIS_TTL)
    
    nominal = xltunai.getNominalFromProduct(task['product'])
    intl_destination = xltunai.toInternationalNumber(task['destination'])
    
    redis_key = sate24.keyByNominalDestination(CHIPINFO, nominal, intl_destination)
    #redis_client.sadd(redis_key, task['requestId'])
    try:
        redis_client.rpush(redis_key, task['requestId'])
    except:
        redis_client.delete(redis_key)
        redis_client.rpush(redis_key, task['requestId'])
    finally:
        redis_client.expire(redis_key, REDIS_TTL)
    
    #pushTopupStatus(task['requestId'], '68', 'Siap mengirimkan trx ke operator')

    message = 'Executing USSD'
    response_code = '68'
    
    ussd_command = xltunai.getTopupUSSDCommand(task['destination'], task['product'], PIN_TRX)
    
    logger.info(u'Executing {0}'.format(ussd_command))
    try:
        response = modem.sendUssd(ussd_command, TOPUP_USSD_TIMEOUT) 
        
        message = response.message.strip()
        logger.info(u'USSD response: {0}'.format(message))
        
        response_code = xltunai.getResponseCodeByUSSDResponse(message)
        
        if response.sessionActive:
            response.cancel()
            
    except TimeoutException:
        message = "USSD Error: Timeout when executing USSD topup. Signal strength: {0}".format(modem.signalStrength)
        logger.warning(message)
        pushTopupStatus(task['requestId'], '68', message)
        #waitForResultSmsAfterUssdError(modem, task['requestId'], message)
        sleep(15)
        adviceLastSN(task['requestId'], modem)
        return
    except:
        message = "USSD Error: Something wrong when executing USSD topup. Signal strength: {0}".format(modem.signalStrength)
        logger.warning(message)
        pushTopupStatus(task['requestId'], '68', message)
        #waitForResultSmsAfterUssdError(modem, task['requestId'], message)
        sleep(15)
        adviceLastSN(task['requestId'], modem)
        return

    DISABLE_SEM = DISABLE_SEM_ON_TRX
    pushTopupStatus(task['requestId'], response_code, message)
    
def waitForResultSmsAfterUssdError(modem, request_id, last_error_message = ''):
    logger.info('Sleeping for {0} seconds after an USSD error'.format(SLEEP_AFTER_USSD_ERROR))
    sleep(SLEEP_AFTER_USSD_ERROR)
    
    redis_key = sate24.keyByRequestId(CHIPINFO, request_id) + '.response_code'
    response_code = redis_client.get(redis_key)

    if response_code == '68' or response_code == None:
        
        prev_balance = BALANCE
        current_balance = checkBalance(modem, do_not_update = True, return_result = True)
        
        if current_balance == 0:
            return 
            
        if current_balance < prev_balance:
            delta = prev_balance - current_balance
            pushTopupStatus(
                request_id, 
                '00', 
                'Transaksi dianggap sukses karena saldo berkurang {0}. Last error message: {1}'.format(
                    delta, 
                    last_error_message
                )
            )
        else:
            pushTopupStatus(
                request_id, 
                '40', 
                'Transaksi dianggap gagal karena saldo tidak berkurang. Last error message: {0}'.format(last_error_message)
            )

def _saveBalanceToRedis(balance_key, balance_value):
    try:
        redis_client.set(balance_key, balance_value)
        redis_client.expire(balance_key, 3600*24*60)
    except:
        logger.warning('Failed to save balance to redis')
    
def saveBalanceToRedis(balance):
    global BASE_CHIPINFO
    global CHIPINFO
    global MSISDN
    
    try:
        balance = int(balance)
    except ValueError:
        return
    
    
    data = {
        'balance.gw:' + BASE_CHIPINFO: balance,
        'balance.gw:' + CHIPINFO: balance,
        'balance.gw:' + MSISDN: balance
    }
    
    redis_pipe = redis_client.pipeline()
    
    try:
        redis_pipe.mset(data)
        
        for k in data:
            redis_pipe.expire(k, 3600 * 24 * 60)
            
    except:
        logger.warning('Failed to save balance to redis')
    finally:
        redis_pipe.execute()
        
def saveSignalStrengthToRedis(value):
    global BASE_CHIPINFO
    global CHIPINFO
    global MSISDN
    
    try:
        redis_pipe = redis_client.pipeline()
        redis_client.set('balance.gw:' + BASE_CHIPINFO + '.signal', value)
        redis_client.expire('balance.gw:' + BASE_CHIPINFO + '.signal', 15 * 60)
    except:
        logger.warning('Failed to save signal strength to redis')

    
def pull(modem):
    global PRODUCTS
    global PULL_COUNT
    global BALANCE
    global DISABLE_SEM
    global MIN_SIGNAL_STRENGTH
    
    if not PRODUCTS:
        sleep(60)
        return
        
    signalStrength = modem.signalStrength
    if signalStrength < MIN_SIGNAL_STRENGTH:
        return
    
    pull_per_minute = 60 / PULL_INTERVAL
    
    PULL_COUNT += 1
    if PULL_COUNT % (5 * pull_per_minute) == 0:
        checkBalance(modem)
        sleep(15)
        PULL_COUNT = 0
    
    if DISABLE_SEM > 0:
        logger.info('SEMAPHORE is still on, delaying pull')
        DISABLE_SEM -= 1
        return
    
    if not isPullEnable():
        sleep(60)
        return
    
    r = None
    url = AAA_HOST + '/pull?city=' + CITY + '&nom=' + PRODUCTS
    try:
        r = requests.get(url)
    except:
        logger.warning("Error requesting to AAA")
        return

    if not r:
        return
        
    task = sate24.parsePullMessage(r.text)
    
    if task['status'] == 'OK':
        
        logger.info('PULL ' + url + ' => ' + r.text)
        publishAAATaskToRedis(task)
        
        if not checkBalance(modem) or BALANCE == 0:
            pushTopupStatus(task['requestId'], '91', 'Transaksi dibatalkan karena gagal check balance')
            sleep(SLEEP_BETWEEN_BALANCE_N_TOPUP)
            return
            
        sleep(SLEEP_BETWEEN_BALANCE_N_TOPUP)
        
        topupTask(task, modem)
        sleep(SLEEP_AFTER_TOPUP)
        
def publishAAATaskToRedis(task):
    try:
        key = 'kimochi.aaa_pull.gw:' + BASE_CHIPINFO
        plain_text = task['timestamp'] + ' - ' + task['member'] + ' isi ' + task['product'] + ' ke ' + task['destination'];
        
        redis_client.publish(key + '.json', json.dumps(task))
        redis_client.publish(key + '.text', plain_text);
    except:
        logger.warning('Error publishing kimochi')
        
def  publishMessageToRedis():
    pass

def pullLoop(modem):
    global TERMINATING
    
    while True:
        signalStrength = modem.signalStrength
        saveSignalStrengthToRedis(signalStrength)
    
        if TERMINATING:
            logger.info('Terminated by request signal')
            sys.exit(0)
        
        pull(modem)
        
        sleep(PULL_INTERVAL)

def checkSignal(modem):
    logger.info('Signal: {0}'.format(modem.signalStrength))
    try:
        redis_client.set(CHIPINFO + '.signal', modem.signalStrength)
        redis_client.expire(CHIPINFO + '.signal', 3600*24)
    except:
        logger.warning("Can not save signal strength to redis")

def checkAccount(modem):
    try:        
        ussd_string = '*123*120*8*3#'
        response = modem.sendUssd(ussd_string, 30)
        logger.info('Account Info: {0}'.format(response.message))
        
        if response.sessionActive:
            response.cancel()
    except:
        logger.warning('Error when requesting account info')
        return False
    
def checkBalance(modem, do_not_update = False, return_result = False):
    global BALANCE
    
    _BALANCE = 0
    
    try:
        
        ussd_string = '*123*120#'
        response = modem.sendUssd(ussd_string, 30) 
        _BALANCE = xltunai.getBalanceFromUSSDResponse(response.message)
        
        if not do_not_update:
            BALANCE = _BALANCE
            saveBalanceToRedis(BALANCE)
            
        logger.info('Balance: {0}'.format(_BALANCE))
        if response.sessionActive:
            response.cancel()
            
        if _BALANCE != 0 and _BALANCE < MIN_BALANCE:
            logger.info('Disabling pull, balance {0} < {1}'.format(_BALANCE, MIN_BALANCE))
            disablePull()
            
    except:
        logger.warning('Error when requesting BALANCE by USSD')
        if return_result:
            return 0
        else:
            return False
    
    try:
        redis_client.set(CHIPINFO + '.balance', BALANCE)
    except:
        logger.warning('Failed to save balance to redis')
    
    if return_result:
        return _BALANCE
    else:
        return True

def getPulsaInfo(modem):
    global PULSA
    global MASA_AKTIF
    
    try:
        ussd_string = "*123#"
        response = modem.sendUssd(ussd_string)
        
        message = response.message.strip()
        logger.info('PULSA: {0}'.format(message))
        if response.sessionActive:
            response.cancel()
        
        PULSA = xltunai.getPulsaFromUssdResponseMessage(message)
        MASA_AKTIF =xltunai.getMasaAktifFromUssdResponseMessage(message)
        logger.info('PULSA: {0} -- MASA AKTIF: {1}'.format(PULSA, MASA_AKTIF))
        
        return message
        
    except:
        logger.warning('Error when requesting pulsa info by USSD')
        return ''
    
def getSIMCardInfo(modem):
    try:
        ussd_string = xltunai.getSIMCardInfoUSSDCommand()
        response = modem.sendUssd(ussd_string)
        
        message = response.message.strip()
        logger.info('SIM INFO: {0}'.format(message))
        if response.sessionActive:
            response.cancel()
        
        return message
        
    except:
        logger.warning('Error when requesting SIM card info by USSD')
        return ''

def updateChipInfo(msisdn):
    global BASE_CHIPINFO
    global CHIPINFO
    global MSISDN
    
    MSISDN = msisdn
    if CHIPINFO.find(msisdn) == -1:
        CHIPINFO = BASE_CHIPINFO + '-' + msisdn
        
    logger.info('CHIPINFO: {0}'.format(CHIPINFO))

def removeOverLimitProducts():
    global PRODUCTS
    
    if os.path.isfile('XL5.deny'):
        sate24.removeProduct(PRODUCTS, 'XL5')
        
    if os.path.isfile('XL10.deny'):
        sate24.removeProduct(PRODUCTS, 'XL10')
    
    if os.path.isfile('ALL.deny'):
        sate24.removeProduct(PRODUCTS, 'XL5')
        sate24.removeProduct(PRODUCTS, 'XL10')
        
        
def main():
    global logger
    
    log_format = '%(asctime)s %(levelname)s: %(message)s'
    
    logging.basicConfig(format=log_format, level=logging.INFO)
    logger = logging.getLogger(__name__)
    
    logger_formatter = logging.Formatter(log_format)
    logger_handler = TimedRotatingFileHandler('logs/log', when='midnight')
    logger_handler.setFormatter(logger_formatter)
    logger.addHandler(logger_handler)
    
    requests_logger = logging.getLogger('requests')
    requests_logger.setLevel(logging.WARNING)
    
    logger.info('Initializing modem...')
    
    modem = GsmModem(PORT, BAUDRATE, smsReceivedCallbackFunc=handleSms)
    modem.smsTextMode = True 
    modem.connect(PIN)
    
    logger.info('Waiting for network coverage...')
    modem.waitForNetworkCoverage(10)
    
    logger.info('Modem ready')
    
    getPulsaInfo(modem)
    sleep(2)
    
    simcard_info = getSIMCardInfo(modem)
    msisdn = xltunai.getMSISDNFromSIMCardInfo(simcard_info)
    
    if not msisdn:
        logger.warning('Gagal mendapatkan msisdn, terminating')
        sleep(5)
        sys.exit(2)
        
    imsi = modem.imsi
    logger.info('MSISDN: {0} -- IMSI: {1}'.format(msisdn, imsi))
    
    updateChipInfo(msisdn)
    saveSimCardInfo(imsi, msisdn)
    
    sleep(2)
    
    saveSignalStrengthToRedis(modem.signalStrength)
    
    logger.info('Process stored SMS')
    try:
        modem.processStoredSms()
    except:
        logger.warning('Failed on Process stored SMS')
    
    logger.info('Delete stored SMS')
    try:
        modem.deleteMultipleStoredSms()
    except:
        #logger.warning('Failed on delete SMS')
        pass
    
    sleep(2)
    
    checkAccount(modem)
    sleep(5)
    
    removeOverLimitProducts()
    
    enablePull()
    
    checkBalance(modem)
    sleep(SLEEP_BETWEEN_BALANCE_N_TOPUP)
    
    pullLoop(modem)
    logger.info('Waiting for SMS message...')    
    try:    
        modem.rxThread.join(2**31) # Specify a (huge) timeout so that it essentially blocks indefinitely, but still receives CTRL+C interrupt signal
    finally:
        modem.close();

def saveSimCardInfo(imsi, msisdn):
    logger.info('Save sim card info to redis')
    
    data = {
        'gw': BASE_CHIPINFO,
        'imsi': imsi,
        'msisdn': msisdn,
        'pulsa': PULSA,
        'masa_aktif': MASA_AKTIF
    }
    
    json_data = json.dumps(data)
    
    with open('simcardinfo.txt', 'w') as f:
        f.write(json_data)
    f.closed

    
    map_data = {
        BASE_CHIPINFO + '.simcardinfo': json_data,
        'simcardinfo.gw:' + BASE_CHIPINFO: json_data,
        'simcardinfo.imsi:' + imsi: json_data,
        'simcardinfo.msisdn:' + msisdn: json_data
    }
    
    logger.info(map_data)

    redis_pipe = redis_client.pipeline()

    try:
        redis_pipe.mset(map_data)
        
        for k in data:
            redis_pipe.expire(k, 3600 * 24 * 60)
            
    except:
        logger.warning('Failed to save simcardinfo to redis')
    finally:
        redis_pipe.execute()
    
if __name__ == '__main__':
    pidfile = open('pid.txt', 'w')
    pidfile.write(str(getpid()))
    pidfile.close()
    
    # trap CTRL-C
    signal.signal(signal.SIGINT, signalHandler)
    # trap supervisor stop
    signal.signal(signal.SIGTERM, signalHandler)
    
    main()
