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

/// Detects human faces in images.
class FaceDetector implements Finalizable {
  /// Native handle.
  late Pointer<Pointer<id3FaceDetector>> _pHandle;
  bool _disposable = true;

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

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

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

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


  ///
  /// Confidence threshold, in the range [0;100].
  /// Hint: Default value is 50.
  /// Note: Setting a high threshold reduces false detection but can increase the number of undetected faces.
  ///
  /// throws FaceException An error has occurred during Face Library execution.
  int get confidenceThreshold => getConfidenceThreshold();
  set confidenceThreshold(int value) => setConfidenceThreshold(value);

  ///
  /// Model used to detect and track faces.
  /// Hint: Default value is FaceDetector4A 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);

  ///
  /// Non-maximum suppression (NMS) intersection-over-union (IOU) threshold, in the range is [0;100].
  /// Hint: Default value is 40.
  /// Note: Setting a high threshold allows to detect more overlapping faces which can be useful in a multi-face scenario. On the contrary, in a portrait scenario, a low NMS IOU threshold should be preferred.
  ///
  /// throws FaceException An error has occurred during Face Library execution.
  int get nmsIouThreshold => getNmsIouThreshold();
  set nmsIouThreshold(int value) => setNmsIouThreshold(value);

  ///
  /// Processing unit where to run the detection 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 face detection.
  /// 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 confidence threshold, in the range (0;100).
  /// Hint: Default value is 50.
  /// Note: Setting a high threshold reduces false detection but can increase the number of undetected faces.
  ///
  /// return Confidence threshold, in the range (0;100).
  /// throws FaceException An error has occurred during Face Library execution.
  int getConfidenceThreshold() {
    Pointer<Int> pConfidenceThreshold = calloc();
    try {
      var err = faceSDK.id3FaceDetector_GetConfidenceThreshold(_pHandle.value, pConfidenceThreshold);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vConfidenceThreshold = pConfidenceThreshold.value;
      return vConfidenceThreshold;
    } finally {
      calloc.free(pConfidenceThreshold);
    }
  }

