//--------------------------------------------------------------------------------------------------
// Copyright (c) id3 Technologies
// All Rights Reserved.
//--------------------------------------------------------------------------------------------------
// ignore_for_file: unused_import
import 'dart:ffi';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
import 'face_sdk_generated_bindings.dart';
import 'face_native.dart';

import '../id3_face.dart';

final _finalizer = NativeFinalizer(faceSDK.addresses.id3FaceTemplate_Dispose.cast());

/// Represents a face template.
class FaceTemplate implements Finalizable {
  /// Native handle.
  late Pointer<Pointer<id3FaceTemplate>> _pHandle;
  bool _disposable = true;

  /// Gets the native handle.
  /// return The native handle.
  Pointer<id3FaceTemplate> get handle => _pHandle.value;

  /// Creates a new instance of the FaceTemplate class.
  ///
  /// throws FaceException An error has occurred during Face Library execution.
  FaceTemplate() {
    _pHandle = calloc();
    try {
      var err = faceSDK.id3FaceTemplate_Initialize(_pHandle);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      _finalizer.attach(this, _pHandle.cast(), detach: this);
    } finally {}
  }

  /// Creates a new instance of the FaceTemplate class.
  ///
  /// param handle     Handle to the FaceTemplate.
  /// throws FaceException An error has occurred during Face Library execution.
  FaceTemplate.fromHandle(Pointer<id3FaceTemplate> handle) {
    _pHandle = calloc();
    _pHandle.value = handle;
    _disposable = false;
  }

  /// Releases all resources used by this FaceTemplate.
  void dispose() {
    if (_disposable) {
      faceSDK.id3FaceTemplate_Dispose(_pHandle);
      calloc.free(_pHandle);
      _finalizer.detach(this);
    }
  }

  // Copyable methods

  /// Creates a copy of the FaceTemplate object.
  ///
  /// return The newly created FaceTemplate object.
  /// throws FaceException An error has occurred during Face Library execution.
  FaceTemplate clone() {
    FaceTemplate clone = FaceTemplate();
    var err = faceSDK.id3FaceTemplate_CopyTo(_pHandle.value, clone.handle);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
    return clone;
  }


  ///
  /// Format of the face template.
  ///
  /// throws FaceException An error has occurred during Face Library execution.
  FaceTemplateFormat get format => getFormat();

  ///
  /// Quality of the face template.
  ///
  /// throws FaceException An error has occurred during Face Library execution.
  int get quality => getQuality();

  // Public methods
  /// Gets the format of the face template.
  ///
  /// return Format of the face template.
  /// throws FaceException An error has occurred during Face Library execution.
  FaceTemplateFormat getFormat() {
    Pointer<Int32> pFormat = calloc();
    try {
      var err = faceSDK.id3FaceTemplate_GetFormat(_pHandle.value, pFormat);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vFormat = FaceTemplateFormatX.fromValue(pFormat.value);
      return vFormat;
    } finally {
      calloc.free(pFormat);
    }
  }

  /// Gets the quality of the face template.
  ///
  /// return Quality of the face template.
  /// throws FaceException An error has occurred during Face Library execution.
  int getQuality() {
    Pointer<Int> pQuality = calloc();
    try {
      var err = faceSDK.id3FaceTemplate_GetQuality(_pHandle.value, pQuality);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vQuality = pQuality.value;
      return vQuality;
    } finally {
      calloc.free(pQuality);
    }
  }

  /// Imports the face template object from a buffer.
  ///
  /// param data Buffer to import the face template object from.
  /// return The newly created face template.
  /// throws FaceException An error has occurred during Face Library execution.
  static FaceTemplate fromBuffer(Uint8List? data) {
    FaceTemplate faceTemplate = FaceTemplate();
    Pointer<UnsignedChar>? pData;
    if (data != null) {
    	pData = calloc.allocate<UnsignedChar>(data.length);
    	pData.cast<Uint8>().asTypedList(data.length).setAll(0, data);
    }
    try {
      var err = faceSDK.id3FaceTemplate_FromBuffer(faceTemplate.handle, pData ?? nullptr, data?.length ?? 0);
      if (err != FaceError.success.value) {
        faceTemplate.dispose();
        throw FaceException(err);
      }
      return faceTemplate;
    } finally {
      if (pData != null) {
        calloc.free(pData);
      }
    }
  }

