package id.co.gtc.erhacam;

import Config.SomeCodes;
import lombok.NonNull;
import org.bytedeco.opencv.opencv_core.*;
import org.bytedeco.opencv.opencv_objdetect.CascadeClassifier;
import org.tinylog.Logger;

import java.util.ArrayList;
import java.util.List;

import static Config.SomeCodes.IsInsideRect;

public class Detectors {
    public static CascadeClassifier frontalfaceDetector;
    private static CascadeClassifier eyeDetector;
    private static CascadeClassifier profilefaceDetector;
    //private static CascadeClassifier palmDetector;
    //private static CascadeClassifier fistDetector;

    private static double scaleFactor = 1.1;
    private final static int minNeighbors = 3;
    private final  static int flags = 0;
    private static Size FaceminSize;
    private static Size FacemaxSize;

    private static final int EyetoFaceRatio = 6;
    private static Size EyeminSize;
    private static Size EyemaxSize;

    public static void LoadAllDetectors(){

        LoadFrontalFaceDetector();
        LoadEyeDetector();

        LoadProfileFaceDetector();


        //LoadFistDetector();
        //LoadRightPalmDetector();
    }

//    private static void LoadFistDetector(){
//        String filename = SomeCodes.ExtractResource("/fist.xml");
//        if (filename!=null) {
//            Logger.info("Fist Detector file : " + filename);
//            if (fistDetector ==null) {
//                try{
//
//                    fistDetector = new CascadeClassifier(filename);
//                    Logger.info("FistDetector loaded");
//                } catch (Exception e){
//                    Logger.error("Exception on loading FistDetector : " + e.getMessage());
//                }
//
//            } else Logger.info("FistDetector already loaded");
//        } else Logger.error("Unable to extract fist detector file");
//    }

//    private static void LoadRightPalmDetector(){
//        String filename = SomeCodes.ExtractResource("/rpalm.xml");
//        if (filename!=null) {
//            Logger.info("Right Palm Detector file : " + filename);
//            if (palmDetector ==null) {
//                try{
//
//                    palmDetector = new CascadeClassifier(filename);
//                    Logger.info("RightPalmDetector loaded");
//                } catch (Exception e){
//                    Logger.error("Exception on loading RightPalmDetector : " + e.getMessage());
//                }
//
//            } else Logger.info("RightPalmDetector already loaded");
//        } else Logger.error("Unable to extract right palm detector file");
//    }

    private static void LoadFrontalFaceDetector(){
        String filename = SomeCodes.ExtractResource("/haarcascade_frontalface_alt.xml");
        if (filename!=null) {
            Logger.info("Face Detector file : " + filename);
            if (frontalfaceDetector==null) {
                try{
                    frontalfaceDetector = new CascadeClassifier(filename);
                    Logger.info("FaceDetector loaded");
                } catch (Exception e){
                    Logger.error("Exception on loading FaceDetector : " + e.getMessage());
                }

            } else Logger.info("FaceDetector already loaded");
        } else Logger.error("Unable to extract face detector file");
    }

    private static void LoadProfileFaceDetector(){
        String filename = SomeCodes.ExtractResource("/haarcascade_profileface.xml");
        if (filename!=null) {
            Logger.info("Profile Face Detector file : " + filename);
            if (profilefaceDetector==null) {
                try{
                    profilefaceDetector = new CascadeClassifier(filename);
                    Logger.info("ProfileFaceDetector loaded");
                } catch (Exception e){
                    Logger.error("Exception on loading ProfileFaceDetector : " + e.getMessage());
                }

            } else Logger.info("ProfileFaceDetector already loaded");
        } else Logger.error("Unable to extract profile face detector file");
    }

    private static void LoadEyeDetector(){
        String filename = SomeCodes.ExtractResource("/haarcascade_eye.xml");
        if (filename!=null) {
            Logger.info("Eye Detector file : " + filename);
            if (eyeDetector==null) {
                try{

                    eyeDetector = new CascadeClassifier(filename);
                    Logger.info("EyeDetector loaded");
                } catch (Exception e){
                    Logger.error("Exception on loading EyeDetector : " + e.getMessage());
                }

            } else Logger.info("EyeDetector already loaded");
        } else Logger.error("Unable to extract eye detector file");
    }






//    public static boolean HavePalm(UMat graymat){
//        RectVector palms = Detect(graymat, palmDetector);
//        return palms!=null && palms.size()>0;
//    }
//
//    public static boolean HaveFist(UMat graymat){
//        RectVector fists = Detect(graymat, fistDetector);
//        return fists!=null && fists.size()>0;
//    }


    /**
     * Detect if there is a frontal face, containing 2 eyes
     * @param graymat Mat in Gray Scale
     * @return List of Rect if face detected, otherwise empty list
     */
    public static @NonNull List<DetectorResult> HaveFrontalFace(UMat graymat){
        List<DetectorResult> result = new ArrayList<>();
        RectVector faces = DetectFrontalFace(graymat);
        if (faces!=null && faces.size()>0){

            for(Rect face : faces.get()){
                RectVector eyes = DetectEye(graymat);
                DetectorResult dr = new DetectorResult();
                dr.setFace(face);
                if (eyes!=null && eyes.size()>=2){
                    for(Rect eye : eyes.get()){
                        if (IsInsideRect(eye, face)) dr.AddEye(eye);
                    }
                }
                result.add(dr);
            }
        }
        return result;
    }

