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

/// Tracks faces on consecutive images and automatically creates and updates associated face templates.
class FaceTracker implements Finalizable {
  /// Native handle.
  late Pointer<Pointer<id3FaceTracker>> _pHandle;
  bool _disposable = true;

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

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

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

  /// Releases all resources used by this FaceTracker.
  void dispose() {
    if (_disposable) {
      faceSDK.id3FaceTracker_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 FaceDetector4B.
  /// Note: Some better accuracy/speed balances can be found by choosing another model.
  ///
  /// throws FaceException An error has occurred during Face Library execution.
  FaceModel get detectionModel => getDetectionModel();
  set detectionModel(FaceModel value) => setDetectionModel(value);

  ///
  /// Model used to create features and assess consistancy among views of a given face.
  /// Hint: Default value is FaceEncoder9B. Some better accuracy/speed balances can be found by choosing another model.
  ///
  /// throws FaceException An error has occurred during Face Library execution.
  FaceModel get encodingModel => getEncodingModel();
  set encodingModel(FaceModel value) => setEncodingModel(value);

  ///
  /// Minimum match score to reach to preserve the ID of a tracked face between frame 't-1' and frame 't'.
  /// Hint: Default value is 3000 which corresponds to a False Match Rate of 1/1000.
  ///
  /// throws FaceException An error has occurred during Face Library execution.
  int get matchThreshold => getMatchThreshold();
  set matchThreshold(int value) => setMatchThreshold(value);

  ///
  /// Maximum number of consecutive non-detections to reach before deleting a tracked face.
  /// Hint: Default value is 30 which corresponds to 2s at a frame rate of 15 FPS. One must adapt this value to its needs in terms of tracker identity memory (in seconds) and measured frame rate on target platform.
  ///
  /// throws FaceException An error has occurred during Face Library execution.
  int get maximumTrackedFaceAge => getMaximumTrackedFaceAge();
  set maximumTrackedFaceAge(int value) => setMaximumTrackedFaceAge(value);

  ///
  /// Minimum number of consecutive detections to reach before creating a tracked face.
  /// Hint: Default value is 1 for FaceDetector4B since the false detection rate is low enough. If using a less accurate detector (such as FaceDetector3C) one might consider increasing a bit this value to avoid false tracks.
  ///
  /// throws FaceException An error has occurred during Face Library execution.
  int get minimumTrackedFaceAge => getMinimumTrackedFaceAge();
  set minimumTrackedFaceAge(int value) => setMinimumTrackedFaceAge(value);

  ///
  /// Non-maximum suppression (NMS) intersection-over-union (IOU) threshold, in the range is [0;100].
  /// Hint: Default value is 40. 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);

  ///
  /// Number of threads to be used for face detection and tracking.
  /// 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.id3FaceTracker_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.id3FaceTracker_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 FaceDetector4B.
  /// Note: 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 getDetectionModel() {
    Pointer<Int32> pDetectionModel = calloc();
    try {
      var err = faceSDK.id3FaceTracker_GetDetectionModel(_pHandle.value, pDetectionModel);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vDetectionModel = FaceModelX.fromValue(pDetectionModel.value);
      return vDetectionModel;
    } finally {
      calloc.free(pDetectionModel);
    }
  }

  /// Sets the model used to detect and track faces.
  /// Hint: Default value is FaceDetector4B.
  /// Note: Some better accuracy/speed balances can be found by choosing another model.
  ///
  /// param detectionModel Model used to detect and track faces.
  /// throws FaceException An error has occurred during Face Library execution.
  void setDetectionModel(FaceModel detectionModel) {
    var err = faceSDK.id3FaceTracker_SetDetectionModel(_pHandle.value, detectionModel.value);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Gets the model used to create features and assess consistancy among views of a given face.
  /// Hint: Default value is FaceEncoder9B. Some better accuracy/speed balances can be found by choosing another model.
  ///
  /// return Model used to create features and assess consistancy among views of a given face.
  /// throws FaceException An error has occurred during Face Library execution.
  FaceModel getEncodingModel() {
    Pointer<Int32> pEncodingModel = calloc();
    try {
      var err = faceSDK.id3FaceTracker_GetEncodingModel(_pHandle.value, pEncodingModel);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vEncodingModel = FaceModelX.fromValue(pEncodingModel.value);
      return vEncodingModel;
    } finally {
      calloc.free(pEncodingModel);
    }
  }

  /// Sets the model used to create features and assess consistancy among views of a given face.
  /// Hint: Default value is FaceEncoder9B. Some better accuracy/speed balances can be found by choosing another model.
  ///
  /// param encodingModel Model used to create features and assess consistancy among views of a given face.
  /// throws FaceException An error has occurred during Face Library execution.
  void setEncodingModel(FaceModel encodingModel) {
    var err = faceSDK.id3FaceTracker_SetEncodingModel(_pHandle.value, encodingModel.value);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Gets the minimum match score to reach to preserve the ID of a tracked face between frame 't-1' and frame 't'.
  /// Hint: Default value is 3000 which corresponds to a False Match Rate of 1/1000.
  ///
  /// return Minimum match score to reach to preserve the ID of a tracked face between frame 't-1' and frame 't'.
  /// throws FaceException An error has occurred during Face Library execution.
  int getMatchThreshold() {
    Pointer<Int> pMatchThreshold = calloc();
    try {
      var err = faceSDK.id3FaceTracker_GetMatchThreshold(_pHandle.value, pMatchThreshold);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vMatchThreshold = pMatchThreshold.value;
      return vMatchThreshold;
    } finally {
      calloc.free(pMatchThreshold);
    }
  }

  /// Sets the minimum match score to reach to preserve the ID of a tracked face between frame 't-1' and frame 't'.
  /// Hint: Default value is 3000 which corresponds to a False Match Rate of 1/1000.
  ///
  /// param matchThreshold Minimum match score to reach to preserve the ID of a tracked face between frame 't-1' and frame 't'.
  /// throws FaceException An error has occurred during Face Library execution.
  void setMatchThreshold(int matchThreshold) {
    var err = faceSDK.id3FaceTracker_SetMatchThreshold(_pHandle.value, matchThreshold);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Gets the maximum number of consecutive non-detections to reach before deleting a tracked face.
  /// Hint: Default value is 30 which corresponds to 2s at a frame rate of 15 FPS. One must adapt this value to its needs in terms of tracker identity memory (in seconds) and measured frame rate on target platform.
  ///
  /// return Maximum number of consecutive non-detections to reach before deleting a tracked face.
  /// throws FaceException An error has occurred during Face Library execution.
  int getMaximumTrackedFaceAge() {
    Pointer<Int> pMaximumTrackedFaceAge = calloc();
    try {
      var err = faceSDK.id3FaceTracker_GetMaximumTrackedFaceAge(_pHandle.value, pMaximumTrackedFaceAge);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vMaximumTrackedFaceAge = pMaximumTrackedFaceAge.value;
      return vMaximumTrackedFaceAge;
    } finally {
      calloc.free(pMaximumTrackedFaceAge);
    }
  }

  /// Sets the maximum number of consecutive non-detections to reach before deleting a tracked face.
  /// Hint: Default value is 30 which corresponds to 2s at a frame rate of 15 FPS. One must adapt this value to its needs in terms of tracker identity memory (in seconds) and measured frame rate on target platform.
  ///
  /// param maximumTrackedFaceAge Maximum number of consecutive non-detections to reach before deleting a tracked face.
  /// throws FaceException An error has occurred during Face Library execution.
  void setMaximumTrackedFaceAge(int maximumTrackedFaceAge) {
    var err = faceSDK.id3FaceTracker_SetMaximumTrackedFaceAge(_pHandle.value, maximumTrackedFaceAge);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Gets the minimum number of consecutive detections to reach before creating a tracked face.
  /// Hint: Default value is 1 for FaceDetector4B since the false detection rate is low enough. If using a less accurate detector (such as FaceDetector3C) one might consider increasing a bit this value to avoid false tracks.
  ///
  /// return Minimum number of consecutive detections to reach before creating a tracked face.
  /// throws FaceException An error has occurred during Face Library execution.
  int getMinimumTrackedFaceAge() {
    Pointer<Int> pMinimumTrackedFaceAge = calloc();
    try {
      var err = faceSDK.id3FaceTracker_GetMinimumTrackedFaceAge(_pHandle.value, pMinimumTrackedFaceAge);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vMinimumTrackedFaceAge = pMinimumTrackedFaceAge.value;
      return vMinimumTrackedFaceAge;
    } finally {
      calloc.free(pMinimumTrackedFaceAge);
    }
  }

  /// Sets the minimum number of consecutive detections to reach before creating a tracked face.
  /// Hint: Default value is 1 for FaceDetector4B since the false detection rate is low enough. If using a less accurate detector (such as FaceDetector3C) one might consider increasing a bit this value to avoid false tracks.
  ///
  /// param minimumTrackedFaceAge Minimum number of consecutive detections to reach before creating a tracked face.
  /// throws FaceException An error has occurred during Face Library execution.
  void setMinimumTrackedFaceAge(int minimumTrackedFaceAge) {
    var err = faceSDK.id3FaceTracker_SetMinimumTrackedFaceAge(_pHandle.value, minimumTrackedFaceAge);
    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. 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.id3FaceTracker_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. 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.id3FaceTracker_SetNmsIouThreshold(_pHandle.value, nmsIouThreshold);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

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

  /// Detects faces in an image and update their info in a TrackedFaceList object.
  /// 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 tracking process.
  /// In a realtime process, one must use this function to keep the face IDs stable in time.
  /// Important: Loading a ``FaceDetector`` is required to use this function.
  ///
  /// param image Source image to process.
  /// param trackedFaceList List of tracked faces.
  /// throws FaceException An error has occurred during Face Library execution.
  void detectFaces(Image image, TrackedFaceList trackedFaceList) {
    var err = faceSDK.id3FaceTracker_DetectFaces(_pHandle.value, image.handle, trackedFaceList.handle);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Tracks faces in an image and update their info in a TrackedFaceList object.
  /// 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 tracking process.
  /// In a realtime process, one must use this function to keep the face IDs stable in time.
  /// Important: Loading a ``FaceDetector`` and a ``FaceEncoder`` model is required to use this function.
  ///
  /// param image Source image to process.
  /// param trackedFaceList List of tracked faces.
  /// throws FaceException An error has occurred during Face Library execution.
  void trackFaces(Image image, TrackedFaceList trackedFaceList) {
    var err = faceSDK.id3FaceTracker_TrackFaces(_pHandle.value, image.handle, trackedFaceList.handle);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Gets the face tracker 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.id3FaceTracker_WarmUp(_pHandle.value, imageWidth, imageHeight);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

}

