//
//  BcaFlazzApi.swift
//  Emoney Info
//
//  Created by Wira Irawan on 27/07/24.
//

import Foundation
import CoreNFC

public class BcaFlazzApi : UnifiedNfcApi {
    var emoney : Emoney = Emoney()
    var riwayatList: [RiwayatCard] = []
    var start = 0
    var finishV2 = 5
    var finish2V202 = 256
    var finishV1 = 16
    var mapv1 : String = ""
    var mapv2 : String = ""
    
    public override init() {}
    
    public func checkFlazzCard(){
        apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BCA_APDU01, completionHandler: {response in
            if (response.sw1 == 0x90 && response.sw2 == 0x00){
                self.emoney.setCardLabel("BCA Flazz")
                self.getCardNumber()
            } else {
                self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
            }
        })
    }
    
    private func getCardNumber(){
        apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BCA_APDU02, completionHandler: {response in
            if (response.sw1 == 0x90 && response.sw2 == 0x00){
                let raw = String(data: response.getData(), encoding: .isoLatin1)
                let start = raw!.firstIndex(of: ";")?.utf16Offset(in: raw!)
                let end = raw!.firstIndex(of: "=")?.utf16Offset(in: raw!)
                self.emoney.setCardNumber(String((raw?.subString(from: (start! + 1), to: end!))!))
                self.getBalance()
            } else {
                self.apduRunner.invalidateSession()
            }
        })
    }
    
    private func getBalance(){
        apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BCA_APDU03, completionHandler: {response in
            if (response.sw1 == 0x90 && response.sw2 == 0x00){
                let raw = response.getData().hexEncodedString()
                let balance = raw.subString(from: 2, to: 8)
                self.emoney.setBalance(balance.hex2decimal())
                self.checkLog()
            } else {
                self.apduRunner.invalidateSession(msg: "readFailed".localizeString(string: self.langCode!))
            }
        })
    }
    
    private func checkLog(){
        apduRunner.exchangeApdu(apduCommand: EmoneyApduCommands.BCA_APDU04, completionHandler: {response in
            if (response.sw1 == 0x90 && response.sw2 == 0x00){
                debugLog("log v2")
//                self.updateScreen()
                self.getLogV2step01(index: self.start)
            } else {
                debugLog("log v1")
//                self.updateScreen()
                self.getLogV1step01(index: self.start)
            }
        })
    }
    
    private func getLogV2step01(index : Int){
        debugLog("log getLogV2step01")
        //00 B0 85 00 78
        let st = index*60
        let hex = String(st, radix: 16)
        let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB0, p1Parameter : 0x85, p2Parameter : hex.hex2byte().bytes.first!, data : Data(), expectedResponseLength : 120)
        apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
            if (response.sw1 == 0x90 && response.sw2 == 0x00){
                debugLog("log data", response.getData().hexEncodedString())
                
                
                self.mapv1.append(response.getData().hexEncodedString())
                self.start+=1
                if (self.start < (self.finishV2)){
                    self.getLogV2step01(index: self.start)
                } else {
                    //mapping first
                    self.parseLogV201()
                    self.start = 0
                    self.getLogV2step02(index: self.start)
                }
            } else {
                self.parseLogV201()
                self.start = 0
                self.getLogV2step02(index: self.start)
            }
        })
    }
    
    private func getLogV1step01(index : Int){
        debugLog("log getLogV1step01")
        //00 b0 84 00 3c
        let st = index*15
        let hex = String(st, radix: 16)
        let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB0, p1Parameter : 0x84, p2Parameter : hex.hex2byte().bytes.first!, data : Data(), expectedResponseLength : 60)
        apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
            if (response.sw1 == 0x90 && response.sw2 == 0x00){
                self.mapv1.append(response.getData().hexEncodedString())
                self.start+=1
                if (self.start < (self.finishV1)){
                    self.getLogV1step01(index: self.start)
                } else {
                    self.start = 0
                    self.getLogV1step02(index: self.start)
                }
            } else {
                self.start = 0
                self.getLogV1step02(index: self.start)
            }
        })
    }
    
    private func getLogV1step02(index : Int){
        debugLog("log getLogV1step02")
        //00b085003c
        let st = index*15
        let hex = String(st, radix: 16)
        let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB0, p1Parameter : 0x85, p2Parameter : hex.hex2byte().bytes.first!, data : Data(), expectedResponseLength : 60)
        apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
            if (response.sw1 == 0x90 && response.sw2 == 0x00){
                self.mapv1.append(response.getData().hexEncodedString())
                self.start+=1
                if (self.start < (self.finishV1)){
                    self.getLogV1step02(index: self.start)
                } else {
                    self.parseLogV101()
                    self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })

                    if (self.riwayatList.count > 0){
                        self.emoney.setRiwayatList(self.riwayatList)
                        self.emoney.setTampilRiwayat(true)
                    }
                    self.updateScreen()
                    self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
                    self.apduRunner.invalidateSession()
                }
            } else {
                self.parseLogV101()
                self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })

                if (self.riwayatList.count > 0){
                    self.emoney.setRiwayatList(self.riwayatList)
                    self.emoney.setTampilRiwayat(true)
                }
                self.updateScreen()
                self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
                self.apduRunner.invalidateSession()
            }
        })
    }
    
    private func getLogV2step02(index : Int){
        debugLog("log getLogV2step02")
        //00b08400f0
        let st = index*60
        let hex = String(st, radix: 16)
        let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB0, p1Parameter : 0x84, p2Parameter : hex.hex2byte().bytes.first!, data : Data(), expectedResponseLength : 240)
        apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
            if (response.sw1 == 0x90 && response.sw2 == 0x00){
                self.mapv1.append(response.getData().hexEncodedString())
                self.start+=1
                if (self.start < (self.finishV2)){
                    self.getLogV2step02(index: self.start)
                } else {
                    self.start = 0
                    self.getLogV2step03()
                }
            } else {
                self.start = 0
                self.getLogV2step03()
            }
        })
    }
    
    private func getLogV2step03(){
        debugLog("log getLogV2step03")
        //00 84 00 00 08
        let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0x84, p1Parameter : 0x00, p2Parameter : 0x00, data : Data(), expectedResponseLength : 8)
        apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
            if (response.sw1 == 0x90 && response.sw2 == 0x00){
                self.getLogV2step04(data: response.getData())
            } else {
                self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
                self.apduRunner.invalidateSession()
            }
        })
    }
    
    private func getLogV2step04(data: Data){
        debugLog("log getLogV2step04")
        //90 32 03 00 0A 0801 0000000000000000 29
        let send = "0801" + data.hexEncodedString()
        let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0x32, p1Parameter : 0x03, p2Parameter : 0x00, data : Data(_: send.hex2byte()), expectedResponseLength : 41)
        apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
            if (response.sw1 == 0x90 && response.sw2 == 0x00){
                self.getLogV2step05(index: self.start)
            } else {
                self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
                self.apduRunner.invalidateSession()
            }
        })
    }
    
    private func getLogV2step05(index : Int){
        debugLog("log getLogV2step05")
        //00 B0 89 00 40
        let st = index
        let hex = String(st, radix: 16)
        let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x00, instructionCode : 0xB0, p1Parameter : 0x89, p2Parameter : hex.hex2byte().bytes.first!, data : Data(), expectedResponseLength : 64)
        apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
            if (response.sw1 == 0x90 && response.sw2 == 0x00){
                self.mapv2.append(response.getData().hexEncodedString())
                self.start+=1
                if (self.start < (self.finish2V202)){
                    self.getLogV2step05(index: self.start)
                } else {
                    self.start = 0
                    self.getLogV2step06(index: self.start)
                }
            } else {
                self.start = 0
                self.getLogV2step06(index: self.start)
            }
        })
    }
    
    private func getLogV2step06(index : Int){
        debugLog("log getLogV2step06")
        //90 32 03 00 01 00 20
        let st = index
        let hex = String(st, radix: 16)
        let BCAV2_LOG = NFCISO7816APDU(instructionClass : 0x90, instructionCode : 0x32, p1Parameter : 0x03, p2Parameter : 0x00, data : Data(_: hex.hex2byte()), expectedResponseLength : 32)
        apduRunner.exchangeApdu(apduCommand: BCAV2_LOG, completionHandler: {response in
            if (response.sw1 == 0x90 && response.sw2 == 0x00){
                self.mapv2.append(response.getData().hexEncodedString())
                self.start+=1
                if (self.start < (self.finish2V202)){
                    self.getLogV2step06(index: self.start)
                } else {
                    //mapping the data
                    self.parseLogV202()
                    self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })
                    
                    if (self.riwayatList.count > 0){
                        self.emoney.setRiwayatList(self.riwayatList)
                        self.emoney.setTampilRiwayat(true)
                    }
                    self.updateScreen()
                    self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
                    self.apduRunner.invalidateSession()
                }
            } else {
                //mapping the data
                self.parseLogV202()
                self.riwayatList = self.riwayatList.sorted(by: { $0.getTransationTime()?.compare($1.getTransationTime()!) == .orderedDescending })

                if (self.riwayatList.count > 0){
                    self.emoney.setRiwayatList(self.riwayatList)
                    self.emoney.setTampilRiwayat(true)
                }
                self.updateScreen()
                self.apduRunner.sessionEx?.alertMessage = "readFinish".localizeString(string: self.langCode!)
                self.apduRunner.invalidateSession()
            }
        })
    }
    
    private func parseLogV101(){
        debugLog("log parseLogV101")
        let logs = self.mapv1.trimmingCharacters(in: .whitespacesAndNewlines)
        if (logs.count % 120 != 0){
            return
        }
        let total = logs.count/120
        for i in 0..<total {
            let start = i*120
            let end = start + 120
            let data = logs.subString(from: start, to: end)
            let riwayat = RiwayatCard()
            let location = data.subString(from: 60, to: 76).hex2byte()
            let locationId = String(data: location, encoding: .utf8)!
            riwayat.setLocationId(locationId)
//            riwayat.setLocationName(data.subString(from: 0, to: 16))
            let amount = data.subString(from: 12, to: 18).hex2decimal()
            riwayat.setAmount(amount)
            let transactionTime = data.subString(from: 76, to: 84).hex2decimal()
            riwayat.setTransactionTime(formatDate(seconds: transactionTime))
            let type = data.subString(from: 0, to: 4).hex2decimal()
            if (type == 1024){
                riwayat.setProsesTipe(1)
                riwayat.setTitle("payment".localizeString(string: self.langCode!))
            } else {
                riwayat.setProsesTipe(0)
                riwayat.setTitle("topup".localizeString(string: self.langCode!))
            }
            
            if (transactionTime > 0){
                riwayatList.append(riwayat)
            }
        }
    }
    
    private func parseLogV201(){
        debugLog("log parseLogV201")
        let logs = self.mapv1.trimmingCharacters(in: .whitespacesAndNewlines)
        if (logs.count % 120 != 0){
            return
        }
        let total = logs.count/120
        for i in 0..<total {
            let start = i*120
            let end = start + 120
            let data = logs.subString(from: start, to: end)
            let riwayat = RiwayatCard()
            let location = data.subString(from: 60, to: 76).hex2byte()
            let locationId = String(data: location, encoding: .utf8)!
            riwayat.setLocationId(locationId)
//            riwayat.setLocationName(data.subString(from: 0, to: 16))
            let amount = data.subString(from: 12, to: 18).hex2decimal()
            riwayat.setAmount(amount)
            let transactionTime = data.subString(from: 76, to: 84).hex2decimal()
            riwayat.setTransactionTime(formatDate(seconds: transactionTime))
            let type = data.subString(from: 0, to: 4).hex2decimal()
            if (type == 1024){
                riwayat.setProsesTipe(1)
                riwayat.setTitle("payment".localizeString(string: self.langCode!))
            } else {
                riwayat.setProsesTipe(0)
                riwayat.setTitle("topup".localizeString(string: self.langCode!))
            }
            
            if (transactionTime > 0){
                riwayatList.append(riwayat)
            }
        }
    }
    
    private func parseLogV202(){
        debugLog("log parseLogV202")
        let logs = self.mapv2.trimmingCharacters(in: .whitespacesAndNewlines)
        if (logs.count % 64 != 0){
            return
        }
        let total = logs.count/64
        for i in 0..<total {
            let start = i*64
            let end = start + 64
            let data = logs.subString(from: start, to: end)
            let riwayat = RiwayatCard()
//            let location = data.subString(from: 28, to: 44).hex2byte()
//            let locationId = String(data: location, encoding: .utf8)!
//            riwayat.setLocationId(locationId)
            let transactionTime = data.subString(from: 8, to: 16).hex2decimal()
            riwayat.setTransactionTime(formatDate(seconds: transactionTime))
            let type = data.subString(from: 0, to: 2).hex2decimal()
            let amount = data.subString(from: 2, to: 8).hex2decimal()
            if (type == 4){
                riwayat.setProsesTipe(1)
                riwayat.setAmount(16777216 - amount)
                riwayat.setTitle("payment".localizeString(string: self.langCode!))
            } else {
                riwayat.setProsesTipe(0)
                riwayat.setAmount(amount)
                riwayat.setTitle("topup".localizeString(string: self.langCode!))
            }
            if (transactionTime > 0){
                riwayatList.append(riwayat)
            }
        }
    }
    
    private func formatDate(seconds : Int) -> Date{
        // Specify date components
        var dateComponents = DateComponents()
        dateComponents.year = 1980
        dateComponents.month = 1
        dateComponents.day = 1
        dateComponents.timeZone = TimeZone(identifier: "Asia/Jakarta")!
        dateComponents.hour = 0
        dateComponents.minute = 0
        dateComponents.second = seconds
        // Create date from components
        let userCalendar = Calendar.current // user calendar
        let someDateTime = userCalendar.date(from: dateComponents)
        return someDateTime!
    }
    
    private func updateScreen(){
        if (self.apduRunner.callback != nil){
            self.apduRunner.callback?.complete(emoney: self.emoney)
        }
    }
    
}
