//--------------------------------------------------------------------------------------------------
// 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.id3FaceEncoder_Dispose.cast());

/// Extracts facial features and encodes face templates.
class FaceEncoder implements Finalizable {
  /// Native handle.
  late Pointer<Pointer<id3FaceEncoder>> _pHandle;
  bool _disposable = true;

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

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

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

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


  ///
  /// Minimal interocular distance (IOD) in pixels required to perform a face encoding.
  /// Hint: Default value is 30 pixels. It is not recommended to change this value.
  /// Warning: Reducing the minimal required IOD to encode smaller face images may create unreliable face templates and significantly degrade recognition performances.
  ///
  /// throws FaceException An error has occurred during Face Library execution.
  int get minimalIod => getMinimalIod();
  set minimalIod(int value) => setMinimalIod(value);

  ///
  /// Model used to create templates.
  /// Hint: Default value is FaceEncoder9A which is the best available algorithm in this SDK in terms of accuracy. Some better accuracy/speed balances can be found by choosing another model.
  ///
  /// throws FaceException An error has occurred during Face Library execution.
  FaceModel get model => getModel();
  set model(FaceModel value) => setModel(value);

  ///
  /// Processing unit where to run the encoding process.
  /// Hint: Default value is CPU.
  ///
  /// throws FaceException An error has occurred during Face Library execution.
  ProcessingUnit get processingUnit => getProcessingUnit();
  set processingUnit(ProcessingUnit value) => setProcessingUnit(value);

  ///
  /// Number of threads to be used for template encoding.
  /// Hint: Default value is 1. Allocating more than one thread can increase the speed of the process.
  ///
  /// throws FaceException An error has occurred during Face Library execution.
  int get threadCount => getThreadCount();
  set threadCount(int value) => setThreadCount(value);

  // Public methods
  /// Gets the minimal interocular distance (IOD) in pixels required to perform a face encoding.
  /// Hint: Default value is 30 pixels. It is not recommended to change this value.
  /// Warning: Reducing the minimal required IOD to encode smaller face images may create unreliable face templates and significantly degrade recognition performances.
  ///
  /// return Minimal interocular distance (IOD) in pixels required to perform a face encoding.
  /// throws FaceException An error has occurred during Face Library execution.
  int getMinimalIod() {
    Pointer<Int> pMinimalIod = calloc();
    try {
      var err = faceSDK.id3FaceEncoder_GetMinimalIod(_pHandle.value, pMinimalIod);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vMinimalIod = pMinimalIod.value;
      return vMinimalIod;
    } finally {
      calloc.free(pMinimalIod);
    }
  }

