package myhr.facescan.myhr_facescan;

import android.util.Log;

import com.luxand.FSDK;
import com.luxand.FSDK.HImage;

import java.io.InvalidObjectException;
import java.util.ArrayList;
import java.util.List;

import myhr.facescan.myhr_facescan.models.FaceMatch;
import myhr.facescan.myhr_facescan.models.FacePosition;
import myhr.facescan.myhr_facescan.models.FaceTemplate;

public class LuxandSdk {
    static FSDK.HTracker tracker;
    static final int CAMERA_ID = 0;
    static ELuxandMode mode = ELuxandMode.Detection;

    static LowPassFilter pan = new LowPassFilter();
    static LowPassFilter tilt = new LowPassFilter();

    static List<FaceTemplate> availableTemplates;

    public static int Initialize(String licenseKey) {
        int libCode = FSDK.ActivateLibrary(licenseKey);

        if (libCode == FSDK.FSDKE_OK) {
            libCode = FSDK.Initialize();
        }

        availableTemplates = new ArrayList<>();
        availableTemplates.add(new FaceTemplate("nax", new byte[]{1, 2}, new byte[]{1, 2}));

        return libCode;
    }

    public static int SetMatchTemplateMode() {
        int[] err_pos = new int[1];
        int libCode = FSDK.SetTrackerMultipleParameters(tracker,
                "RecognizeFaces=true;" +
                        "DetectFacialFeatures=true;" +
                        "HandleArbitraryRotations=false;" +
                        "DetermineFaceRotationAngle=false;" +
                        "InternalResizeWidth=100;" +
                        "FaceDetectionThreshold=5;" +
                        "DetectGender=false;" +
                        "DetectAge=false;" +
                        "DetectExpression=false;" +
                        "DetectAngles=false;", err_pos);

        if (err_pos[0] != 0) {
            Log.e("LUXAND", "Failed to SetTrackerMultipleParameters: " + err_pos[0]);
        }

        return libCode;
    }

    public static int SetTrackingMode() {
        int[] err_pos = new int[1];
        int libCode = FSDK.SetTrackerMultipleParameters(tracker,
                "RecognizeFaces=false;" +
                        "DetectFacialFeatures=true;" +
                        "HandleArbitraryRotations=false;" +
                        "DetermineFaceRotationAngle=true;" +
                        "InternalResizeWidth=100;" +
                        "FaceDetectionThreshold=5;" +
                        "DetectGender=false;" +
                        "DetectAge=false;" +
                        "DetectExpression=true;" +
                        "DetectAngles=true;", err_pos);

        if (err_pos[0] != 0) {
            Log.e("LUXAND", "Failed to SetTrackerMultipleParameters: " + err_pos[0]);
        }

        return libCode;
    }

    public static int SetDetectionMode() {
        int[] err_pos = new int[1];
        int libCode = FSDK.SetTrackerMultipleParameters(tracker,
                "RecognizeFaces=false;" +
                        "DetectFacialFeatures=true;" +
                        "HandleArbitraryRotations=true;" +
                        "DetermineFaceRotationAngle=false;" +
                        "InternalResizeWidth=500;" +
                        "FaceDetectionThreshold=5;" +
                        "DetectGender=false;" +
                        "DetectAge=false;" +
                        "DetectExpression=false;" +
                        "DetectAngles=false;", err_pos);

        if (err_pos[0] != 0) {
            Log.e("LUXAND", "Failed to SetTrackerMultipleParameters: " + err_pos[0]);
        }

        return libCode;
    }

    private static void ValidateTracker() {
        if (mode == ELuxandMode.Tracking) {
            return;
        }

        if (tracker == null) {
            tracker = new FSDK.HTracker();
            int libCode = FSDK.CreateTracker(tracker);

            if (libCode != FSDK.FSDKE_OK) {
                Log.e("LUXAND", "Failed to CreateTracker: " + libCode);
            }
        }

        mode = ELuxandMode.Tracking;
        SetTrackingMode();
    }

    private static void ValidateDetector() {
        if (mode == ELuxandMode.Detection) {
            return;
        }

        if (tracker != null) {
            FreeTracker();
        }

        mode = ELuxandMode.Detection;
        SetDetectionMode();
    }

    private static void ValidateMatchTemplate() {
        if (mode == ELuxandMode.MatchTemplate) {
            return;
        }

        if (tracker == null) {
            tracker = new FSDK.HTracker();
            int libCode = FSDK.CreateTracker(tracker);

            if (libCode != FSDK.FSDKE_OK) {
                Log.e("LUXAND", "Failed to CreateTracker: " + libCode);
            }
        }

        mode = ELuxandMode.MatchTemplate;
        SetMatchTemplateMode();
    }

