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

/// Represents a detected face.
class DetectedFace implements Finalizable {
  /// Native handle.
  late Pointer<Pointer<id3DetectedFace>> _pHandle;
  bool _disposable = true;

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

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

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

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

  // Copyable methods

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


  ///
  /// Bounds of the detected face.
  ///
  /// throws FaceException An error has occurred during Face Library execution.
  Rectangle get bounds => getBounds();
  set bounds(Rectangle value) => setBounds(value);

  ///
  /// Confidence score of the detected face.
  ///
  /// throws FaceException An error has occurred during Face Library execution.
  int get detectionScore => getDetectionScore();

  ///
  /// ID of the detected face.
  ///
  /// throws FaceException An error has occurred during Face Library execution.
  int get id => getId();
  set id(int value) => setId(value);

  ///
  /// Landmarks (eyes, nose and mouth corners) of the detected face.
  ///
  /// throws FaceException An error has occurred during Face Library execution.
  PointList get landmarks => getLandmarks();
  set landmarks(PointList value) => setLandmarks(value);

  // Public methods
  /// Gets the bounds of the detected face.
  ///
  /// return Bounds of the detected face.
  /// throws FaceException An error has occurred during Face Library execution.
  Rectangle getBounds() {
    Pointer<id3FaceRectangle> pBounds = calloc();
    var err = faceSDK.id3DetectedFace_GetBounds(_pHandle.value, pBounds);
    if (err != FaceError.success.value) {
    	calloc.free(pBounds);
    	throw FaceException(err);
    }
    return Rectangle(pBounds);
  }