  /// Sets the minimal interocular distance (IOD) in pixels required to perform a face encoding.
  /// Hint: Default value is 30 pixels. It is not recommended to change this value.
  /// Warning: Reducing the minimal required IOD to encode smaller face images may create unreliable face templates and significantly degrade recognition performances.
  ///
  /// param minimalIod Minimal interocular distance (IOD) in pixels required to perform a face encoding.
  /// throws FaceException An error has occurred during Face Library execution.
  void setMinimalIod(int minimalIod) {
    var err = faceSDK.id3FaceEncoder_SetMinimalIod(_pHandle.value, minimalIod);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Gets the model used to create templates.
  /// Hint: Default value is FaceEncoder9A which is the best available algorithm in this SDK in terms of accuracy. Some better accuracy/speed balances can be found by choosing another model.
  ///
  /// return Model used to create templates.
  /// throws FaceException An error has occurred during Face Library execution.
  FaceModel getModel() {
    Pointer<Int32> pModel = calloc();
    try {
      var err = faceSDK.id3FaceEncoder_GetModel(_pHandle.value, pModel);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vModel = FaceModelX.fromValue(pModel.value);
      return vModel;
    } finally {
      calloc.free(pModel);
    }
  }

  /// Sets the model used to create templates.
  /// Hint: Default value is FaceEncoder9A which is the best available algorithm in this SDK in terms of accuracy. Some better accuracy/speed balances can be found by choosing another model.
  ///
  /// param model Model used to create templates.
  /// throws FaceException An error has occurred during Face Library execution.
  void setModel(FaceModel model) {
    var err = faceSDK.id3FaceEncoder_SetModel(_pHandle.value, model.value);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Gets the processing unit where to run the encoding process.
  /// Hint: Default value is CPU.
  ///
  /// return Processing unit where to run the encoding process.
  /// throws FaceException An error has occurred during Face Library execution.
  ProcessingUnit getProcessingUnit() {
    Pointer<Int32> pProcessingUnit = calloc();
    try {
      var err = faceSDK.id3FaceEncoder_GetProcessingUnit(_pHandle.value, pProcessingUnit);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vProcessingUnit = ProcessingUnitX.fromValue(pProcessingUnit.value);
      return vProcessingUnit;
    } finally {
      calloc.free(pProcessingUnit);
    }
  }

  /// Sets the processing unit where to run the encoding process.
  /// Hint: Default value is CPU.
  ///
  /// param processingUnit Processing unit where to run the encoding process.
  /// throws FaceException An error has occurred during Face Library execution.
  void setProcessingUnit(ProcessingUnit processingUnit) {
    var err = faceSDK.id3FaceEncoder_SetProcessingUnit(_pHandle.value, processingUnit.value);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Gets the number of threads to be used for template encoding.
  /// Hint: Default value is 1. Allocating more than one thread can increase the speed of the process.
  ///
  /// return Number of threads to be used for template encoding.
  /// throws FaceException An error has occurred during Face Library execution.
  int getThreadCount() {
    Pointer<Int> pThreadCount = calloc();
    try {
      var err = faceSDK.id3FaceEncoder_GetThreadCount(_pHandle.value, pThreadCount);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vThreadCount = pThreadCount.value;
      return vThreadCount;
    } finally {
      calloc.free(pThreadCount);
    }
  }

  /// Sets the number of threads to be used for template encoding.
  /// Hint: Default value is 1. Allocating more than one thread can increase the speed of the process.
  ///
  /// param threadCount Number of threads to be used for template encoding.
  /// throws FaceException An error has occurred during Face Library execution.
  void setThreadCount(int threadCount) {
    var err = faceSDK.id3FaceEncoder_SetThreadCount(_pHandle.value, threadCount);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Computes the quality of a detected face.
  /// Here, quality expresses the match capability of an image: a high quality image will generate less match errors (false acceptance or false rejection) than a low quality one.
  /// Warning: A minimum interocular distance (IOD) of 30 pixels for the detected face is required for this function, below this value it will output an error.
  /// Important: Loading the ``FaceEncodingQualityEstimator`` model is required to use this function.
  ///
  /// param image Source image to process.
  /// param detectedFace Detected face to process.
  /// return The estimated quality of the detected face. Range is (0:100).
  /// throws FaceException An error has occurred during Face Library execution.
  int computeQuality(Image image, DetectedFace detectedFace) {
    Pointer<Int> pQuality = calloc();
    try {
      var err = faceSDK.id3FaceEncoder_ComputeQuality(_pHandle.value, image.handle, detectedFace.handle, pQuality);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vQuality = pQuality.value;
      return vQuality;
    } finally {
      calloc.free(pQuality);
    }
  }

  /// Extracts the unique features of a detected face and store them in a FaceTemplate.
  /// Warning: A minimum interocular distance (IOD) of 30 pixels for the detected face is required for this function, below this value it will output an error.
  /// Important: Loading a ``FaceEncoder`` model is required to use this function.
  ///
  /// param image Source image to process.
  /// param detectedFace Detected face to process.
  /// return The generated face template.
  /// throws FaceException An error has occurred during Face Library execution.
  FaceTemplate createTemplate(Image image, DetectedFace detectedFace) {
    FaceTemplate faceTemplate = FaceTemplate();
    var err = faceSDK.id3FaceEncoder_CreateTemplate(_pHandle.value, image.handle, detectedFace.handle, faceTemplate.handle);
    if (err != FaceError.success.value) {
      faceTemplate.dispose();
      throw FaceException(err);
    }
    return faceTemplate;
  }

  /// Gets the face encoder module ready to work by initializing all its internal memory layout.
  /// This function can be called after setting all the parameters of the face encoder module.
  /// Calling this function is not mandatory as the internal memory layout is automatically initialized in the first call to createTemplate() if missing.
  /// Important: Loading a ``FaceEncoder`` is required to use this function.
  ///
  /// throws FaceException An error has occurred during Face Library execution.
  void warmUp() {
    var err = faceSDK.id3FaceEncoder_WarmUp(_pHandle.value);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

}