    public static void FreeTracker() {
        if (tracker == null) {
            return;
        }

        int libCode = FSDK.ClearTracker(tracker);
        tracker = null;
        mode = ELuxandMode.Detection;

        if (libCode != FSDK.FSDKE_OK) {
            Log.e("LUXAND", "Failed to FreeTracker: " + libCode);
        } else {
            Log.e("LUXAND", "=== TRACKER CLEARED ===");
        }
    }

    public static void SetAvailableTempalte(List<FaceTemplate> templates) {
        availableTemplates = templates;
    }

    public static List<FaceMatch> TrackFaceForMatchTemplate(byte[] buffer, List<FaceTemplate> templates) {
        ValidateMatchTemplate();

        List<FaceMatch> result = new ArrayList<>();
        HImage image = new HImage();
        int libCode = FSDK.LoadImageFromJpegBuffer(image, buffer, buffer.length);

        if (libCode != FSDK.FSDKE_OK) {
            Log.e("LUXAND", "Failed to load image: " + libCode);
            return result;
        }

        long[] faceCount = new long[5];
        long[] ids = new long[5];
        libCode = FSDK.FeedFrame(tracker, CAMERA_ID, image, faceCount, ids);

        if (libCode != FSDK.FSDKE_OK) {
            Log.e("LUXAND", "Failed to feed frame: " + libCode);
            FSDK.FreeImage(image);
            return result;
        }

        FSDK.TFacePosition pos = new FSDK.TFacePosition();

        for (int i = 0; i < ids.length; i++) {
            if (ids[i] == 0) {
                continue;
            }

            libCode = FSDK.GetTrackerFacePosition(tracker, CAMERA_ID, ids[i], pos);

            if (libCode != FSDK.FSDKE_OK) {
                Log.e("LUXAND", "Failed to get tracker face position: " + libCode);
                continue;
            }

//            FSDK.FSDK_Features eyes = new FSDK.FSDK_Features();
//            libCode = FSDK.DetectEyes(image, eyes);
//
//            if (libCode != FSDK.FSDKE_OK) {
//                Log.e("LUXAND", "Cannot detect eye: " + libCode);
//                continue;
//            }
//
//            FSDK.FSDK_FaceTemplate eyeTemplate = new FSDK.FSDK_FaceTemplate();
//            FSDK.GetFaceTemplateUsingEyes(image, eyes, eyeTemplate);

//            FSDK.FSDK_Features face = new FSDK.FSDK_Features();
//            libCode = FSDK.DetectFacialFeaturesInRegion(image, pos, face);
//
//            if (libCode != FSDK.FSDKE_OK) {
//                Log.e("LUXAND", "Cannot DetectFacialFeaturesInRegion: " + libCode);
//                continue;
//            }

            FSDK.FSDK_FaceTemplate faceTemplate = new FSDK.FSDK_FaceTemplate();
            libCode = FSDK.GetFaceTemplateInRegion(image, pos, faceTemplate);

            if (libCode != FSDK.FSDKE_OK) {
                Log.e("LUXAND", "Cannot GetFaceTemplateUsingFeatures: " + libCode);
                continue;
            }

            for (int j = 0; j < templates.size(); j++) {
                FSDK.FSDK_FaceTemplate t = new FSDK.FSDK_FaceTemplate();
//                t.template = availableTemplates.get(j).EyeTemplate;
//                float[] similarityEye = new float[1];
//                libCode = FSDK.MatchFaces(eyeTemplate, t, similarityEye);
//
//                if (libCode != FSDK.FSDKE_OK) {
//                    Log.e("LUXAND", "Cannot MatchFaces for eye: " + libCode);
//                    continue;
//                }

                t.template = templates.get(i).FaceTemplate;
                float[] similarityFace = new float[1];
                libCode = FSDK.MatchFaces(faceTemplate, t, similarityFace);

                if (libCode == FSDK.FSDKE_OK) {
                    result.add(new FaceMatch(
                            new FacePosition(pos.xc, pos.yc, pos.w),
                            templates.get(i).Id,
                            0,
                            similarityFace[0]));
                    break;
                } else {
                    Log.e("LUXAND", "Cannot MatchFaces for face: " + libCode);
                }
            }
        }

        FSDK.FreeImage(image);

        return result;
    }

