#!/usr/bin/python3
import traceback

import sys
import gi

gi.require_version('FPrint', '2.0')
from gi.repository import FPrint, GLib, Gio

# Exit with error on any exception, included those happening in async callbacks
sys.excepthook = lambda *args: (traceback.print_exception(*args), sys.exit(1))

ctx = GLib.main_context_default()

c = FPrint.Context()
c.enumerate()
devices = c.get_devices()

d = devices[0]
del devices

assert d.get_driver() == "egis_etu905"
assert not d.has_feature(FPrint.DeviceFeature.CAPTURE)
assert d.has_feature(FPrint.DeviceFeature.IDENTIFY)
assert d.has_feature(FPrint.DeviceFeature.VERIFY)
assert d.has_feature(FPrint.DeviceFeature.STORAGE)
assert d.has_feature(FPrint.DeviceFeature.STORAGE_LIST)
assert d.has_feature(FPrint.DeviceFeature.STORAGE_DELETE)
assert d.has_feature(FPrint.DeviceFeature.STORAGE_CLEAR)

d.open_sync()

def enroll_progress(*args):
    assert d.get_finger_status() == FPrint.FingerStatusFlags.PRESENT
    print("enroll progress stage: " +  str(args[1]))

def identify_done(dev, res):
    global identified
    identified = True
    identify_match, identify_print = dev.identify_finish(res)
    print("MATCH FOUND!" if identify_match else "NO MATCH FOUND")
    if identify_match:
        assert identify_match.equal(identify_print)

# List
print("--- LISTING ---")
stored = d.list_prints_sync()
prints1 = len(stored)
print(f"--- LIST DONE: Found {prints1} prints before enroll---")

# Enroll
print("--- ENROLLING ---")
template = FPrint.Print.new(d)

assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE
p = d.enroll_sync(template, None, enroll_progress, None)
assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE
print("--- ENROLL DONE ---")
del template

# List
print("--- LISTING ---")
stored = d.list_prints_sync()
prints2 = len(stored)
print(f"--- LIST DONE: Found {prints2} prints after enroll---")
assert (prints2 - prints1) == 1

# Verify
print("--- VERIFYING ---")
assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE
verify_res, verify_print = d.verify_sync(p)
assert d.get_finger_status() == FPrint.FingerStatusFlags.NONE
print(f"--- VERIFY DONE: Result {verify_res} ---")

# Identify
print("--- ASYNC IDENTIFYING ---")
identified = False
deserialized_prints = []
for sp in stored:
    deserialized_prints.append(FPrint.Print.deserialize(sp.serialize()))
# The last stored print should be the newly enrolled one
assert deserialized_prints[-1].equal(p)

d.identify(deserialized_prints, callback=identify_done)
del deserialized_prints

while not identified:
    ctx.iteration(True)
print("--- IDENTIFY DONE ---")

# Cancel test - start async identify and cancel it
print("--- TESTING CANCELLATION ---")
deserialized_prints = []
for sp in stored:
    deserialized_prints.append(FPrint.Print.deserialize(sp.serialize()))

cancellable = Gio.Cancellable()
identify_cancelled = False
cancel_result = None

def identify_cancelled_cb(dev, res):
    global identify_cancelled, cancel_result
    identify_cancelled = True
    try:
        result = dev.identify_finish(res)
        cancel_result = result
    except Exception as e:
        cancel_result = e
        print(f"Identify cancelled with error: {e}")

# Test 1: Cancel immediately after starting identify
d.identify(deserialized_prints, cancellable=cancellable, callback=identify_cancelled_cb)

print("--- IDENTIFY STARTED, CANCELLING IMMEDIATELY ---")
cancellable.cancel()

while not identify_cancelled:
    ctx.iteration(True)
print(f"--- CANCELLATION TEST DONE, result: {cancel_result} ---")

# Reopen device to ensure clean state for next cancel test
print("--- REOPENING DEVICE BEFORE NEXT CANCEL TEST ---")
d.close_sync()
d.open_sync()
print("--- DEVICE REOPENED ---")

# Test 2: Cancel after device reaches wait-for-finger stage
cancellable = Gio.Cancellable()
identify_cancelled = False
cancel_result = None
d.identify(deserialized_prints, cancellable=cancellable, callback=identify_cancelled_cb)

# Wait until the device is actually waiting for a finger before cancelling.
print("Waiting for device to reach wait-for-finger stage before cancelling...")
while not (d.get_finger_status() & FPrint.FingerStatusFlags.NEEDED):
    ctx.iteration(True)
cancellable.cancel()

while not identify_cancelled:
    ctx.iteration(True)
print(f"--- CANCELLATION TEST DONE, result: {cancel_result} ---")
del deserialized_prints

# Close and reopen device to ensure clean state after cancellation
print("--- REOPENING DEVICE ---")
d.close_sync()
d.open_sync()
print("--- DEVICE REOPENED ---")

# Delete
print("--- DELETING ---")
p_to_delete = next((sp for sp in stored if sp.equal(p)), None)
if p_to_delete:
    d.delete_print_sync(p_to_delete)
    print("--- DELETE DONE ---")
del p_to_delete
del p

# List
print("--- LISTING ---")
stored = d.list_prints_sync()
prints3 = len(stored)
print(f"--- LIST DONE: Found {prints3} prints after deleting---")
assert (prints2 - prints3) == 1
del stored

d.close_sync()

del d
del c

