#! cython

# Note that feature public key can be obtained from the Guardant Station
#   web browser interface, it is marked as for usage in the GrdVerifyDigest
#   function

from sys import exit, argv
from json import loads
from hashlib import sha256
from datetime import datetime, timedelta

from grdlic_python import Status, Feature, VendorCodes, GetApiVersion, GetLicenseInfo, VerifyDigest, GetErrorMessage, LedBlink, INVALID_LICENSE_ID_VALUE, INVALID_FEATURE_NUMBER_VALUE, RemoteMode, DongleModel, FeatureEncryptMode, GrdAesContext, GrdECC160, GrdLanguageId

cpdef int factorial(int x):
    cdef int y = 1
    cdef int i

    for i in range(1, x+1):
        y *= i
    
    print("Factorial(" + str(x) + ")= " + str(y))

    return y

cpdef str uhex(value):
    return hex(value & (2**32 - 1)) # 32 for int size

cpdef int handle_error(status):
    # print corresponding error message
    [status_, message] = GetErrorMessage(status, GrdLanguageId.GRD_LANG_EN)
    print("Status: " + str(status) + ", msg: " + message)

    if (status != Status.OK and status != Status.NO_SERVICE and status != Status.LICENSE_ALREADY_ACTIVATED and status != Status.NO_UPDATES_AVAILABLE):
        print("Error! Program exits.")
        exit(-1)
    #return
    return -1

cpdef list print_license_info(license):

    licenseId = INVALID_LICENSE_ID_VALUE
    lastFeatureNumber = INVALID_FEATURE_NUMBER_VALUE

    isBroken = license["isBroken"]
    if (isBroken == 1):
        print("  License " + uhex(license["licenseId"]) + " in DL is broken!")
        return [licenseId, lastFeatureNumber]

    try:
        licenseInfo = license["licenseInfo"]
    except KeyError:
        dongleInfo = license["dongleInfo"]
        print("  Hardware dongle without license:\n" +
              "    Dongle ID: " + uhex(dongleInfo["dongleId"]) + "\n" +
              "    Dongle model: " + uhex(dongleInfo["dongleModel"]) + "\n" +
              "    Vendor public code: " + uhex(dongleInfo["publicCode"]))

        LedBlink(dongleInfo["dongleId"])
        #status = LedBlink(dongleInfo["dongleId"])
        #handle_error(status)

        return [licenseId, lastFeatureNumber]

    print("  License " + uhex(licenseInfo["licenseId"]) + " is OK:\n" +
          "    Vendor public code: " + uhex(licenseInfo["vendorPublicCode"]) + "\n" +
          "    Vendor company name: " + licenseInfo["vendorCompanyName"] + "\n" +
          "    Products count: " + str(licenseInfo["productsCount"]))

    licenseId = licenseInfo["licenseId"]

    products = licenseInfo["products"]

    for product in products:

        print("      Product \"" + product["name"] + "\"" +
                   " with number " + str(product["number"]) +
                   " (modification " + str(product["modification"]) + ")" +
                   " with " + str(product["featuresCount"]) + " features")

        features = product["features"]

        for feature in features:

            print("        Feature \"" + feature["name"] + "\"" +
                         " with number " + str(feature["number"]))

            lastFeatureNumber = feature["number"]

    return [licenseId, lastFeatureNumber]

cpdef list print_licenses_info(licenseInfoJsonArray):

    root = loads(licenseInfoJsonArray)

    lastLicenseId = INVALID_LICENSE_ID_VALUE
    lastFeatureNumber = INVALID_FEATURE_NUMBER_VALUE

    if root["licenseCount"] == 0:
        print("  No licenses found!")
        return [lastLicenseId, lastFeatureNumber]

    licenses = root["licenses"]
    for license in licenses:
        [lastLicenseId, lastFeatureNumber] = print_license_info(license)

    return [lastLicenseId, lastFeatureNumber]

