import Foundation

enum HMACAlgorithm {
    case MD5, SHA1, SHA224, SHA256, SHA384, SHA512

    func toCCHmacAlgorithm() -> CCHmacAlgorithm {
        var result: Int = 0
        switch self {
        case .MD5:
            result = kCCHmacAlgMD5
        case .SHA1:
            result = kCCHmacAlgSHA1
        case .SHA224:
            result = kCCHmacAlgSHA224
        case .SHA256:
            result = kCCHmacAlgSHA256
        case .SHA384:
            result = kCCHmacAlgSHA384
        case .SHA512:
            result = kCCHmacAlgSHA512
        }
        return CCHmacAlgorithm(result)
    }

    func digestLength() -> Int {
        var result: CInt = 0
        switch self {
        case .MD5:
            result = CC_MD5_DIGEST_LENGTH
        case .SHA1:
            result = CC_SHA1_DIGEST_LENGTH
        case .SHA224:
            result = CC_SHA224_DIGEST_LENGTH
        case .SHA256:
            result = CC_SHA256_DIGEST_LENGTH
        case .SHA384:
            result = CC_SHA384_DIGEST_LENGTH
        case .SHA512:
            result = CC_SHA512_DIGEST_LENGTH
        }
        return Int(result)
    }
}

extension String {
    func hmac(algorithm: HMACAlgorithm, key: String) -> String {
      let cKey = key.cString(using: String.Encoding.utf8)
      let cData = self.cString(using: String.Encoding.utf8)
      var result = [CUnsignedChar](repeating: 0, count: Int(algorithm.digestLength()))
      CCHmac(algorithm.toCCHmacAlgorithm(), cKey!, Int(strlen(cKey!)), cData!, Int(strlen(cData!)), &result)
      let hmacData:NSData = NSData(bytes: result, length: (Int(algorithm.digestLength())))
      var bytes = [UInt8](repeating:0, count: hmacData.length)
      hmacData.getBytes(&bytes, length: hmacData.length)
      var hexString = ""
      for byte in bytes {
          hexString += String(format:"%02hhx", UInt8(byte))
      }
      return hexString
    }
}

extension Data {
    struct HexEncodingOptions: OptionSet {
        let rawValue: Int
        static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
    }

    func hexEncodedString(options: HexEncodingOptions = []) -> String {
        let hexDigits = Array((options.contains(.upperCase) ? "0123456789ABCDEF" : "0123456789abcdef").utf16)
        var chars: [unichar] = []
        chars.reserveCapacity(2 * count)
        for byte in self {
            chars.append(hexDigits[Int(byte / 16)])
            chars.append(hexDigits[Int(byte % 16)])
        }
        return String(utf16CodeUnits: chars, count: chars.count)
    }
}

func pbkdf2(hash: CCPBKDFAlgorithm, password: String, salt: String, keyByteCount: Int, rounds: Int) -> String? {

      guard let passwordData = password.data(using: .utf8), let saltData = salt.data(using: .utf8) else { return nil }

    var derivedKeyData = Data(repeating: 0, count: keyByteCount)
    let derivedCount = derivedKeyData.count
    let derivationStatus: Int32 = derivedKeyData.withUnsafeMutableBytes { derivedKeyBytes in
        let keyBuffer: UnsafeMutablePointer<UInt8> =
            derivedKeyBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
        return saltData.withUnsafeBytes { saltBytes -> Int32 in
            let saltBuffer: UnsafePointer<UInt8> = saltBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
            return CCKeyDerivationPBKDF(
                CCPBKDFAlgorithm(kCCPBKDF2),
                password,
                passwordData.count,
                saltBuffer,
                saltData.count,
                hash,
                UInt32(rounds),
                keyBuffer,
                derivedCount)
        }
    }
  return derivationStatus == kCCSuccess ? derivedKeyData.hexEncodedString() : nil
}

@objc(LessPassModule)
class LessPassModule: NSObject {
  @objc(createFingerprint:resolver:rejecter:)
  func createFingerprint(_ masterPassword: String,
                         resolver resolve: RCTPromiseResolveBlock,
                         rejecter reject:RCTPromiseRejectBlock) -> Void {
    resolve("".hmac(algorithm: HMACAlgorithm.SHA256, key: masterPassword))
  }

  @objc(calcEntropy:withLogin:withMasterPassword:withCounter:resolver:rejecter:)
  func calcEntropy(_ site: String,
                   withLogin login: String,
                   withMasterPassword masterPassword: String,
                   withCounter counter: String,
                   resolver resolve: RCTPromiseResolveBlock,
                   rejecter reject:RCTPromiseRejectBlock) -> Void {
    let salt = site + login + counter
    let r = pbkdf2(hash: CCPBKDFAlgorithm(kCCPRFHmacAlgSHA256), password: masterPassword, salt: salt, keyByteCount: 32, rounds: 100000)
    resolve(r)
  }

  @objc
  static func requiresMainQueueSetup() -> Bool {
    return false
  }
}
