#!/usr/bin/python
# LICENSE: GPL2
# (c) 2014 Kamil Wartanowicz

import inspect
import logging
import os
import sys
import traceback

import sim_xml
import sim_soft
import sat_ctrl

from util import types_g
from util import types

class SoftCard(object):
    def __init__(self, simType=types.TYPE_USIM, file=None):
        self.simType = simType
        if not file:
            file = os.path.dirname(__file__) + "/sim_backup.xml"
        self.file = file

    def connect(self):
        self.init()

    def init(self):
        #TODO: check if already opened, then close might be needed
        self.simXml = sim_xml.SimXml(self.file)
        self.satCtrl = sat_ctrl.SatCtrl(types.TYPE_SIM, self.simXml)
        self.simHandler = sim_soft.SimHandler(self.simXml, self.satCtrl, self.simType)

    def getATR(self):
        self.init()
        return self.simXml.getAtr()

    def transmit(self, apdu):
        try:
            return self._transmit(apdu)
        except:
            exc_type, exc_value, exc_traceback = sys.exc_info()
            stackTrace = "".join(traceback.format_tb(exc_traceback))
            error = exc_value
            raise Exception("%s\nError: %s" %(stackTrace, error))

    def _transmit(self, apdu):
        sw = 0x9000
        data = []

        channel = types.channel(apdu)
        if channel > types.MAX_LOGICAL_CHANNELS - 1:
            sw = types_g.sw.CLASS_NOT_SUPPORTED
            return data, sw

        # Check if the channel is open
        if self.simHandler.isChannelOpen(channel) == False:
            sw = types_g.sw.LOGICAL_CHANNEL_NOT_SUPPORTED
            return data, sw
        # TODO: open channel through SELECT command
        self.simHandler.setChannel(channel)

        ins = types.insName(apdu)
        if ins == "SELECT_FILE":
            data, sw = self.simHandler.select(apdu)
        elif ins == "GET_RESPONSE":
            data, sw = self.simHandler.getResponse(apdu)
        elif ins == "READ_BINARY":
            data, sw = self.simHandler.readBinary(apdu)
        elif ins == "VERIFY_PIN":
            data, sw = self.simHandler.verifyPin(apdu)
        elif ins == "UNBLOCK_PIN":
            data, sw = self.simHandler.unblockPin(apdu)
        elif ins == "CHANGE_PIN":
            data, sw = self.simHandler.changePin(apdu)
        elif ins == "ENABLE_PIN":
            data, sw = self.simHandler.enablePin(apdu)
        elif ins == "DISABLE_PIN":
            data, sw = self.simHandler.disablePin(apdu)
        elif ins == "STATUS":
            data, sw = self.simHandler.status(apdu)
        elif ins == "SEARCH_RECORD":
            data, sw = self.simHandler.searchRecord(apdu)
        elif ins == "READ_RECORD":
            data, sw = self.simHandler.readRecord(apdu)
        elif ins == "UPDATE_BINARY":
            data, sw = self.simHandler.updateBinary(apdu)
        elif ins == "UPDATE_RECORD":
            data, sw = self.simHandler.updateRecord(apdu)
        elif ins == "DEACTIVATE_FILE":
            data, sw = self.simHandler.deactivateFile(apdu)
        elif ins == "ACTIVATE_FILE":
            data, sw = self.simHandler.activateFile(apdu)
        elif ins == "TERMINAL_PROFILE":
            data, sw = self.satCtrl.terminalProfile(apdu)
        elif ins == "TERMINAL_RESPONSE":
            data, sw = self.satCtrl.terminalResponse(apdu)
        elif ins == "FETCH":
            data, sw = self.satCtrl.fetch(apdu)
        elif ins == "ENVELOPE":
            data, sw = self.satCtrl.envelope(apdu)
        elif ins == "MANAGE_CHANNEL":
            data, sw = self.simHandler.manageChannel(apdu)
        elif ins == 'INTERNAL_AUTHENTICATE':
            data, sw = self.simHandler.authenticate(apdu)
        elif ins == "CREATE_FILE":
            data, sw = self.simHandler.createFile(apdu)
        elif ins == "DELETE_FILE":
            data, sw = self.simHandler.deleteFile(apdu)
        elif ins == "RESIZE_FILE":
            data, sw = self.simHandler.resizeFile(apdu)
        elif ins == 'DE':
            data, sw = self.simHandler.unknownInstruction()
        else:
            raise Exception("Invalid instruction: %s" %ins)

        return data, sw

class SoftReader(object):
    def __init__(self, name):
        self.name = name
        self.card = None

    def createConnection(self, type=types.TYPE_SIM):
        self.card = SoftCard(simType=type)
        return self.card

reader1 = SoftReader('Soft SIM reader 0')
reader2 = SoftReader('Soft SIM reader 1')

READERS = [reader1, reader2]

class Reader(object):
    def __init__(self):
        self.index = None
        self.reader = None
        self.card = None