cpdef int test_general():

    print("\n---------------------------\n"
            "Example of using grdlic API"
          "\n---------------------------\n")

    # get grdlic API version
    print("Call GrdGetApiVersion")
    [major, minor, version] = GetApiVersion()
    print("API version: " + str(major) + "." + str(minor))

    # get info about all licenses (DL or Sign) available at this PC (because of m_remoteMode = RemoteMode.LOCAL)
    visibilityStr = "{\"dongleModel\": 0x480, \"remoteMode\": 1}" 
    print("Call GrdGetLicenseInfo")
    [status, licenseInfoJsonArray] = GetLicenseInfo(visibilityStr, None) # vendor access codes not specified
    handle_error(status)

    # print info about all licenses available at this PC
    print("ALL AVAILABLE LICENSES:")
    [licenseId, lastFeatureNumber] = print_licenses_info(licenseInfoJsonArray)

    if lastFeatureNumber == INVALID_FEATURE_NUMBER_VALUE:
        #return
        return -1

    print("Pick feature number " + str(lastFeatureNumber))

    # log in to some (the last, for example) feature
    print("Call GrdLogin")
    #vendorCodes = VendorCodes(0x519175b7, 0x51917645, 0x51917603) # vendor access codes specified are DEMO
    vendorCodes = VendorCodes(0xB33F7308, 0xD21C2838, 0xD2EDAAE4) # vendor access codes specified are TEST-0P
    feature = Feature(lastFeatureNumber)
    status = feature.Login(vendorCodes, visibilityStr)
    handle_error(status)

    # get info about feature license
    print("Call GrdFeatureGetInfo")
    [status, licenseInfoJson] = feature.GetInfo()
    handle_error(status)
    print("FEATURE INFO:")
    licenseInfo = loads(licenseInfoJson)
    print_license_info(licenseInfo)

    # check feature
    print("Call GrdFeatureCheck")
    status = feature.Check(None) # feature public key not specified
    handle_error(status)

    # prepare dummy data
    data = b"\x01\x23\x45\x67\x89\xAB\xCD\xEF\x01\x23\x45\x67\x89\xAB\xCD\xEF"
    plain_data_hash_obj = sha256()
    plain_data_hash_obj.update(data)
    plain_data_hash = plain_data_hash_obj.digest()

    # encrypt dummy data (limited) in ECB mode
    print("Call GrdFeatureEncrypt in ECB mode")
    status = feature.Encrypt(data, FeatureEncryptMode.GRD_EM_ECB, None)
    handle_error(status)

    # decrypt dummy data (limited) in ECB mode
    print("Call GrdFeatureDecrypt in ECB mode")
    status = feature.Decrypt(data, FeatureEncryptMode.GRD_EM_ECB, None)
    handle_error(status)

    # check dummy data (limited)
    crypt_data_hash_obj = sha256()
    crypt_data_hash_obj.update(data)
    crypt_data_hash = crypt_data_hash_obj.digest()
    print("Sanity check after (limited) encryption and decryption: " + ("OK" if crypt_data_hash == plain_data_hash else "ERROR"))
    if crypt_data_hash != plain_data_hash:
        exit(-1)

    context = GrdAesContext(b"\xFE\xDC\xBA\x98\x76\x54\x32\x10\xFE\xDC\xBA\x98\x76\x54\x32\x10", b"")

    # encrypt dummy data (unlimited) in CBC mode
    print("Call GrdFeatureEncrypt in CBC mode without counter decrement")
    status = feature.Encrypt(data, FeatureEncryptMode.GRD_EM_CBC | FeatureEncryptMode.GRD_NO_COUNTER_DECREMENT, context)
    handle_error(status)

    # decrypt dummy data (unlimited) in CBC mode
    print("Call GrdFeatureDecrypt in CBC mode without counter decrement")
    status = feature.Decrypt(data, FeatureEncryptMode.GRD_EM_CBC | FeatureEncryptMode.GRD_NO_COUNTER_DECREMENT, context)
    handle_error(status)

    # check dummy data (unlimited)
    crypt_data_hash_obj = sha256()
    crypt_data_hash_obj.update(data)
    crypt_data_hash = crypt_data_hash_obj.digest()
    print("Sanity check after unlimited encryption and decryption: " + ("OK" if crypt_data_hash == plain_data_hash else "ERROR"))
    if crypt_data_hash != plain_data_hash:
        exit(-1)    

    # prepare dummy data digest
    digest = bytes(GrdECC160.DIGEST_SIZE)

    # sign dummy data
    print("Call GrdFeatureSign")
    status = feature.Sign(data, digest)
    handle_error(status)

    # get current time from real clock (in seconds)
    print("Call GrdGetRealTime")
    [status, currentTime] = feature.GetRealTime()
    handle_error(status)
    print("Current time: " + str(currentTime) + " seconds, or " + datetime.fromtimestamp(currentTime).strftime("%H:%M:%S %d.%m.%Y"))

    # get feature max concurrent resource value
    print("Call GrdFeatureGetMaxConcurrentResource")
    [status, maxConcurrentResource] = feature.GetMaxConcurrentResource()
    handle_error(status)
    print("Max concurrent resource value: " + str(maxConcurrentResource))

    # log out of the feature
    print("Call GrdFeatureLogout")
    status = feature.Logout()
    handle_error(status)

    #return
    return 0

cpdef int cymain(int argv):
    factorial(argv)
    test_general()

    return 0

if __name__ == "__main__":
    cymain(argv[1:])