  /// Sets the confidence threshold, in the range (0;100).
  /// Hint: Default value is 50.
  /// Note: Setting a high threshold reduces false detection but can increase the number of undetected faces.
  ///
  /// param confidenceThreshold Confidence threshold, in the range (0;100).
  /// throws FaceException An error has occurred during Face Library execution.
  void setConfidenceThreshold(int confidenceThreshold) {
    var err = faceSDK.id3FaceDetector_SetConfidenceThreshold(_pHandle.value, confidenceThreshold);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Gets the model used to detect and track faces.
  /// Hint: Default value is FaceDetector4A 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 detect and track faces.
  /// throws FaceException An error has occurred during Face Library execution.
  FaceModel getModel() {
    Pointer<Int32> pModel = calloc();
    try {
      var err = faceSDK.id3FaceDetector_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 detect and track faces.
  /// Hint: Default value is FaceDetector4A 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 detect and track faces.
  /// throws FaceException An error has occurred during Face Library execution.
  void setModel(FaceModel model) {
    var err = faceSDK.id3FaceDetector_SetModel(_pHandle.value, model.value);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Gets the non-maximum suppression (NMS) intersection-over-union (IOU) threshold, in the range is (0;100).
  /// Hint: Default value is 40.
  /// Note: Setting a high threshold allows to detect more overlapping faces which can be useful in a multi-face scenario. On the contrary, in a portrait scenario, a low NMS IOU threshold should be preferred.
  ///
  /// return Non-maximum suppression (NMS) intersection-over-union (IOU) threshold, in the range is (0;100).
  /// throws FaceException An error has occurred during Face Library execution.
  int getNmsIouThreshold() {
    Pointer<Int> pNmsIouThreshold = calloc();
    try {
      var err = faceSDK.id3FaceDetector_GetNmsIouThreshold(_pHandle.value, pNmsIouThreshold);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vNmsIouThreshold = pNmsIouThreshold.value;
      return vNmsIouThreshold;
    } finally {
      calloc.free(pNmsIouThreshold);
    }
  }

  /// Sets the non-maximum suppression (NMS) intersection-over-union (IOU) threshold, in the range is (0;100).
  /// Hint: Default value is 40.
  /// Note: Setting a high threshold allows to detect more overlapping faces which can be useful in a multi-face scenario. On the contrary, in a portrait scenario, a low NMS IOU threshold should be preferred.
  ///
  /// param nmsIouThreshold Non-maximum suppression (NMS) intersection-over-union (IOU) threshold, in the range is (0;100).
  /// throws FaceException An error has occurred during Face Library execution.
  void setNmsIouThreshold(int nmsIouThreshold) {
    var err = faceSDK.id3FaceDetector_SetNmsIouThreshold(_pHandle.value, nmsIouThreshold);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Gets the processing unit where to run the detection process.
  /// Hint: Default value is CPU.
  ///
  /// return Processing unit where to run the detection process.
  /// throws FaceException An error has occurred during Face Library execution.
  ProcessingUnit getProcessingUnit() {
    Pointer<Int32> pProcessingUnit = calloc();
    try {
      var err = faceSDK.id3FaceDetector_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 detection process.
  /// Hint: Default value is CPU.
  ///
  /// param processingUnit Processing unit where to run the detection process.
  /// throws FaceException An error has occurred during Face Library execution.
  void setProcessingUnit(ProcessingUnit processingUnit) {
    var err = faceSDK.id3FaceDetector_SetProcessingUnit(_pHandle.value, processingUnit.value);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Gets the number of threads to be used for face detection.
  /// 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 face detection.
  /// throws FaceException An error has occurred during Face Library execution.
  int getThreadCount() {
    Pointer<Int> pThreadCount = calloc();
    try {
      var err = faceSDK.id3FaceDetector_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 face detection.
  /// 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 face detection.
  /// throws FaceException An error has occurred during Face Library execution.
  void setThreadCount(int threadCount) {
    var err = faceSDK.id3FaceDetector_SetThreadCount(_pHandle.value, threadCount);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Detects faces in an image and store their info in a DetectedFaceList object.
  /// Note: The algorithm searches for faces in the range (16px;512px). If the image is too large to fit this range, one must resize it before the detection process.
  /// Important: Loading a ``FaceDetector`` model is required to use this function.
  ///
  /// param image Source image to process.
  /// return The list of detected faces.
  /// throws FaceException An error has occurred during Face Library execution.
  DetectedFaceList detectFaces(Image image) {
    DetectedFaceList detectedFaceList = DetectedFaceList();
    var err = faceSDK.id3FaceDetector_DetectFaces(_pHandle.value, image.handle, detectedFaceList.handle);
    if (err != FaceError.success.value) {
      detectedFaceList.dispose();
      throw FaceException(err);
    }
    return detectedFaceList;
  }

  /// Tracks faces in an image and update their info in a DetectedFaceList object.
  /// In a realtime process, one must use this function instead of DetectFaces() to keep the face IDs stable in time.
  /// Note: The algorithm searches for faces in the range (16px;512px). If the image is too large to fit this range, one must resize it before the detection process.
  /// Important: Loading a ``FaceDetector`` model is required to use this function.
  /// Warning: This function is deprecated: use the FaceTracker module instead.
  ///
  /// param image Source image to process.
  /// param detectedFaceList List of detected faces.
  /// throws FaceException An error has occurred during Face Library execution.
  void trackFaces(Image image, DetectedFaceList detectedFaceList) {
    var err = faceSDK.id3FaceDetector_TrackFaces(_pHandle.value, image.handle, detectedFaceList.handle);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Gets the face detector module ready to work on a specific image size by initializing all its internal memory layout.
  /// This function can be called after setting all the parameters of the face detector module if the image size is fixed, for example using frames from a video stream.
  /// Calling this function is not mandatory as the internal memory layout is automatically initialized in the first call to detectFaces() or trackFaces() if missing.
  /// Important: Loading a ``FaceDetector`` model is required to use this function.
  ///
  /// param imageWidth Width of the expected image size.
  /// param imageHeight Height of expected image size.
  /// throws FaceException An error has occurred during Face Library execution.
  void warmUp(int imageWidth, int imageHeight) {
    var err = faceSDK.id3FaceDetector_WarmUp(_pHandle.value, imageWidth, imageHeight);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

}