    public static @NonNull List<DetectorResult> HaveLeft45Face(UMat graymat){
        List<DetectorResult> result = new ArrayList<>();
        RectVector faces = DetectProfileFace(graymat);
        if (faces!=null && faces.size()>0){

            for(Rect face : faces.get()){
                RectVector eyes = DetectEye(graymat);
                DetectorResult dr = new DetectorResult();
                dr.setFace(face);
                if (eyes!=null && eyes.size()>0){
                    for(Rect eye : eyes.get()){
                        if (IsInsideRect(eye, face)) dr.AddEye(eye);
                    }
                }
                result.add(dr);
            }
        }
        return result;
    }




    public static void setScaleFactor(double value){
        if (scaleFactor!=value) scaleFactor = value;
    }

    public static void setFaceMinSize(int value){
        if (FaceminSize!=null){
            if (FaceminSize.width()!=value || FaceminSize.height()!=value) {
                FaceminSize = new Size(value, value);
                EyeminSize = new Size(value/EyetoFaceRatio, value/EyetoFaceRatio);
                Logger.info("FaceMinSize changed to : " + FaceminSize.width());
                Logger.info("EyeMinSize changed to : " + EyeminSize.width());
            }
        } else {
            FaceminSize = new Size(value, value);
            EyeminSize = new Size(value/EyetoFaceRatio, value/EyetoFaceRatio);
            Logger.info("FaceMinSize created with value : " + FaceminSize.width());
            Logger.info("EyeMinSize created with value : " + EyeminSize.width());
        }

    }

    public static void setFaceMaxSize(int value){
        if (FacemaxSize!=null){
            if (FacemaxSize.width()!=value || FacemaxSize.height()!=value) {
                FacemaxSize = new Size(value, value);
                EyemaxSize = new Size(value/EyetoFaceRatio, value/EyetoFaceRatio);
                Logger.info("FaceMaxSize changed to : " + FacemaxSize.width());
                Logger.info("EyeMaxSize changed to : " + EyemaxSize.width());
            }
        } else {
            FacemaxSize = new Size(value, value);
            EyemaxSize = new Size(value/EyetoFaceRatio, value/EyetoFaceRatio);
            Logger.info("FaceMaxSize created with value : " + FacemaxSize.width());
            Logger.info("EyeMaxSize created with value : " + EyemaxSize.width());
        }

    }


    public static RectVector DetectProfileFace(UMat graymat){
        return Detect(graymat, profilefaceDetector, scaleFactor, minNeighbors, flags, FaceminSize, FacemaxSize);
    }

    public static RectVector DetectFrontalFace(Mat graymat, int minsize, int maxsize){
        Size min = new Size(minsize, minsize);
        Size max = new Size(maxsize, maxsize);
        RectVector rect = new RectVector();
        frontalfaceDetector.detectMultiScale(graymat, rect,scaleFactor, minNeighbors, flags, min, max);
        return rect;
    }

    public static RectVector DetectProfileFace(Mat graymat, int minsize, int maxsize){
        Size min = new Size(minsize, minsize);
        Size max = new Size(maxsize, maxsize);
        RectVector rect = new RectVector();
        profilefaceDetector.detectMultiScale(graymat, rect,scaleFactor, minNeighbors, flags, min, max);
        return rect;
    }


    /**
     * Detect Face from Mat
     * @param graymat Mat in Gray Scale
     * @return RectVector if face detected, otherwise null
     */
    public static RectVector DetectFrontalFace(UMat graymat){
        return Detect(graymat, frontalfaceDetector, scaleFactor, minNeighbors, flags, FaceminSize, FacemaxSize);
    }




    /**
     * Detect Eye from Mat
     * If Eye detected, it will return RectVector with size is number of eyes detected
     * @param graymat Mat in Gray Scale
     * @return RectVector if eye detected, otherwise null
     */
    public static RectVector DetectEye(UMat graymat){
        //return Detect(graymat, eyeDetector, scaleFactor, minNeighbors, flags, EyeminSize, EyemaxSize);
        return Detect(graymat, eyeDetector);
    }




    @SuppressWarnings("unused")
    private static RectVector Detect(UMat graymat, CascadeClassifier detector){
        if (detector!=null){
            if (graymat!=null){
                if (!graymat.empty()){
                    RectVector detected = new RectVector();
                    detector.detectMultiScale(graymat, detected);
                    return detected;
                }
            }

        }
        return null;
    }

    @SuppressWarnings("SameParameterValue")
    private static RectVector Detect(UMat graymat, CascadeClassifier detector, double scaleFactor, int minNeighbors, int flags, Size minSize, Size maxSize){
        if (detector!=null){
            if (graymat!=null){
                if (!graymat.empty()){
                    if (minSize!=null){
                        if (maxSize!=null){
                            RectVector detected = new RectVector();
                            detector.detectMultiScale(graymat, detected, scaleFactor, minNeighbors, flags, minSize, maxSize);
                            return detected;
                        }
                    }

                }
            }

        }
        return null;
    }



}
