import Foundation
import AppBundle

private final class CurrencyFormatterEntry {
    let symbol: String
    let thousandsSeparator: String
    let decimalSeparator: String
    let symbolOnLeft: Bool
    let spaceBetweenAmountAndSymbol: Bool
    let decimalDigits: Int
    
    init(symbol: String, thousandsSeparator: String, decimalSeparator: String, symbolOnLeft: Bool, spaceBetweenAmountAndSymbol: Bool, decimalDigits: Int) {
        self.symbol = symbol
        self.thousandsSeparator = thousandsSeparator
        self.decimalSeparator = decimalSeparator
        self.symbolOnLeft = symbolOnLeft
        self.spaceBetweenAmountAndSymbol = spaceBetweenAmountAndSymbol
        self.decimalDigits = decimalDigits
    }
}

private func getCurrencyExp(currency: String) -> Int {
    switch currency {
    case "CLF":
        return 4
    case "BHD", "IQD", "JOD", "KWD", "LYD", "OMR", "TND":
        return 3
    case "BIF", "BYR", "CLP", "CVE", "DJF", "GNF", "ISK", "JPY", "KMF", "KRW", "MGA", "PYG", "RWF", "UGX", "UYI", "VND", "VUV", "XAF", "XOF", "XPF":
        return 0
    case "MRO":
        return 1
    default:
        return 2
    }
}

private func loadCurrencyFormatterEntries() -> [String: CurrencyFormatterEntry] {
    guard let filePath = getAppBundle().path(forResource: "currencies", ofType: "json") else {
        return [:]
    }
    guard let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
        return [:]
    }
    
    guard let object = try? JSONSerialization.jsonObject(with: data, options: []), let dict = object as? [String: AnyObject] else {
        return [:]
    }
    
    var result: [String: CurrencyFormatterEntry] = [:]
    
    for (code, contents) in dict {
        if let contentsDict = contents as? [String: AnyObject] {
            let entry = CurrencyFormatterEntry(
                symbol: contentsDict["symbol"] as! String,
                thousandsSeparator: contentsDict["thousandsSeparator"] as! String,
                decimalSeparator: contentsDict["decimalSeparator"] as! String,
                symbolOnLeft: (contentsDict["symbolOnLeft"] as! NSNumber).boolValue,
                spaceBetweenAmountAndSymbol: (contentsDict["spaceBetweenAmountAndSymbol"] as! NSNumber).boolValue,
                decimalDigits: getCurrencyExp(currency: code.uppercased())
            )
            result[code] = entry
            result[code.lowercased()] = entry
        }
    }
    
    return result
}

private let currencyFormatterEntries = loadCurrencyFormatterEntries()

public func setupCurrencyNumberFormatter(currency: String) -> NumberFormatter {
    guard let entry = currencyFormatterEntries[currency] ?? currencyFormatterEntries["USD"] else {
        preconditionFailure()
    }

    var result = ""
    if entry.symbolOnLeft {
        result.append("¤")
        if entry.spaceBetweenAmountAndSymbol {
            result.append(" ")
        }
    }

    result.append("#")

    if entry.decimalDigits != 0 {
        result.append(entry.decimalSeparator)
    }

    for _ in 0 ..< entry.decimalDigits {
        result.append("#")
    }
    if entry.decimalDigits != 0 {
        result.append("0")
    }

    if !entry.symbolOnLeft {
        if entry.spaceBetweenAmountAndSymbol {
            result.append(" ")
        }
        result.append("¤")
    }

    let numberFormatter = NumberFormatter()

    numberFormatter.numberStyle = .currency

    numberFormatter.positiveFormat = result
    numberFormatter.negativeFormat = "-\(result)"

    numberFormatter.currencySymbol = ""
    numberFormatter.currencyDecimalSeparator = entry.decimalSeparator
    numberFormatter.currencyGroupingSeparator = entry.thousandsSeparator

    numberFormatter.minimumFractionDigits = entry.decimalDigits
    numberFormatter.maximumFractionDigits = entry.decimalDigits
    numberFormatter.minimumIntegerDigits = 1

    return numberFormatter
}