  /// Imports the face template object from a file.
  ///
  /// param path Path to the file to import the face template object from.
  /// return The newly created face template.
  /// throws FaceException An error has occurred during Face Library execution.
  static FaceTemplate fromFile(String? path) {
    FaceTemplate faceTemplate = FaceTemplate();
    Pointer<Char>? pPath = path?.toNativeUtf8().cast<Char>();
    try {
      var err = faceSDK.id3FaceTemplate_FromFile(faceTemplate.handle, pPath ?? nullptr);
      if (err != FaceError.success.value) {
        faceTemplate.dispose();
        throw FaceException(err);
      }
      return faceTemplate;
    } finally {
      if (pPath != null) {
        calloc.free(pPath);
      }
    }
  }

  /// Exports the face template object to a Biometric Data Template (BDT) buffer.
  /// This buffer can only be used with id3 Face Match on Card specific implementations to verify a face template.
  ///
  /// return A buffer that receives the biometric data template.
  /// throws FaceException An error has occurred during Face Library execution.
  Uint8List toBdt() {
    Pointer<UnsignedChar> pData = nullptr;
    Pointer<Int> pDataSize = calloc();
    pDataSize[0] = -1;
    try {
      var err = faceSDK.id3FaceTemplate_ToBdt(_pHandle.value, pData, pDataSize);
      if (err == FaceError.insufficientBuffer.value) {
        pData = calloc.allocate(pDataSize.value);
        err = faceSDK.id3FaceTemplate_ToBdt(_pHandle.value, pData, pDataSize);
      }
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vData = Uint8List.fromList(pData.cast<Uint8>().asTypedList(pDataSize.value));
      return vData;
    } finally {
      calloc.free(pData);
      calloc.free(pDataSize);
    }
  }

  /// Exports the face template as a Biometric Information Template (BIT) for enrolment on a smart card equipped with id3 Match-on-Card technology.
  /// Note: The threshold value is required and should be set according to the used Face Encoder and desired security level.
  /// Important: The reference data qualifier (RDQ) should be defined in accordance with the smart card application specifications.
  ///
  /// param threshold The decision threshold, from 0 to 653535, to be applied during the face comparison on the smart card. See FaceMatcherThreshold for a list of typical values.
  /// param referenceDataQualifier Reference data qualifier.
  /// return A buffer that receives the biometric information template.
  /// throws FaceException An error has occurred during Face Library execution.
  Uint8List toBit(int threshold, int referenceDataQualifier) {
    Pointer<UnsignedChar> pData = nullptr;
    Pointer<Int> pDataSize = calloc();
    pDataSize[0] = -1;
    try {
      var err = faceSDK.id3FaceTemplate_ToBit(_pHandle.value, threshold, referenceDataQualifier, pData, pDataSize);
      if (err == FaceError.insufficientBuffer.value) {
        pData = calloc.allocate(pDataSize.value);
        err = faceSDK.id3FaceTemplate_ToBit(_pHandle.value, threshold, referenceDataQualifier, pData, pDataSize);
      }
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vData = Uint8List.fromList(pData.cast<Uint8>().asTypedList(pDataSize.value));
      return vData;
    } finally {
      calloc.free(pData);
      calloc.free(pDataSize);
    }
  }

  /// Exports the face template object to a buffer.
  ///
  /// return The buffer that receives the face template.
  /// throws FaceException An error has occurred during Face Library execution.
  Uint8List toBuffer() {
    Pointer<UnsignedChar> pData = nullptr;
    Pointer<Int> pDataSize = calloc();
    pDataSize[0] = -1;
    try {
      var err = faceSDK.id3FaceTemplate_ToBuffer(_pHandle.value, pData, pDataSize);
      if (err == FaceError.insufficientBuffer.value) {
        pData = calloc.allocate(pDataSize.value);
        err = faceSDK.id3FaceTemplate_ToBuffer(_pHandle.value, pData, pDataSize);
      }
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vData = Uint8List.fromList(pData.cast<Uint8>().asTypedList(pDataSize.value));
      return vData;
    } finally {
      calloc.free(pData);
      calloc.free(pDataSize);
    }
  }

  /// Exports the face template object to a file.
  ///
  /// param path Path to the file to export the face template object to.
  /// throws FaceException An error has occurred during Face Library execution.
  void toFile(String? path) {
    Pointer<Char>? pPath = path?.toNativeUtf8().cast<Char>();
    try {
      var err = faceSDK.id3FaceTemplate_ToFile(_pHandle.value, pPath ?? nullptr);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
    } finally {
      if (pPath != null) {
        calloc.free(pPath);
      }
    }
  }

  /// Updates the face template by combining it with the specified face template.
  ///
  /// param newFaceTemplate New face template to combine.
  /// throws FaceException An error has occurred during Face Library execution.
  void update(FaceTemplate newFaceTemplate) {
    var err = faceSDK.id3FaceTemplate_Update(_pHandle.value, newFaceTemplate.handle);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

}

