
import Foundation

extension StringProtocol  {
    func substring<S: StringProtocol>(from start: S, options: String.CompareOptions = []) -> SubSequence? {
        guard let lower = range(of: start, options: options)?.upperBound
        else { return nil }
        return self[lower...]
    }
    func substring<S: StringProtocol, T: StringProtocol>(from start: S, to end: T, options: String.CompareOptions = []) -> SubSequence? {
        guard let lower = range(of: start, options: options)?.upperBound,
            let upper = self[lower...].range(of: end, options: options)?.lowerBound
        else { return nil }
        return self[lower..<upper]
    }
    
    func index(from: Int) -> Index {
        return self.index(startIndex, offsetBy: from)
    }
    
    func substring(with r: Range<Int>) -> String {
        let startIndex = index(from: r.lowerBound)
        let endIndex = index(from: r.upperBound)
        return String(self[startIndex..<endIndex])
    }
    
    func hex2decimal() -> Int {
        Int(self, radix: 16) ?? 0
    }
    
    func hex2bin() -> String {
        guard let value = Int(self, radix: 16) else {
            return ""
        }
        return String(value, radix: 2)
    }
    
    func bin2decimal() -> Int {
        return Int(self, radix: 2) ?? 0
    }
    
    func bin2hex() -> String {
        guard let value = Int(self, radix: 2) else {
            return ""
        }
        return String(value, radix: 16)
    }
    
    func secondComplementsAmount() -> Int{
        let bin = self.hex2bin()
        let characters = Array(bin)
        var firstComplement = ""
        for c in characters {
            if (c == "0"){
                firstComplement.append("1")
            } else {
                firstComplement.append("0")
            }
        }
        let flipped = Array(firstComplement)
        var isFinish = false
        var secondComplement = ""
        for index in stride(from: firstComplement.count - 1, through: 0, by: -1) {
            let c = flipped[index]
            if (!isFinish){
                if (c == "0"){
                    secondComplement.append("1")
                    isFinish = true
                } else {
                    secondComplement.append("0")
                }
            } else {
                secondComplement.append(c)
            }
            
        }
        let second = Array(secondComplement)
        var flipback = ""
        for index in stride(from: secondComplement.count - 1, through: 0, by: -1) {
            let c = second[index]
            flipback.append(c)
        }
        let binhex = flipback.bin2hex()
        return binhex.hex2decimal()
    }
    
    func formatCardNumber() -> String {
        let chars = Array(self)
        var cardNumber = ""
        for i in 0..<chars.count {
            cardNumber.append(chars[i])
            if (i == 3) {
                cardNumber.append(" ")
            } else if (i == 7){
                cardNumber.append(" ")
            } else if (i == 11){
                cardNumber.append(" ")
            }
        }
        return cardNumber
    }
    
    func formatMaskedCardNumber() -> String {
        let chars = Array(self)
        var cardNumber = ""
        for i in 0..<chars.count {
            if (i == 3) {
                cardNumber.append(chars[i])
                cardNumber.append(" ")
            } else if (i == 7){
                cardNumber.append(chars[i])
                cardNumber.append(" ")
            } else if (i == 8){
                cardNumber.append("*")
            } else if (i == 9){
                cardNumber.append("*")
            } else if (i == 10){
                cardNumber.append("*")
            } else if (i == 11){
                cardNumber.append("*")
                cardNumber.append(" ")
            } else {
                cardNumber.append(chars[i])
            }
        }
        return cardNumber
    }
    
}

extension Int {
    func decimal2bin() -> String {
        return String(self, radix: 2)
    }
    
    func decimal2hex() -> String {
        return String(self, radix: 16)
    }
    var bin: String {
        String(self, radix: 2).leftPad(with: "0", length: 8)
    }
}

extension UInt8 {
    var bin: String {
        String(self, radix: 2).leftPad(with: "0", length: 8)
    }
}

extension [UInt8] {
    func hexString() -> String {
        return map { String(format: "%02hhx", $0) }.joined()
    }
}

extension String {
    
    //    static func hexString(_ data: Data) -> String {
    //        return data.map { String(format: "%02x", $0) }.joined()
    //    }
    
    //    static func hex2byte(_ str: String) -> Data {
    //        var data = Data()
    //        var tempStr = str
    //        while tempStr.count > 0 {
    //            let c = String(tempStr.prefix(2))
    //            tempStr = String(tempStr.dropFirst(2))
    //            if let num = UInt8(c, radix: 16) {
    //                data.append(num)
    //            }
    //        }
    //        return data
    //    }
    
    func hex2byte() -> Data {
        var data = Data()
        var tempStr = self
        while tempStr.count > 0 {
            let c = String(tempStr.prefix(2))
            tempStr = String(tempStr.dropFirst(2))
            if let num = UInt8(c, radix: 16) {
                data.append(num)
            }
        }
        return data
    }
    
    func subString(from: Int, to: Int) -> String {
       guard from >= 0, to >= from, from <= self.count, to <= self.count else {
           return ""
       }
       let startIndex = self.index(self.startIndex, offsetBy: from)
       let endIndex = self.index(self.startIndex, offsetBy: to)
       return String(self[startIndex..<endIndex])
    }
    
    func leftPad(with character: Character, length: UInt) -> String {
        let maxLength = Int(length) - count
        guard maxLength > 0 else {
            return self
        }
        return String(repeating: String(character), count: maxLength) + self
    }
    
    func stringToBytes() -> [UInt8]? {
        let length = self.count
        if length & 1 != 0 {
            return nil
        }
        var bytes = [UInt8]()
        bytes.reserveCapacity(length/2)
        var index = self.startIndex
        for _ in 0..<length/2 {
            let nextIndex = self.index(index, offsetBy: 2)
            if let b = UInt8(self[index..<nextIndex], radix: 16) {
                bytes.append(b)
            } else {
                return nil
            }
            index = nextIndex
        }
        return bytes
    }
    func localizeString(string: String) -> String {
          guard
              let path = Bundle.main.path(forResource: string, ofType: "lproj"),
              let bundle = Bundle(path: path)
          else {
              return NSLocalizedString(self, comment: "")
          }
          return NSLocalizedString(self, tableName: nil, bundle: bundle,
          value: "", comment: "")
      }
}


extension String: LocalizedError {
    static let numbers: Set<Character> = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
    static let hexSymbols: Set<Character> = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "A", "B", "C", "D", "E", "F" ]
    
    public var errorDescription: String? { return self }
    
    var isNumeric: Bool {
        guard self.count > 0 else { return false }
        return Set(self).isSubset(of: String.numbers)
    }
    
    var isHex: Bool {
        guard self.count > 0 && self.count % 2 == 0 else { return false }
        return Set(self).isSubset(of: String.hexSymbols)
    }
    
    subscript(_ range: CountableRange<Int>) -> String {
        let start = index(startIndex, offsetBy: max(0, range.lowerBound))
        let end = index(start, offsetBy: min(self.count - range.lowerBound,
                                             range.upperBound - range.lowerBound))
        return String(self[start..<end])
    }
    
    subscript(_ range: CountablePartialRangeFrom<Int>) -> String {
        let start = index(startIndex, offsetBy: max(0, range.lowerBound))
        return String(self[start...])
    }
    
    func deletingPrefix(_ prefix: String) -> String {
            guard self.hasPrefix(prefix) else { return self }
            return String(self.dropFirst(prefix.count))
    }
    func indexInt(of char: Character) -> Int? {
        return firstIndex(of: char)?.utf16Offset(in: self)
    }

}