  /// Sets the bounds of the detected face.
  ///
  /// param bounds Bounds of the detected face.
  /// throws FaceException An error has occurred during Face Library execution.
  void setBounds(Rectangle bounds) {
    var err = faceSDK.id3DetectedFace_SetBounds(_pHandle.value, bounds.handle);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Gets the confidence score of the detected face.
  ///
  /// return Confidence score of the detected face.
  /// throws FaceException An error has occurred during Face Library execution.
  int getDetectionScore() {
    Pointer<Int> pDetectionScore = calloc();
    try {
      var err = faceSDK.id3DetectedFace_GetDetectionScore(_pHandle.value, pDetectionScore);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vDetectionScore = pDetectionScore.value;
      return vDetectionScore;
    } finally {
      calloc.free(pDetectionScore);
    }
  }

  /// Gets the ID of the detected face.
  ///
  /// return ID of the detected face.
  /// throws FaceException An error has occurred during Face Library execution.
  int getId() {
    Pointer<Int> pId = calloc();
    try {
      var err = faceSDK.id3DetectedFace_GetId(_pHandle.value, pId);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vId = pId.value;
      return vId;
    } finally {
      calloc.free(pId);
    }
  }

  /// Sets the ID of the detected face.
  ///
  /// param id ID of the detected face.
  /// throws FaceException An error has occurred during Face Library execution.
  void setId(int id) {
    var err = faceSDK.id3DetectedFace_SetId(_pHandle.value, id);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Gets the landmarks (eyes, nose and mouth corners) of the detected face.
  ///
  /// return Landmarks (eyes, nose and mouth corners) of the detected face.
  /// throws FaceException An error has occurred during Face Library execution.
  PointList getLandmarks() {
    PointList landmarks = PointList();
    var err = faceSDK.id3DetectedFace_GetLandmarks(_pHandle.value, landmarks.handle);
    if (err != FaceError.success.value) {
      landmarks.dispose();
      throw FaceException(err);
    }
    return landmarks;
  }

  /// Sets the landmarks (eyes, nose and mouth corners) of the detected face.
  ///
  /// param landmarks Landmarks (eyes, nose and mouth corners) of the detected face.
  /// throws FaceException An error has occurred during Face Library execution.
  void setLandmarks(PointList landmarks) {
    var err = faceSDK.id3DetectedFace_SetLandmarks(_pHandle.value, landmarks.handle);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Creates a detected face.
  ///
  /// param bounds Bounds of the detected face.
  /// param detectionScore Confidence score of the detected face.
  /// param id ID of the detected face.
  /// param landmarks Landmarks (eyes, nose and mouth corners) of the detected face.
  /// return The newly created detected face.
  /// throws FaceException An error has occurred during Face Library execution.
  static DetectedFace create(Rectangle bounds, int detectionScore, int id, PointList landmarks) {
    DetectedFace detectedFace = DetectedFace();
    var err = faceSDK.id3DetectedFace_Create(detectedFace.handle, bounds.handle, detectionScore, id, landmarks.handle);
    if (err != FaceError.success.value) {
      detectedFace.dispose();
      throw FaceException(err);
    }
    return detectedFace;
  }

  /// Imports the face object from a buffer.
  ///
  /// param data Buffer to import the face object from.
  /// return The newly created detected face.
  /// throws FaceException An error has occurred during Face Library execution.
  static DetectedFace fromBuffer(Uint8List? data) {
    DetectedFace detectedFace = DetectedFace();
    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.id3DetectedFace_FromBuffer(detectedFace.handle, pData ?? nullptr, data?.length ?? 0);
      if (err != FaceError.success.value) {
        detectedFace.dispose();
        throw FaceException(err);
      }
      return detectedFace;
    } finally {
      if (pData != null) {
        calloc.free(pData);
      }
    }
  }

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

  /// Gets the distance between the detected face and the camera when using a depth map in pixels.
  ///
  /// param depthImage Depth image to process.
  /// return The estimated distance to camera in pixels.
  /// throws FaceException An error has occurred during Face Library execution.
  int getDistanceToCamera(Image depthImage) {
    Pointer<Int> pDistanceToCamera = calloc();
    try {
      var err = faceSDK.id3DetectedFace_GetDistanceToCamera(_pHandle.value, depthImage.handle, pDistanceToCamera);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vDistanceToCamera = pDistanceToCamera.value;
      return vDistanceToCamera;
    } finally {
      calloc.free(pDistanceToCamera);
    }
  }

  /// Gets the bounding box around the detected face with specified aspect ratio and specified margin.
  ///
  /// param aspectRatio Aspect ratio of the bounding box. Default recommended value is 1.33 (4/3).
  /// param margin Relative margin around the detected face. Recommended value is 0.5.
  /// return The portrait bounds.
  /// throws FaceException An error has occurred during Face Library execution.
  Rectangle getExpandedBounds(double aspectRatio, double margin) {
    Pointer<id3FaceRectangle> pPortraitBounds = calloc();
    var err = faceSDK.id3DetectedFace_GetExpandedBounds(_pHandle.value, aspectRatio, margin, pPortraitBounds);
    if (err != FaceError.success.value) {
    	calloc.free(pPortraitBounds);
    	throw FaceException(err);
    }
    return Rectangle(pPortraitBounds);
  }

  /// Gets the distance between the eyes (IOD) of the detected face in pixels.
  ///
  /// return The computed interocular distance (IOD) in pixels.
  /// throws FaceException An error has occurred during Face Library execution.
  int getInterocularDistance() {
    Pointer<Int> pIod = calloc();
    try {
      var err = faceSDK.id3DetectedFace_GetInterocularDistance(_pHandle.value, pIod);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
      final vIod = pIod.value;
      return vIod;
    } finally {
      calloc.free(pIod);
    }
  }

  /// Gets the bounding box of the detected face for ICAO portrait cropping.
  /// This method shall be used to ensure compliance with the ICAO standard.
  ///
  /// param eyeImageWidthRatio Ratio between eye distance and image width. Must be in the range )0;1(. Default recommended value is 0.25.
  /// param eyeImageHeightRatio Ratio between eye distance to top and image height. Must be in the range )0;1(. Default recommended value is 0.45.
  /// param imageRatio Ratio between image height and image width. Default recommended value is 1.33 (4/3).
  /// return The portrait bounds of the detected face.
  /// throws FaceException An error has occurred during Face Library execution.
  Rectangle getPortraitBounds(double eyeImageWidthRatio, double eyeImageHeightRatio, double imageRatio) {
    Pointer<id3FaceRectangle> pPortraitBounds = calloc();
    var err = faceSDK.id3DetectedFace_GetPortraitBounds(_pHandle.value, eyeImageWidthRatio, eyeImageHeightRatio, imageRatio, pPortraitBounds);
    if (err != FaceError.success.value) {
    	calloc.free(pPortraitBounds);
    	throw FaceException(err);
    }
    return Rectangle(pPortraitBounds);
  }

  /// Rescales the detected face object bounding box and landmarks. This function can be useful if the image was downscaled to speed up detection, then you need to upscale the detected face to fit the source image size.
  ///
  /// param scale The multiplicative rescaling factor to apply to the face object.
  /// throws FaceException An error has occurred during Face Library execution.
  void rescale(double scale) {
    var err = faceSDK.id3DetectedFace_Rescale(_pHandle.value, scale);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Rotates the face object by a given angle in degrees from a given center.
  ///
  /// param angle Angle of the rotation to apply to the face object.
  /// param center Center of the rotation to apply to the face object.
  /// throws FaceException An error has occurred during Face Library execution.
  void rotate(int angle, Point center) {
    var err = faceSDK.id3DetectedFace_Rotate(_pHandle.value, angle, center.handle);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

  /// Exports the face object to a buffer.
  ///
  /// return The buffer to which the face object is exported.
  /// 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.id3DetectedFace_ToBuffer(_pHandle.value, pData, pDataSize);
      if (err == FaceError.insufficientBuffer.value) {
        pData = calloc.allocate(pDataSize.value);
        err = faceSDK.id3DetectedFace_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);
    }
  }

  /// Saves the face object to a file.
  ///
  /// param path Path to the file to export the face 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.id3DetectedFace_ToFile(_pHandle.value, pPath ?? nullptr);
      if (err != FaceError.success.value) {
        throw FaceException(err);
      }
    } finally {
      if (pPath != null) {
        calloc.free(pPath);
      }
    }
  }

  /// Translates the face object.
  ///
  /// param tx Translation to apply to the face object alongside the x-axis.
  /// param ty Translation to apply to the face object alongside the y-axis.
  /// throws FaceException An error has occurred during Face Library execution.
  void translate(int tx, int ty) {
    var err = faceSDK.id3DetectedFace_Translate(_pHandle.value, tx, ty);
    if (err != FaceError.success.value) {
      throw FaceException(err);
    }
  }

}