    public static int[] TrackFace(byte[] buffer) {
        ValidateTracker();

        int[] result = new int[3];
        HImage image = new HImage();
        int libCode = FSDK.LoadImageFromJpegBuffer(image, buffer, buffer.length);

        if (libCode != FSDK.FSDKE_OK) {
            Log.e("LUXAND", "Failed to load image: " + libCode);
            return result;
        }

        long[] faceCount = new long[5];
        long[] ids = new long[5];
        libCode = FSDK.FeedFrame(tracker, CAMERA_ID, image, faceCount, ids);
        FSDK.FreeImage(image);

        if (libCode != FSDK.FSDKE_OK) {
            Log.e("LUXAND", "Failed to feed frame: " + libCode);
            return result;
        }

        FSDK.TFacePosition pos = new FSDK.TFacePosition();
        libCode = FSDK.GetTrackerFacePosition(tracker, CAMERA_ID, ids[0], pos);

        if (libCode != FSDK.FSDKE_OK) {
            Log.e("LUXAND", "Failed to get tracker face position: " + libCode);
            return result;
        }

        result[0] = pos.xc;
        result[1] = pos.yc;
        result[2] = pos.w;

//        String sAttr[] = new String[]{""};
//        libCode = FSDK.GetTrackerFacialAttribute(tracker, 0, ids[0], "Age", sAttr, 256);
//
//        if (libCode == FSDK.FSDKE_OK) {
//            result[3] = (byte) Math.round(Double.parseDouble(sAttr[0].split(";")[0].split("=")[1]));
//        } else {
//            Log.i("Failed to GetTrackerFacialAttribute Age: ", String.valueOf(libCode));
//        }
//
//        libCode = FSDK.GetTrackerFacialAttribute(tracker, 0, ids[0], "Gender", sAttr, 256);
//
//        if (libCode == FSDK.FSDKE_OK) {
//            result[4] = Double.parseDouble(sAttr[0].split(";")[0].split("=")[1]) > 0.8 ? 1 : 2;
//        } else {
//            Log.i("Failed to GetTrackerFacialAttribute Gender: ", String.valueOf(libCode));
//        }

        return result;
    }

    public static double[] GetFaceAttribute(byte[] buffer, Boolean isExpression) {
        ValidateTracker();

        double[] result = new double[2];
        HImage image = new HImage();
        int libCode = FSDK.LoadImageFromJpegBuffer(image, buffer, buffer.length);

        if (libCode != FSDK.FSDKE_OK) {
            Log.e("LUXAND", "Failed to load image: " + libCode);
            return result;
        }

        long[] faceCount = new long[5];
        long[] ids = new long[5];
        libCode = FSDK.FeedFrame(tracker, CAMERA_ID, image, faceCount, ids);
        FSDK.FreeImage(image);

        if (libCode != FSDK.FSDKE_OK) {
            Log.e("LUXAND", "Failed to feed frame: " + libCode);
            return result;
        }

        String sAttr[] = new String[]{""};
        libCode = FSDK.GetTrackerFacialAttribute(tracker, 0, ids[0], isExpression ? "Expression" : "Angles", sAttr, 256);

        if (libCode != FSDK.FSDKE_OK) {
            Log.e("LUXAND", "Failed to GetTrackerFacialAttribute: " + libCode);
            return result;
        }

        String attr[] = sAttr[0].split(";");

        if (isExpression) {
            result[0] = Double.parseDouble(attr[0].split("=")[1]);
            result[1] = Double.parseDouble(attr[1].split("=")[1]);
        } else {
            result[0] = pan.pass(Float.parseFloat(attr[1].split("=")[1]));
            result[1] = tilt.pass(Float.parseFloat(attr[2].split("=")[1]));
        }

        return result;
    }

    public static byte[][] GetTemplate(byte[] buffer) {
        ValidateDetector();

        byte[][] templates = new byte[2][];
        HImage image = new HImage();
        int libCode = FSDK.LoadImageFromJpegBuffer(image, buffer, buffer.length);

        if (libCode != FSDK.FSDKE_OK) {
            Log.e("LUXAND", "Failed to load image: " + libCode);
            return templates;
        }

        FSDK.FSDK_Features eyes = new FSDK.FSDK_Features();
        libCode = FSDK.DetectEyes(image, eyes);

        if (libCode != FSDK.FSDKE_OK) {
            Log.e("LUXAND", "Cannot detect eye: " + libCode);
            return templates;
        }

        FSDK.FSDK_FaceTemplate ft = new FSDK.FSDK_FaceTemplate();
        FSDK.GetFaceTemplateUsingEyes(image, eyes, ft);
        templates[0] = ft.template;

        FSDK.FSDK_Features face = new FSDK.FSDK_Features();
        libCode = FSDK.DetectFacialFeatures(image, face);

        if (libCode != FSDK.FSDKE_OK) {
            Log.e("LUXAND", "Cannot detect face: " + libCode);
            return templates;
        }

        ft = new FSDK.FSDK_FaceTemplate();
        FSDK.GetFaceTemplateUsingFeatures(image, face, ft);
        templates[1] = ft.template;

        FSDK.FreeImage(image);

        return templates;
    }
}

class LowPassFilter {

    private float a;
    private Float y = null;

    public LowPassFilter() {
        a = 0.325f;
    }

    public float pass(final float x) {
        y = a * x + (1 - a) * (y == null ? x : y);
        return y;
    }

}

enum ELuxandMode {
    Detection,
    Tracking,
    MatchTemplate
}