class SimSoftCtrl(object):
    def __init__(self, type=types.TYPE_USIM, logLevel=logging.INFO):
        dir = os.path.dirname(__file__)
        resultFile = dir + "/../sim_soft.log"
        FORMATTER = logging.Formatter(fmt='%(asctime)s %(message)s', datefmt='%H:%M:%S')
        fileHndl = logging.FileHandler(resultFile, mode='w')
        fileHndl.setFormatter(FORMATTER)
        fileHndl.setLevel(logLevel)
        logger = logging.getLogger("sim_soft")
        #dont't propagate to root logger
        logger.propagate=False
        logger.handlers = []
        logger.setLevel(logLevel)
        logger.addHandler(fileHndl)
        self.logging = logger
        self.readers = []
        self.simType = type

    def close(self):
        pass

    def getReader(self, index):
        for reader in self.readers:
            if reader.index == index:
                return reader
        return None

    def getReaderName(self, index):
        reader = self.getReader(index)
        if not reader:
            return None
        return reader.reader.name

    def getCard(self, index):
        self.checkReader(index)
        return self.getReader(index).card

    def checkReader(self, index):
        if not self.getReader(index):
            raise Exception("Reader with index=%d not created" %index)

    def checkCard(self, index):
        if not self.getCard(index):
            raise Exception("Card for reader with index=%d not created" %index)

    #exported methods
    def listReaders(self):
        self.logFunctionAndArgs()
        readers = READERS
        readersStr = []
        for reader in readers:
            readersStr.append(reader.name)
        self.logReturnVal(readersStr=readersStr)
        return readersStr

    def addReader(self, index):
        self.logFunctionAndArgs()
        self.readers.append(Reader())
        readersConnected = READERS
        if not len(readersConnected):
            raise Exception("No reader connected")
        if index >= len(readersConnected):
            raise Exception("Reader id:%d not connected, number of connected readers:%d"
                            %(index, len(readersConnected)))
        self.readers[-1].index = index
        self.readers[-1].reader = readersConnected[index]
        self.logReturnVal()
        return None

    def removeReader(self, index):
        self.logFunctionAndArgs()
        self.checkReader(index)
        for reader in self.readers:
            if reader.index == index:
                del reader
        self.logReturnVal()
        return None

    def removeAllReaders(self):
        self.logFunctionAndArgs()
        for reader in self.readers:
            del reader
        self.logReturnVal()
        return None

    def r_createConnection(self, index):
        self.logFunctionAndArgs()
        self.checkReader(index)
        self.getReader(index).card = self.getReader(index).reader.createConnection(type=self.simType)
        return None

    def c_connect(self, index):
        self.logFunctionAndArgs()
        self.checkCard(index)
        self.getCard(index).connect()
        self.logReturnVal()
        return None

    def c_disconnect(self, index):
        self.logFunctionAndArgs()
        self.checkCard(index)
        self.getCard(index).disconnect()
        self.logReturnVal()
        return None

    def c_getATR(self, index):
        self.logFunctionAndArgs()
        self.checkCard(index)
        atr = self.getCard(index).getATR()
        self.logReturnVal(atr=atr)
        return atr

    def c_transmit(self, apdu, index):
        self.logFunctionAndArgs()
        self.checkCard(index)
        data, sw = self.getCard(index).transmit(apdu)
        sw1 = sw>>8
        sw2 = sw & 0x00FF
        self.logReturnVal(data=data, sw1=sw1, sw2=sw2)
        return data, sw1, sw2

    def logFunctionAndArgs(self):
        frame = inspect.getouterframes(inspect.currentframe())[1][0]
        args, _, _, values = inspect.getargvalues(frame)
        frameinfo = inspect.getframeinfo(frame)
        functionName=inspect.getframeinfo(frame)[2]
        output = ""
        for arg in args[1:]: #[1:] skip the first argument 'self'
            value = values[arg]
            if isinstance(value, str):
                #add apostrophes for string values
                value = "\'"+value+"\'"
            elif isinstance(value, int):
                value = ''.join('%02X' % value)
            else:
                newValue = ""
                for i in value:
                    if isinstance(i, int):
                        newValue += '%02X' % i
                    else:
                        newValue += str(i)
                value = newValue
            output += arg + '=' + value
            if arg != args[-1]:
                #add comma if not the last element
                output +=','
        #do not print "\n' as a new line
        output = output.replace("\n","\\n")
        self.logging.info("--> "+functionName+'('+output+')')

    def logReturnVal(self, **kwargs):
        output = ""
        for key, value in kwargs.iteritems():
            if isinstance(value, str):
                #add apostrophes for string values
                value = "\'"+value+"\'"
            elif isinstance(value, int):
                value = ''.join('%02X' % value)
            else:
                newValue = ""
                for i in value:
                    if isinstance(i, int):
                        newValue += '%02X' % i
                    else:
                        newValue += str(i)
                value = newValue
            output += key + ':' + value + ', '
        output = output.rstrip(', ') #remove last comma and space
        self.logging.info("<-- "+output+'\n')