import 'dart:ffi';
import 'dart:typed_data';

import 'package:camera/camera.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:myhr_facescan/models/face_match.dart';
import 'package:myhr_facescan/models/face_position.dart';
import 'package:myhr_facescan/models/face_template.dart';

class LuxandController extends GetxController {
  var platform = const MethodChannel("myhr_facescan");
  var isInitialized = false;

  final String licenseKey;

  LuxandController({required this.licenseKey});

  initialize() async {
    if (isInitialized) {
      return;
    }

    DynamicLibrary.open("libfsdk.so");

    int result = await platform
        .invokeMethod("activateLuxand", {'licenseKey': licenseKey});

    if (result == 0) {
      isInitialized = true;
    } else {
      print('CANNOT INITIALIZE LUXAND: ${result}');
    }
  }

  freeResource() async {
    await platform.invokeMethod<bool>("freeResource");
  }

  Future<FacePosition?> detectFace(CameraImage image) async {
    List<int> strides = Int32List(image.planes.length * 2);
    int index = 0;
    final bytes = image.planes.map((plane) {
      strides[index] = (plane.bytesPerRow);
      index++;
      strides[index] = (plane.bytesPerPixel)!;
      index++;
      return plane.bytes;
    }).toList();

    var result = await platform.invokeMethod<List<int>>("detectFace", {
      'platforms': bytes,
      'height': image.height,
      'width': image.width,
      'strides': strides
    });

    if (result == null || result[0] <= 0) {
      return null;
    }

    return FacePosition(x: result[0], y: result[1], width: result[2]);
  }

  Future<List<Uint8List>> getFaceTemplate(CameraImage image) async {
    List<int> strides = Int32List(image.planes.length * 2);
    int index = 0;
    final bytes = image.planes.map((plane) {
      strides[index] = (plane.bytesPerRow);
      index++;
      strides[index] = (plane.bytesPerPixel)!;
      index++;
      return plane.bytes;
    }).toList();

    var data =
        await platform.invokeMethod<Map<Object?, Object?>>("getFaceTemplate", {
      'platforms': bytes,
      'height': image.height,
      'width': image.width,
      'strides': strides
    });

    if (data!['face'] == null) {
      return [];
    }

    return [data['eye'] as Uint8List, data['face'] as Uint8List];
  }

  Future<List<double>> getFaceAttribute(
      CameraImage image, bool isExpression) async {
    List<int> strides = Int32List(image.planes.length * 2);
    int index = 0;
    final bytes = image.planes.map((plane) {
      strides[index] = (plane.bytesPerRow);
      index++;
      strides[index] = (plane.bytesPerPixel)!;
      index++;
      return plane.bytes;
    }).toList();

    var data = await platform.invokeMethod<List<double>>("getFaceAttribute", {
      'platforms': bytes,
      'height': image.height,
      'width': image.width,
      'strides': strides,
      'isExpression': isExpression
    });

    if (data == null) {
      return [];
    }

    return data;
  }

  Future setAvailableTemplate(List<FaceTemplate> templates) async {
    var ids = templates.map((e) => e.id).toList();
    var eyes = templates.map((e) => e.eye).toList();
    var faces = templates.map((e) => e.face).toList();

    await platform.invokeMethod<bool>("setAvailableTemplate", {
      'ids': ids,
      'eyes': eyes,
      'faces': faces,
    });
  }

  Future<List<FaceMatch>> detectFaceForMatch(
      CameraImage image, List<FaceTemplate> templates) async {
    List<int> strides = Int32List(image.planes.length * 2);
    int index = 0;
    final bytes = image.planes.map((plane) {
      strides[index] = (plane.bytesPerRow);
      index++;
      strides[index] = (plane.bytesPerPixel)!;
      index++;
      return plane.bytes;
    }).toList();

    var ids = templates.map((e) => e.id).toList();
    var eyes = templates.map((e) => e.eye).toList();
    var faces = templates.map((e) => e.face).toList();

    var data =
        await platform.invokeMethod<List<Object?>>("detectFaceForMatch", {
      'platforms': bytes,
      'height': image.height,
      'width': image.width,
      'strides': strides,
      'ids': ids,
      'eyes': eyes,
      'faces': faces,
    });

    var result = <FaceMatch>[];

    if (data == null) {
      return result;
    }

    for (var d in data) {
      var items = d.toString().split('(,)');
      result.add(FaceMatch(
          id: items[0],
          similarityEye: double.parse(items[1]),
          similarityFace: double.parse(items[2]),
          position: FacePosition(
              x: int.parse(items[3]),
              y: int.parse(items[4]),
              width: int.parse(items[5]))));
    }

    return result;
  }
}
