import Foundation
import CoreNFC

@available(iOS 13.0, *)
extension NFCISO7816APDU {
    func toHexString() -> String {
        let dataFieldInHex = (self.data ?? Data(_ : [])).hexEncodedString()
        return String(format:"%02X %02X %02X %02X", self.instructionClass, self.instructionCode, self.p1Parameter, self.p2Parameter) + " " + dataFieldInHex + String(format:"%02X", self.expectedResponseLength) + " "
    }
}

typealias CompletionHandler = (_ response:ApduResponse) -> Void

@available(iOS 13.0, *)
public class ApduRunner: NSObject, NFCTagReaderSessionDelegate {
    public static let NFC_TAG_CONNECTED_EVENT:String = "nfcTagConnected"
    
    var sessionEx: NFCTagReaderSession?
    var callback: ApduCallback?
    var nfcApi : UnifiedNfcApi?
    
    func setApduCallback(callback : ApduCallback) {
        self.callback = callback
    }
    
    func setUnifiedNfcApi(nfcApi : UnifiedNfcApi) {
        self.nfcApi = nfcApi
    }
    
    func startScan() {
        let langCode = Locale.current.languageCode ?? "en"
        self.sessionEx = NFCTagReaderSession(pollingOption: [.iso14443, .iso18092], delegate: self)
        self.sessionEx?.alertMessage = "scanMessage".localizeString(string: langCode)
        self.sessionEx?.begin()
    }
    

    public func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
        guard self.callback != nil else {
            debugLog("NfcCallback is empty.")
            return
        }
        guard self.sessionEx != nil else {
            return
        }
        guard tags.count > 0 else {
            debugLog("Nfc Tag???.")
            return
        }
        guard let firstTag = tags.first else {
            return
        }
        if case NFCTag.iso7816(_) = firstTag {
            sessionEx?.connect(to: firstTag) { [self] (error: Error?) in
                if let err = error {
                    debugLog("Error connecting to Nfc Tag" + err.localizedDescription)
                    return
                }
                debugLog("Nfc Tag is connected.")
                if let callback = self.callback, let nfcApi = self.nfcApi {
                    callback.connected(unifiedNfcApi: nfcApi)
                }
            }
        } else if case .feliCa(let feliCaTag) = firstTag {
            sessionEx?.connect(to: firstTag) { [self] (error: Error?) in
                if let err = error {
                    debugLog("Error connecting to Nfc Tag" + err.localizedDescription)
                    return
                }
//                felicaTag = feliCaTag
                debugLog("Felica is connected.")
                if let callback = self.callback, let nfcApi = self.nfcApi {
                    debugLog("Felica is connected 2.")
                    callback.felicaConnected(unifiedNfcApi: nfcApi, tag: feliCaTag)
                }
//                self.sendFelicaCommand(tag: feliCaTag, session: sessionEx!)
                
//                let idm = feliCaTag.currentIDm.map { String(format: "%.2hhx", $0) }.joined()
//                let hexString = "0F06" + idm + "010B30018000"
//                print("hex: \(hexString)")
//                if let apduData = hexString.hexToData() {
//                    feliCaTag.sendFeliCaCommand(commandPacket: apduData)
//                }
                
//                if (self.callback != nil){
//                    self.callback!.connected(felicaNfcApi: self.felicaNfcApi!)
//                }
            }
        }
    }
    
    
    func exchangeApdu(apduCommand: NFCISO7816APDU, completionHandler: @escaping CompletionHandler) {
        guard let connectedTag = self.sessionEx?.connectedTag else {
            debugLog("No connected NFC tag available")
            return
        }
        if case let NFCTag.iso7816(nfcTag) = connectedTag {
            nfcTag.sendCommand(apdu: apduCommand) { (response: Data, sw1: UInt8, sw2: UInt8, error: Error?)
                in
                let resp = ApduResponse()
                debugLog("SW1-SW2: " + String(format: "%02X, %02X", sw1, sw2))
                resp.setSw1(_sw1: sw1)
                resp.setSw2(_sw2: sw2)
                resp.setData(_data: response)
                completionHandler(resp)
            }
        }
    }
        
    
    func invalidateSession() {
        sessionEx?.invalidate()
    }
    
    func invalidateSession(msg : String) {
        sessionEx?.invalidate(errorMessage: msg)
    }
    
    public func tagReaderSessionDidBecomeActive(_ session: NFCTagReaderSession) {
        debugLog("Nfc session is active")
    }
    
    public func tagReaderSession(_ session: NFCTagReaderSession, didInvalidateWithError error: Error) {
        debugLog("Error happend: " + error.localizedDescription)
        NotificationCenter.default.post(name: Notification.Name("stopTimer"), object: nil)
        if ((sessionEx?.isReady) != nil){
            self.invalidateSession(msg: error.localizedDescription)
        }
    }
    
    
}