public func fractionalToCurrencyAmount(value: Double, currency: String) -> Int64? {
    guard let entry = currencyFormatterEntries[currency] ?? currencyFormatterEntries["USD"] else {
        return nil
    }
    var factor: Double = 1.0
    for _ in 0 ..< entry.decimalDigits {
        factor *= 10.0
    }
    if value > Double(Int64.max) / factor {
        return nil
    } else {
        return Int64(value * factor)
    }
}

public func currencyToFractionalAmount(value: Int64, currency: String) -> Double? {
    guard let entry = currencyFormatterEntries[currency] ?? currencyFormatterEntries["USD"] else {
        return nil
    }
    var factor: Double = 1.0
    for _ in 0 ..< entry.decimalDigits {
        factor *= 10.0
    }
    return Double(value) / factor
}

public func formatCurrencyAmount(_ amount: Int64, currency: String) -> String {
    if let entry = currencyFormatterEntries[currency] ?? currencyFormatterEntries["USD"] {
        var result = ""
        if amount < 0 {
            result.append("-")
        }
        if entry.symbolOnLeft {
            result.append(entry.symbol)
            if entry.spaceBetweenAmountAndSymbol {
                result.append(" ")
            }
        }
        var integerPart = abs(amount)
        var fractional: [Character] = []
        for _ in 0 ..< entry.decimalDigits {
            let part = integerPart % 10
            integerPart /= 10
            if let scalar = UnicodeScalar(UInt32(part + 48)) {
                fractional.append(Character(scalar))
            }
        }
        result.append("\(integerPart)")
        if !fractional.isEmpty {
            result.append(entry.decimalSeparator)
        }
        for i in 0 ..< fractional.count {
            result.append(fractional[fractional.count - i - 1])
        }
        if !entry.symbolOnLeft {
            if entry.spaceBetweenAmountAndSymbol {
                result.append(" ")
            }
            result.append(entry.symbol)
        }
        
        return result
    } else {
        assertionFailure()
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency
        formatter.currencyCode = currency
        formatter.negativeFormat = "-¤#,##0.00"
        return formatter.string(from: (Float(amount) * 0.01) as NSNumber) ?? ""
    }
}

public func formatCurrencyAmountCustom(_ amount: Int64, currency: String) -> (String, String, Bool) {
    if let entry = currencyFormatterEntries[currency] ?? currencyFormatterEntries["USD"] {
        var result = ""
        if amount < 0 {
            result.append("-")
        }
        /*if entry.symbolOnLeft {
            result.append(entry.symbol)
            if entry.spaceBetweenAmountAndSymbol {
                result.append(" ")
            }
        }*/
        var integerPart = abs(amount)
        var fractional: [Character] = []
        for _ in 0 ..< entry.decimalDigits {
            let part = integerPart % 10
            integerPart /= 10
            if let scalar = UnicodeScalar(UInt32(part + 48)) {
                fractional.append(Character(scalar))
            }
        }
        result.append("\(integerPart)")
        if !fractional.isEmpty {
            result.append(entry.decimalSeparator)
        }
        for i in 0 ..< fractional.count {
            result.append(fractional[fractional.count - i - 1])
        }
        /*if !entry.symbolOnLeft {
            if entry.spaceBetweenAmountAndSymbol {
                result.append(" ")
            }
            result.append(entry.symbol)
        }*/

        return (result, entry.symbol, entry.symbolOnLeft)
    } else {
        return ("", "", false)
    }
}

public struct CurrencyFormat {
    public var symbol: String
    public var symbolOnLeft: Bool
    public var decimalSeparator: String
    public var decimalDigits: Int

    public init?(currency: String) {
        guard let entry = currencyFormatterEntries[currency] else {
            return nil
        }
        self.symbol = entry.symbol
        self.symbolOnLeft = entry.symbolOnLeft
        self.decimalSeparator = entry.decimalSeparator
        self.decimalDigits = entry.decimalDigits
    }
}