package id.co.gtc.erhacam;

import BASS.AudioPlayer;
import BASS.PlaybackStatus;
import Camera.*;
import Config.CameraConfigEnum;
import Config.SomeCodes;
import Database.PhotoReviewClass;
import Database.Sqlite;
import ErhaAPI.ErhaAPI;
import ErhaAPI.BarcodeResullt;
import ErhaAPI.PhotoResult;
import ErhaAPI.PatientRecord;
import ErhaAPI.UploadResult;

import javafx.application.Platform;

import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.AnchorPane;
import javafx.stage.DirectoryChooser;


import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;

import javafx.scene.control.Alert.AlertType;
import org.bytedeco.javacv.OpenCVFrameGrabber;
import org.bytedeco.javacv.VideoInputFrameGrabber;
import org.bytedeco.opencv.opencv_core.Rect;
import org.bytedeco.opencv.opencv_core.Size;
import org.tinylog.Logger;

import static Config.SomeCodes.*;

public class CaptureView {

    @FXML
    private AnchorPane CaptureViewAnchor;

    @FXML
    private AnchorPane cam1;
    @FXML
    private AnchorPane cam2;
    @FXML
    private AnchorPane cam3;
    @FXML
    private AnchorPane cam4;
    @FXML
    private AnchorPane cam5;
    @FXML
    private AnchorPane controlpane;

    private Cameradetail image1, image2, image3, image4, image5;

    @FXML
    private TextArea barcodeData;
    @FXML
    private TextArea medicalRecordID;
    @FXML
    private TextArea PatientName;

    @FXML
    private Button TakePhotoButton;

    private AudioPlayer audioPlayer;
    private String audio_posisikan_muka = "posisikan_wajah.wav";
    private String audio_scan_barcode = "scan_barcode.wav";
    private String audio_pengambilan_gagal = "pengambilan_gagal.wav";
    private String audio_pengambilan_berhasil = "pengambilan_berhasil.wav";
    private String audio_upload_gagal = "upload_gagal.wav";
    private String audio_countdown = "countdown321.wav";
    private String audio_camera_shutter = "camera-shutter-click-01.wav";
    private String audio_tahan_posisi = "tahan_posisi.wav";
    private String audio_data_barcode_tidak_ditemukan = "data_barcode_tidak_ditemukan.wav";
    private String audio_hubungi_staf_kami = "hubungistafkami.wav";
    private List<String> cams;

    private final AtomicBoolean[] have_face = new AtomicBoolean[5];
    private final AtomicBoolean[] have_eyes = new AtomicBoolean[5];
    private final AtomicBoolean[] have_profile = new AtomicBoolean[5];

    private final AtomicBoolean isTakingPhoto = new AtomicBoolean(false);

    private final ErhaAPI erhaAPI = new ErhaAPI(false);

    private void TakePhotoButtonChange(){
        boolean disabled = false;
        String barcode = barcodeData.getText();
        String medicalrecord = medicalRecordID.getText();
        String patientname = PatientName.getText();
        if (barcode.isBlank()) disabled = true;
        if (medicalrecord.isBlank()) disabled = true;
        if (patientname.isBlank()) disabled = true;
        TakePhotoButton.setDisable(disabled);
    }

    @FXML
    private void ChangeDirectory(){
        DirectoryChooser dc = new DirectoryChooser();
        dc.setTitle("Select Directory");
        String path = dc.showDialog(null).getAbsolutePath();

        config.SetPhotoDirectory(path);
        config.Save();
    }

    private void trigger_autofocus(Cameradetail image) throws InterruptedException {
        if (image!=null){
            if (image.isCapturing()){
                image.setAutoFocus(false);
                Thread.sleep(5);
                image.setFocus(0.7);
                Thread.sleep(5);
                image.setAutoFocus(true);
                Thread.sleep(5);
            }
        }
    }

    @FXML
    private void AutoFocus() throws InterruptedException {
        trigger_autofocus(image1);
        trigger_autofocus(image2);
        trigger_autofocus(image3);
        trigger_autofocus(image4);
        trigger_autofocus(image5);
    }



    @FXML
    private void TakePhotos(){
        String directory = config.getPhotoDirectory();
        String prefix = RemoveSpaces(medicalRecordID.getText()) ;
        boolean has_face = Arrays.stream(have_face).anyMatch(AtomicBoolean::get);
        if (ValidDirectory(directory)){
            if (ValidMedicalRecordId(prefix)){
                isTakingPhoto.set(true);
                if (has_face){
                    AutoCloseAlert.show("Pengambilan Foto", "Tahan Posisi Anda", "Proses ini kurang lebih 3 detik", 5, null);
                    if (audioPlayer!=null && audioPlayer.isInited()){
                        if (!Objects.equals(audioPlayer.getCurrentFile(), audio_tahan_posisi)){
                            audioPlayer.StopCurrentPlayback();
                            audioPlayer.PlayFile(audio_tahan_posisi, new PlaybackStatus() {
                                @Override
                                public void onPlaybackStarted(String filename) {

                                }

                                @Override
                                public void onPlaybackFinished(String filename) {


                                    audioPlayer.PlayFile(audio_countdown, new PlaybackStatus() {
                                        @Override
                                        public void onPlaybackStarted(String filename) {

                                        }

                                        @Override
                                        public void onPlaybackFinished(String filename) {
                                            take_photo_lanjutan(directory, prefix);
                                        }

                                        @Override
                                        public void onPlaybackFailure(String filename) {

                                        }
                                    });


                                }

                                @Override
                                public void onPlaybackFailure(String filename) {

                                }
                            });

                        }
                    }

                } else {
                    AutoCloseAlert.show("Error", "No Face Detected", "No Face Detected", 5, null);
                    isTakingPhoto.set(false);
                    if (audioPlayer!=null && audioPlayer.isInited()){
                        if (!Objects.equals(audioPlayer.getCurrentFile(),audio_posisikan_muka)) {
                            audioPlayer.StopCurrentPlayback();
                            audioPlayer.PlayFile(audio_posisikan_muka, null);
                        }
                    }

                }
            } else {
                System.out.println("Prefix invalid, not taking photo");
                isTakingPhoto.set(false);
                //AutoCloseAlert.show("QR Code Not Available", "", "Please scan QR before continue", 5, s -> AutoCloseAlert.show("Scan Barcode", "Silahkan Scan Barcode Anda", "Arahkan kertas barcode ke kamera", 0, null));
                if (!Objects.equals(AutoCloseAlert.shownBanner, AutoCloseAlert.banner_01)){
                    if (AutoCloseAlert.banner_01!=null){
                        System.out.println("Showing banner 01 because prefix invalid");
                        AutoCloseAlert.showbanner(AutoCloseAlert.banner_01,0,null);
                    }
                }
                if (audioPlayer!=null && audioPlayer.isInited()){
                    if (!Objects.equals(audioPlayer.getCurrentFile(), audio_scan_barcode)) {
                        audioPlayer.StopCurrentPlayback();
                        audioPlayer.PlayFile(audio_scan_barcode, null);
                    }
                }
            }
        } else {
            System.out.println("Photo Directory invalid, not taking photo");
            isTakingPhoto.set(false);
            AutoCloseAlert.show("Invalid Photo Directory","Photo Directory not set", "Please set photo directory at Setting", 5, s -> AutoCloseAlert.show("Setting", "Setting", "Please set photo directory", 0, null));
        }


    }

    private void take_photo_lanjutan(String directory, String prefix){
        audioPlayer.PlayFile(audio_camera_shutter, null);
        Size thumbsize = new Size(160,120);

        PhotoReviewClass prc = new PhotoReviewClass();
        prc.setPrefix(prefix);

        long nanostart = System.nanoTime(); // for performance measurement

        ExecutorService executor = Executors.newFixedThreadPool(5);

        Callable<PhotoResult> task1 = ()->{
            if (image1!=null) {
                image1.RemapROI(0.1,0.3, false);

                double sharpness = CalculateSharpness(image1.getGrayMat());
                image1.setSharpness_indicator(sharpness);
                PhotoResult p1 = image1.TakePhoto(directory,prefix);
                p1.setSharpscore(sharpness);
                if (ValidFile(p1.getFullres())){
                    if (ValidFile(p1.getCompressedfile())){
                        return p1;
                    }
                }
            } else System.out.println("Image1 is null");
            return null;
        };
        Callable<PhotoResult> task2 = ()->{
            if (image2!=null) {
                image2.RemapROI(0.1,0.3, false);
                double sharpness = CalculateSharpness(image2.getGrayMat());
                image2.setSharpness_indicator(sharpness);
                PhotoResult p2 = image2.TakePhoto(directory,prefix);
                p2.setSharpscore(sharpness);
                if (ValidFile(p2.getFullres())){
                    if (ValidFile(p2.getCompressedfile())){
                        return p2;
                    }
                }
            } else System.out.println("Image2 is null");
            return null;
        };

        Callable<PhotoResult> task3 = ()->{
            if (image3!=null) {
                image3.RemapROI(0.1,0.3, false);
                double sharpness = CalculateSharpness(image3.getGrayMat());
                image3.setSharpness_indicator(sharpness);
                PhotoResult p3 = image3.TakePhoto(directory,prefix);
                p3.setSharpscore(sharpness);

                if (ValidFile(p3.getFullres())){
                    if (ValidFile(p3.getCompressedfile())){
                        return p3;
                    }
                }

            } else System.out.println("Image3 is null");
            return null;
        };

        Callable<PhotoResult> task4 = ()->{
            if (image4!=null) {
                image4.RemapROI(0.1,0.3, false);
                double sharpness = CalculateSharpness(image4.getGrayMat());
                image4.setSharpness_indicator(sharpness);
                PhotoResult p4 = image4.TakePhoto(directory,prefix);
                p4.setSharpscore(sharpness);

                if (ValidFile(p4.getFullres())){
                    if (ValidFile(p4.getCompressedfile())){
                        return p4;
                    }
                }

            } else System.out.println("Image4 is null");
            return  null;
        };

        Callable<PhotoResult> task5 = ()->{
            if (image5!=null) {
                image5.RemapROI(0.1,0.3, false);
                double sharpness = CalculateSharpness(image5.getGrayMat());
                image5.setSharpness_indicator(sharpness);
                PhotoResult p5 = image5.TakePhoto(directory,prefix);
                p5.setSharpscore(sharpness);

                if (ValidFile(p5.getFullres())){
                    if (ValidFile(p5.getCompressedfile())){
                        return p5;
                    }
                }

            } else System.out.println("Image5 is null");
            return null;
        };

        PhotoResult p1 = null;
        PhotoResult p2 = null;
        PhotoResult p3 = null;
        PhotoResult p4 = null;
        PhotoResult p5 = null;
        try{
            Future<PhotoResult> f1 = executor.submit(task1);
            Future<PhotoResult> f2 = executor.submit(task2);
            Future<PhotoResult> f3 = executor.submit(task3);
            Future<PhotoResult> f4 = executor.submit(task4);
            Future<PhotoResult> f5 = executor.submit(task5);

            p1 = f1.get();
            p2 = f2.get();
            p3 = f3.get();
            p4 = f4.get();
            p5 = f5.get();

        } catch (Exception e){
            Logger.error("Error TakePhotos: " + e.getMessage());
        } finally {
            executor.shutdown();
        }



        String timestamp = SomeCodes.GetDateTimeString();
        System.out.println("Creating timestamp: "+timestamp);

        // check for blurred image
        double score1=-1, score2=-1, score3=-1, score4=-1, score5=-1;
        if (p1!=null){
            score1 = p1.getSharpscore();
        }
        if (p2!=null){
            score2 = p2.getSharpscore();
        }
        if (p3!=null){
            score3 = p3.getSharpscore();
        }
        if (p4!=null){
            score4 = p4.getSharpscore();
        }
        if (p5!=null){
            score5 = p5.getSharpscore();
        }
        System.out.println("Sharpness score: "+score1+", "+score2+", "+score3+", "+score4+", "+score5);
        double lowest = FindLowestValue(score1,score2,score3,score4,score5);
        if (lowest<=config.getSharpnessThreshold()){
            String culprit = "";
            if (lowest==score1) culprit = "camera 1";
            else if (lowest==score2) culprit = "camera 2";
            else if (lowest==score3) culprit = "camera 3";
            else if (lowest==score4) culprit = "camera 4";
            else if (lowest==score5) culprit = "camera 5";
            String lowest_string = String.format("%.2f", lowest);
            AutoCloseAlert.show("Take Photos Failed", "Blurred Image Detected", "Blurred Image Detected at "+culprit+" with sharpness score "+lowest_string, 5, null);
            isTakingPhoto.set(false);
            return;
        }


        Rect bestroi = null;
        Rect reducedroi = null;
        if (p1!=null && p1.getBestROI()!=null){
            bestroi = p1.getBestROI();
            reducedroi = p1.getReducedROI();
        }

        if (p2!=null && p2.getBestROI()!=null){
            if (bestroi==null)
                bestroi = p2.getBestROI();
            else if (p2.getBestROI().area()>bestroi.area())
                bestroi = p2.getBestROI();
            if (reducedroi==null)
                reducedroi = p2.getReducedROI();
            else if (p2.getReducedROI().area()>reducedroi.area())
                reducedroi = p2.getReducedROI();
        }
        if (p3!=null && p3.getBestROI()!=null){
            if (bestroi==null)
                bestroi = p3.getBestROI();
            else if (p3.getBestROI().area()>bestroi.area())
                bestroi = p3.getBestROI();
            if (reducedroi==null)
                reducedroi = p3.getReducedROI();
            else if (p3.getReducedROI().area()>reducedroi.area())
                reducedroi = p3.getReducedROI();
        }
        if (p4!=null && p4.getBestROI()!=null){
            if (bestroi==null)
                bestroi = p4.getBestROI();
            else if (p4.getBestROI().area()>bestroi.area())
                bestroi = p4.getBestROI();
            if (reducedroi==null)
                reducedroi = p4.getReducedROI();
            else if (p4.getReducedROI().area()>reducedroi.area())
                reducedroi = p4.getReducedROI();
        }
        if (p5!=null && p5.getBestROI()!=null){
            if (bestroi==null)
                bestroi = p5.getBestROI();
            else if (p5.getBestROI().area()>bestroi.area())
                bestroi = p5.getBestROI();
            if (reducedroi==null)
                reducedroi = p5.getReducedROI();
            else if (p5.getReducedROI().area()>reducedroi.area())
                reducedroi = p5.getReducedROI();
        }

        if (p1!=null){
            prc.setFileLeft90(ValidFile(p1.getFullres()) ? p1.getFullres() : "");
            prc.setCompressedLeft90(ValidFile(p1.getCompressedfile()) ? p1.getCompressedfile() : "");
            if (ValidFile(p1.getFullcrop())){
                // ada crop
                prc.setCroppedLeft90(p1.getFullcrop());
            } else {
                String xx = image1.CropBestMat(directory,prefix, bestroi);
                if (ValidFile(xx)) prc.setCroppedLeft90(xx);
            }
            if (ValidFile(p1.getCompressedcrop())){
                prc.setCompressedCropLeft90(p1.getCompressedcrop());
            } else {
                String xx =image1.CropReducedMat(directory,prefix, reducedroi);
                if (ValidFile(xx)) prc.setCompressedCropLeft90(xx);
            }
            String thumb1 = MakeThumbfile(p1.getFullres(), thumbsize);
            if (ValidFile(thumb1)) prc.setThumbLeft90(thumb1);
        }
        if (p2!=null){
            prc.setFileLeft45(ValidFile(p2.getFullres()) ? p2.getFullres() : "");
            prc.setCompressedLeft45(ValidFile(p2.getCompressedfile()) ? p2.getCompressedfile() : "");
            if (ValidFile(p2.getFullcrop())){
                // ada crop
                prc.setCroppedLeft45(p2.getFullcrop());
            } else {
                String xx = image2.CropBestMat(directory,prefix, bestroi);
                if (ValidFile(xx)) prc.setCroppedLeft45(xx);
            }
            if (ValidFile(p2.getCompressedcrop())){
                prc.setCompressedCropLeft45(p2.getCompressedcrop());
            } else {
                String xx = image2.CropReducedMat(directory,prefix, reducedroi);
                if (ValidFile(xx)) prc.setCompressedCropLeft45(xx);
            }
            String thumb2 = MakeThumbfile(p2.getFullres(), thumbsize);
            if (ValidFile(thumb2)) prc.setThumbLeft45(thumb2);
        }
        if (p3!=null){
            prc.setFileCenter(ValidFile(p3.getFullres()) ? p3.getFullres() : "");
            prc.setCompressedCenter(ValidFile(p3.getCompressedfile()) ? p3.getCompressedfile() : "");
            if (ValidFile(p3.getFullcrop())){
                // ada crop
                prc.setCroppedCenter(p3.getFullcrop());
            } else {
                String xx = image3.CropBestMat(directory,prefix, bestroi);
                if (ValidFile(xx)) prc.setCroppedCenter(xx);
            }
            if (ValidFile(p3.getCompressedcrop())){
                prc.setCompressedCropCenter(p3.getCompressedcrop());
            } else {
                String xx = image3.CropReducedMat(directory,prefix, reducedroi);
                if (ValidFile(xx)) prc.setCompressedCropCenter(xx);
            }
            String thumb3 = MakeThumbfile(p3.getFullres(), thumbsize);
            if (ValidFile(thumb3)) prc.setThumbCenter(thumb3);
        }
        if (p4!=null){
            prc.setFileRight45(ValidFile(p4.getFullres()) ? p4.getFullres() : "");
            prc.setCompressedRight45(ValidFile(p4.getCompressedfile()) ? p4.getCompressedfile() : "");
            if (ValidFile(p4.getFullcrop())){
                // ada crop
                prc.setCroppedRight45(p4.getFullcrop());
            } else {
                String xx = image4.CropBestMat(directory,prefix, bestroi);
                if (ValidFile(xx)) prc.setCroppedRight45(xx);
            }
            if (ValidFile(p4.getCompressedcrop())){
                prc.setCompressedCropRight45(p4.getCompressedcrop());
            } else {
                String xx = image4.CropReducedMat(directory,prefix, reducedroi);
                if (ValidFile(xx)) prc.setCompressedCropRight45(xx);
            }
            String thumb4 = MakeThumbfile(p4.getFullres(), thumbsize);
            if (ValidFile(thumb4)) prc.setThumbRight45(thumb4);
        }
        if (p5!=null){
            prc.setFileRight90(ValidFile(p5.getFullres()) ? p5.getFullres() : "");
            prc.setCompressedRight90(ValidFile(p5.getCompressedfile()) ? p5.getCompressedfile() : "");
            if (ValidFile(p5.getFullcrop())){
                // ada crop
                prc.setCroppedRight90(p5.getFullcrop());
            } else {
                String xx = image5.CropBestMat(directory,prefix, bestroi);
                if (ValidFile(xx)) prc.setCroppedRight90(xx);
            }
            if (ValidFile(p5.getCompressedcrop())){
                prc.setCompressedCropRight90(p5.getCompressedcrop());
            } else {
                String xx= image5.CropReducedMat(directory,prefix, reducedroi);
                if (ValidFile(xx)) prc.setCompressedCropRight90(xx);
            }
            String thumb5 = MakeThumbfile(p5.getFullres(), thumbsize);
            if (ValidFile(thumb5)) prc.setThumbRight90(thumb5);
        }

        long duration = (System.nanoTime() - nanostart) / 1000000; // in milliseconds
        System.out.println("TakePhotos duration: "+duration+" ms");

        //AutoCloseAlert.show("Photos Taken", "Photos Taken", "Photos Taken", 5, null);
        if (AutoCloseAlert.banner_02!=null) {
            System.out.println("Showing banner 02 after photo taken");
            AutoCloseAlert.showbanner(AutoCloseAlert.banner_02, 0, null);

        }

        String[] files = prc.compressed();
        if (files.length>0){
            if (audioPlayer!=null && audioPlayer.isInited()){
                if (!Objects.equals(audioPlayer.getCurrentFile(),audio_pengambilan_berhasil)){
                    audioPlayer.StopCurrentPlayback();
                    audioPlayer.PlayFile(audio_pengambilan_berhasil, new PlaybackStatus() {
                        @Override
                        public void onPlaybackStarted(String filename) {

                        }

                        @Override
                        public void onPlaybackFinished(String filename) {
                            UploadFiles(prc, prefix);
                        }

                        @Override
                        public void onPlaybackFailure(String filename) {

                        }

                    });
                }
            }
        } else {
            if (audioPlayer!=null && audioPlayer.isInited()){
                if (!Objects.equals(audioPlayer.getCurrentFile(),audio_pengambilan_gagal)){
                    audioPlayer.StopCurrentPlayback();
                    audioPlayer.PlayFile(audio_pengambilan_gagal, null);
                }
            }
        }
    }

    private void UploadFiles(PhotoReviewClass  prc, String prefix){
        String[] files = prc.compressed();
        if (files.length>0){
            InsertSQL(prc);

            Task<Void> uploadtask = new Task<>() {
                @Override
                protected Void call() {
                    int totalfiles = files.length;
                    int counter = 0;
                    for (String ff : files) {
                        UploadResult ur = erhaAPI.Upload_File(prefix, ff,true);
                        if (ur != null) {
                            if (ur.message.startsWith("Record has been created")) {
                                counter++;
                                updateMessage("Upload success for " + ff);
                            } else updateMessage("Upload failed for " + ff+", Message : "+ur.message);
                        } else updateMessage("Upload failed for " + ff+" because UploadResult is null");

                    }
                    if (counter == totalfiles) {
                        super.succeeded();
                    } else super.failed();
                    return null;
                }
            };



            uploadtask.messageProperty().addListener((obs, oldval, newval)-> {
                System.out.println("UploadTask message: "+newval);
                Logger.info(newval);
            });


            uploadtask.setOnSucceeded(e-> {
                System.out.println("UploadTask succeeded");
                Platform.runLater(()->{
                    AutoCloseAlert.close();
                    barcodeData.setText("");
                    medicalRecordID.setText("");
                    PatientName.setText("");
                    isTakingPhoto.set(false);
                });

            });

            uploadtask.setOnFailed(e-> {
                System.out.println("UploadTask failed");
                if (audioPlayer!=null && audioPlayer.isInited()){
                    if (!Objects.equals(audioPlayer.getCurrentFile(), audio_upload_gagal)){
                        audioPlayer.StopCurrentPlayback();
                        audioPlayer.PlayFile(audio_upload_gagal, null);
                    }
                }
                AutoCloseAlert.show("Upload Failed", "Upload Failed", "Upload Failed", 5, s -> {
                    isTakingPhoto.set(false);
                    Platform.runLater(()->{
                        barcodeData.setText("");
                        medicalRecordID.setText("");
                        PatientName.setText("");
                    });
                });
            });

            new Thread(uploadtask).start();

        } else ShowAlert(AlertType.ERROR, "Error", "No Photos Taken", "No Photos Taken, please check camera");
    }

    @FXML
    public void initialize(){
        audio_posisikan_muka = ExtractResource("/posisikan_wajah.wav");
        audio_pengambilan_berhasil = ExtractResource("/pengambilan_berhasil.wav");
        audio_pengambilan_gagal = ExtractResource("/pengambilan_gagal.wav");
        audio_scan_barcode = ExtractResource("/scan_barcode.wav");
        audio_upload_gagal = ExtractResource("/upload_gagal.wav");
        audio_countdown = ExtractResource("/countdown321.wav");
        audio_camera_shutter = ExtractResource("/camera-shutter-click-01.wav");
        audio_tahan_posisi = ExtractResource("/tahan_posisi.wav");
        audio_data_barcode_tidak_ditemukan = ExtractResource("/data_barcode_tidak_ditemukan.wav");
        audio_hubungi_staf_kami = ExtractResource("/hubungistafkami.wav");

        //tambahan 19/03/2025
        barcodeData.textProperty().addListener((observable, oldValue, newValue) -> {
            System.out.println("barcodeData changed from ["+oldValue+"] to ["+newValue+"]");
            if (ValidBarCode(newValue)){
                if (Objects.equals(AutoCloseAlert.banner_01, AutoCloseAlert.shownBanner)){
                    System.out.println("Close banner 01");
                    AutoCloseAlert.close();
                }
            } else {
                if (AutoCloseAlert.banner_01!=null){
                    System.out.println("Show banner 01");
                    AutoCloseAlert.showbanner(AutoCloseAlert.banner_01,0, null);
                }
            }
            TakePhotoButtonChange();
        });
        medicalRecordID.textProperty().addListener((observable, oldValue, newValue) -> TakePhotoButtonChange());
        PatientName.textProperty().addListener((observable, oldValue, newValue) -> TakePhotoButtonChange());

        barcodeData.setText("");

        audioPlayer = new AudioPlayer(1,48000);
        Logger.info("Audio Player : "+(audioPlayer.isInited()? "Inited" : "Not Inited"));

        cams = null;
        try{
            String[] xxx = VideoInputFrameGrabber.getDeviceDescriptions();
            if (xxx!=null && xxx.length>0){
                cams = Arrays.asList(xxx);
            }
        } catch (Exception e){
            Logger.error("Error getting camera list: "+e.getMessage());
        }



        LoadCameraDetail(cam1, 1, CameraConfigEnum.CameraConfigLeft90);
        LoadCameraDetail(cam2, 2, CameraConfigEnum.CameraConfigLeft45);
        LoadCameraDetail(cam3, 3, CameraConfigEnum.CameraConfigCenter);
        LoadCameraDetail(cam4, 4, CameraConfigEnum.CameraConfigRight45);
        LoadCameraDetail(cam5, 5, CameraConfigEnum.CameraConfigRight90);

        Platform.runLater(()->{
            int indexleft90=-1;
            int indexleft45=-1;
            int indexcenter=-1;
            int indexright45=-1;
            int indexright90;
            if (cams!=null && !cams.isEmpty()){
                String camleft90 = config.getCameraLeft90();
                if (ValidString(camleft90)){
                    int[] indexes = FindIndexes(cams, camleft90);
                    if (indexes.length>0){
                        indexleft90 = FindFirstIndex(cams, camleft90);
                        Logger.info("Left90 Index: "+indexleft90);
                        if (indexleft90!=-1){
                            final int finalindex = indexleft90;
                            new Thread(()-> SetupCameraWithController(image1, camleft90, finalindex)).start();
                        }
                    }
                }
                String camleft45 = config.getCameraLeft45();
                if (ValidString(camleft45)){
                    int[] indexes = FindIndexes(cams, camleft45);
                    if (indexes.length>0){
                        indexleft45 = FindFirstIndex(cams, camleft45, indexleft90);
                        Logger.info("Left45 Index: "+indexleft45);
                        if (indexleft45!=-1) {
                            final int finalindex = indexleft45;
                            new Thread(()-> SetupCameraWithController(image2, camleft45, finalindex)).start();
                        }
                    }
                }
                String camcenter = config.getCameraCenter();
                if (ValidString(camcenter)){
                    int[] indexes = FindIndexes(cams, camcenter);
                    if (indexes.length>0){
                        indexcenter = FindFirstIndex(cams, camcenter, indexleft90, indexleft45);
                        Logger.info("Center Index: "+indexcenter);
                        if (indexcenter!=-1) {
                            final int finalindex = indexcenter;
                            new Thread(()-> SetupCameraWithController(image3, camcenter, finalindex)).start();
                        }
                    }
                }
                String camright45 = config.getCameraRight45();
                if (ValidString(camright45)){
                    int[] indexes = FindIndexes(cams, camright45);
                    if (indexes.length>0){
                        indexright45 = FindFirstIndex(cams, camright45, indexleft90, indexleft45, indexcenter);
                        Logger.info("Right45 Index: "+indexright45);
                        if (indexright45!=-1) {
                            final int finalindex = indexright45;
                            new Thread(()-> SetupCameraWithController(image4, camright45, finalindex)).start();
                        }
                    }

                }
                String camright90 = config.getCameraRight90();
                if (ValidString(camright90)){
                    int[] indexes = FindIndexes(cams, camright90);
                    if (indexes.length>0){
                        indexright90 = FindFirstIndex(cams, camright90, indexleft90, indexleft45, indexcenter, indexright45);
                        Logger.info("Right90 Index: "+indexright90);
                        if (indexright90!=-1) {
                            final int finalindex = indexright90;
                            new Thread(()-> SetupCameraWithController(image5, camright90, finalindex)).start();
                        }
                    }
                }
            }

        });


    }

    public void Unload(){

        if (image1!=null) {
            image1.StopLiveView();
        }
        if (image2!=null) {
            image2.StopLiveView();
        }
        if (image3!=null) {
            image3.StopLiveView();
        }
        if (image4!=null) {
            image4.StopLiveView();
        }
        if (image5!=null) {
            image5.StopLiveView();
        }
        config.Save();
    }


    private void SetupCameraWithController(Cameradetail image, String cameraname, int  devicenumber){
        if (image!=null){

            String title = switch(image.getCameraConfigEnum()){
                case CameraConfigCenter -> "03";
                case CameraConfigLeft45 -> "02";
                case CameraConfigLeft90 -> "01";
                case CameraConfigRight45 -> "04";
                case CameraConfigRight90 -> "05";
            };

            final AtomicBoolean _have_face = switch (image.getCameraConfigEnum()){
                case CameraConfigCenter -> have_face[2];
                case CameraConfigLeft45 -> have_face[1];
                case CameraConfigLeft90 -> have_face[0];
                case CameraConfigRight45 -> have_face[3];
                case CameraConfigRight90 -> have_face[4];
            };

            final AtomicBoolean _have_profile = switch (image.getCameraConfigEnum()){
                case CameraConfigCenter -> have_profile[2];
                case CameraConfigLeft45 -> have_profile[1];
                case CameraConfigLeft90 -> have_profile[0];
                case CameraConfigRight45 -> have_profile[3];
                case CameraConfigRight90 -> have_profile[4];
            };

            final AtomicBoolean _have_eye = switch (image.getCameraConfigEnum()){
                case CameraConfigCenter -> have_eyes[2];
                case CameraConfigLeft45 -> have_eyes[1];
                case CameraConfigLeft90 -> have_eyes[0];
                case CameraConfigRight45 -> have_eyes[3];
                case CameraConfigRight90 -> have_eyes[4];
            };



            Platform.runLater(()-> image.setCameraTitle(title));
            if (devicenumber!=-1){
                OpenCVFrameGrabber grabber = new OpenCVFrameGrabber(devicenumber);
                // default
                int livewidth = 640;
                int liveheight = 480;
                int photowidth = 640;
                int photoheight = 480;
                int reducewidth = 640;
                int reduceheight = 480;

                // mode1 selalu paling tinggi
                if (cameraname.contains("ACER QHD")){
                    photowidth = AcerQHD.ModeBest.getWidth();
                    photoheight = AcerQHD.ModeBest.getHeight();
                    livewidth = AcerQHD.ModeLive.getWidth();
                    liveheight = AcerQHD.ModeLive.getHeight();
                } else if (cameraname.contains("AVerVision M15W")){
                    photowidth = AverVisionM15W.ModeBest.getWidth();
                    photoheight = AverVisionM15W.ModeBest.getHeight();
                    livewidth = AverVisionM15W.ModeLive.getWidth();
                    liveheight = AverVisionM15W.ModeLive.getHeight();
                } else if (cameraname.contains("Arducam IMX477")){
                    // mode1 terbaik 16:9
                    // mode5 terbaik 4:3
                    photowidth = ArducamIMX477.ModeBest.getWidth();
                    photoheight = ArducamIMX477.ModeBest.getHeight();
                    livewidth = ArducamIMX477.ModeLive.getWidth();
                    liveheight = ArducamIMX477.ModeLive.getHeight();
                } else if (cameraname.contains("OBSBOT Meet 2")){
                    photowidth = ObsbotMeet2.ModeBest.getWidth();
                    photoheight = ObsbotMeet2.ModeBest.getHeight();
                    livewidth = ObsbotMeet2.ModeLive.getWidth();
                    liveheight = ObsbotMeet2.ModeLive.getHeight();
                    reducewidth = ObsbotMeet2.Mode3.getWidth();
                    reduceheight = ObsbotMeet2.Mode3.getHeight();
                }
                image.SetGrabber(grabber, livewidth,liveheight,photowidth,photoheight,reducewidth,reduceheight, true);

                boolean use_face_detector = true;
                boolean use_qr_detector = true;

                LiveCamEvent lce = new LiveCamEvent() {
                    @Override
                    public void onDetectedQRCode(String barCode) {
                        barCode = RemoveSpaces(barCode);
                        if (ValidBarCode(barCode)){
                            String prefix = barcodeData.getText();
                            if (!barCode.equals(prefix)){
                                String finalbarCode = barCode;
                                Platform.runLater(()-> barcodeData.setText(finalbarCode));
                                Task<PatientRecord> checkpatientID = new Task<>() {
                                    @Override
                                    protected PatientRecord call() throws Exception {
                                        BarcodeResullt br =  erhaAPI.Validate_Barcode(finalbarCode,true);
                                        if (br!=null){
                                            if (br.message.startsWith("Records found")){
                                                if (br.data!=null && br.data.length>0){
                                                    PatientRecord pr = br.data[0];
                                                    if (!pr.medical_record_detail_id.isEmpty()){
                                                        if (!pr.name.isEmpty()){
                                                            super.succeeded();
                                                            return pr;
                                                        } else {
                                                            Logger.error("Barcode ",finalbarCode," PatientRecord name is empty");
                                                            throw new Exception("Barcode "+finalbarCode+" PatientRecord name kosong");
                                                        }
                                                    } else {
                                                        Logger.error("Barcode ",finalbarCode," PatientRecord medical_record_detail_id is empty");
                                                        throw new Exception("Data dengan barcode "+finalbarCode+", PatientRecord medical_record_detail_id kosong");
                                                    }
                                                } else {
                                                    Logger.error("Record associated with barcode ",finalbarCode," is empty");
                                                    //AutoCloseAlert.show("Data Tidak Ditemukan","Data dengan barcode "+finalbarCode+" ditemukan di server, tetapi kosong","Pastikan data barcode anda benar", 5);
                                                    throw new Exception("Data dengan barcode "+finalbarCode+" ditemukan di server, tetapi kosong");
                                                }
                                            } else {
                                                Logger.error("Record associated with barcode ",finalbarCode," is not found");
                                                //AutoCloseAlert.show("Data Tidak Ditemukan","Data dengan barcode "+finalbarCode+" tidak ditemukan di server","Pastikan data barcode anda benar",5);
                                                throw new Exception("Data dengan barcode "+finalbarCode+" tidak ditemukan di server");
                                            }
                                        } else {
                                            Logger.error("BarcodeResullt with barcode ",finalbarCode," is null");
                                            //AutoCloseAlert.show("Data Tidak Ditemukan", "BarcodeResult dengan barcode "+finalbarCode+" menghasilkan null", "Pastikan data barcode anda benar", 5);
                                            throw new Exception("BarcodeResult dengan barcode "+finalbarCode+" menghasilkan null");
                                        }
                                    }
                                };

                                checkpatientID.setOnSucceeded(event -> {
                                    PatientRecord pr = checkpatientID.getValue();
                                    if (pr!=null){
                                        Platform.runLater(()->{
                                            int medrecid = toInt(pr.medical_record_detail_id);
                                            medicalRecordID.setText(medrecid+"");
                                            PatientName.setText(pr.name);
                                        });

                                        if (audioPlayer!=null && audioPlayer.isInited()){
                                            if (!Objects.equals(audioPlayer.getCurrentFile(),audio_posisikan_muka)) {
                                                audioPlayer.StopCurrentPlayback();
                                                audioPlayer.PlayFile(audio_posisikan_muka, null);
                                            }
                                        }

                                    }
                                });

                                checkpatientID.setOnFailed(event -> {
                                    if (audioPlayer!=null && audioPlayer.isInited()){
                                        if (!Objects.equals(audioPlayer.getCurrentFile(), audio_data_barcode_tidak_ditemukan)) {
                                            audioPlayer.StopCurrentPlayback();
                                            audioPlayer.PlayFile(audio_data_barcode_tidak_ditemukan,null);
                                        }
                                    }
                                    Task<?> failed = (Task<?>) event.getSource();
                                    Throwable t = failed.getException();
                                    final String message = t.getMessage();
                                    System.out.println("checkpatientID.setOnFailed message : "+message);


                                    AutoCloseAlert.show("Data Tidak Ditemukan", message, "Pastikan data barcode anda benar", 5, s -> {
                                        isTakingPhoto.set(false);
                                        Platform.runLater(()->{
                                            medicalRecordID.setText("");
                                            PatientName.setText("");
                                            barcodeData.setText("");
                                        });
                                    });

                                });

                                new Thread(checkpatientID).start();




                            }
                        }
                    }

                    @Override
                    public void onFrontalFaceDetector(boolean hasface, int width, int height) {
                        if (hasface!= _have_face.get()){
                            _have_face.set(hasface);
                            if (!hasface) {
                                _have_eye.set(false);
                            }
                            //update_status(image);

                        }

                    }

                    @Override
                    public void onProfileFaceDetector(boolean hasface, int width, int height) {
                        if (hasface!= _have_profile.get()){
                            _have_profile.set(hasface);
                            if (!hasface) {
                                _have_eye.set(false);
                            }
                            //update_status(image);
                        }

                    }

                    @Override
                    public void onEyeDetector(boolean hasEye) {
                        if (_have_face.get() || _have_profile.get()) _have_eye.set(hasEye);
                        //update_status(image);
                    }

                    @Override
                    public void onLeftEarDetector(boolean hasLeftEar, int width, int height) {

                    }

                    @Override
                    public void onRightEarDetector(boolean hasRightEar, int width, int height) {

                    }

                    @Override
                    public void onLeftEyeDetector(boolean hasLeftEye, int width, int height) {

                    }

                    @Override
                    public void onRightEyeDetector(boolean hasRightEye, int width, int height) {

                    }

                    @Override
                    public void onLog(String log) {
                        String ss = String.format("[%s] : %s", title, log);
                        Logger.info(ss);
                    }

                    @Override
                    public void onBlink(int counter) {
                        System.out.println("Blink detected at camera "+title+" delay= "+counter);
                        if (audioPlayer!=null && audioPlayer.isPlaying()) return;       // let audio finish playback
                        if (isTakingPhoto.get()) return;        // other camera is taking picture
                        // revisi 08/04/2025
                        TakePhotos();

//                        String directory = config.getPhotoDirectory();
//                        String prefix = RemoveSpaces(medicalRecordID.getText()) ;
//                        if (ValidDirectory(directory)){
//                            if (ValidMedicalRecordId(prefix)){
//                                isTakingPhoto.set(true);
//                                TakePhotos();
//                            } else {
//                                System.out.println("Prefix invalid, not taking photo");
//                                isTakingPhoto.set(false);
//                                //AutoCloseAlert.show("QR Code Not Available", "", "Please scan QR before continue", 5, s -> AutoCloseAlert.show("Scan Barcode", "Silahkan Scan Barcode Anda", "Arahkan kertas barcode ke kamera", 0, null));
//                                if (!Objects.equals(AutoCloseAlert.shownBanner, AutoCloseAlert.banner_01)){
//                                    if (AutoCloseAlert.banner_01!=null){
//                                        System.out.println("Showing banner 01 because prefix invalid");
//                                        AutoCloseAlert.showbanner(AutoCloseAlert.banner_01,0,null);
//                                    }
//                                }
//                                if (audioPlayer!=null && audioPlayer.isInited()){
//                                    if (!Objects.equals(audioPlayer.getCurrentFile(), audio_scan_barcode)) {
//                                        audioPlayer.StopCurrentPlayback();
//                                        audioPlayer.PlayFile(audio_scan_barcode, null);
//                                    }
//                                }
//                            }
//                        } else {
//                            System.out.println("Photo Directory invalid, not taking photo");
//                            isTakingPhoto.set(false);
//                            AutoCloseAlert.show("Invalid Photo Directory","Photo Directory not set", "Please set photo directory at Setting", 5, s -> AutoCloseAlert.show("Setting", "Setting", "Please set photo directory", 0, null));
//                        }
                    }

                    @Override
                    public void onStartCapturing() {
                        if (!Objects.equals(AutoCloseAlert.shownBanner, AutoCloseAlert.banner_01)){
                            if (AutoCloseAlert.banner_01!=null){
                                System.out.println("Showing banner 01 for first time");
                                AutoCloseAlert.showbanner(AutoCloseAlert.banner_01,0,null);
                            }
                        }

                    }

                    @Override
                    public void onIntervalUpdate() {
                        update_status(image);
                    }

                    private void update_status(Cameradetail image){
                        Platform.runLater(()-> {
                            String sb = "Camera Started, " +
                                    image.getBestWidth() +
                                    "x" +
                                    image.getBestHeight() +
                                    "@" +
                                    image.getLiveFPS();

                            image.setCameraStatus(sb);
                        });
                    }
                };

                Platform.runLater(()-> image.setCameraStatus("Camera Starting"));
                if (image.StartLiveView(lce, title, use_qr_detector, use_face_detector)){
                    //TODO Start Live View berhasil, apa lagi yang mau dikerjakan ?

                } else Platform.runLater(()->image.setCameraStatus("Unable to Set Grabber"));
            } else Platform.runLater(()->image.setCameraStatus("Camera not found, please check setting"));
        }
    }

    private void LoadCameraDetail(AnchorPane cam, int camid, CameraConfigEnum cc){
        try{

            have_face[camid-1] = new AtomicBoolean(false);
            have_eyes[camid-1] = new AtomicBoolean(false);
            have_profile[camid-1] = new AtomicBoolean(false);

            FXMLLoader loader = new FXMLLoader(getClass().getResource("cameradetail.fxml"));
            AnchorPane child = loader.load();



            cam.getChildren().clear();
            cam.getChildren().add(child);

            cam.widthProperty().addListener(observable ->{
                //System.out.println("Width property, cam "+camid+" width="+cam.getWidth()+" height="+cam.getHeight());
                if (cam.getWidth()!=0) {
                    if (cam.getHeight()!=0){
                        AnchorPane.setTopAnchor(child, 0.0);
                        AnchorPane.setBottomAnchor(child, 0.0);
                        AnchorPane.setLeftAnchor(child, 0.0);
                        AnchorPane.setRightAnchor(child, 0.0);
                    }
                }
            });
            cam.heightProperty().addListener(observable -> {
                //System.out.println("Height property, cam "+camid+" width="+cam.getWidth()+" height="+cam.getHeight());
                if (cam.getWidth()!=0) {
                    if (cam.getHeight()!=0){
                        AnchorPane.setTopAnchor(child, 0.0);
                        AnchorPane.setBottomAnchor(child, 0.0);
                        AnchorPane.setLeftAnchor(child, 0.0);
                        AnchorPane.setRightAnchor(child, 0.0);
                    }
                }
            });

            switch(camid){
                case 1:
                    image1 = loader.getController();
                    image1.setCameraConfigEnum(cc);
                    break;
                case 2:
                    image2 = loader.getController();
                    image2.setCameraConfigEnum(cc);
                    break;
                case 3:
                    image3 = loader.getController();
                    image3.setCameraConfigEnum(cc);
                    break;
                case 4:
                    image4 = loader.getController();
                    image4.setCameraConfigEnum(cc);
                    break;
                case 5:
                    image5 = loader.getController();
                    image5.setCameraConfigEnum(cc);
                    break;
            }

        } catch (Exception e){
            Logger.error("Error LoadCameraDetail: " + e.getMessage());
        }
    }

    private void InsertSQL(PhotoReviewClass prc){
        Sqlite sql = new Sqlite();
        sql.Insert(prc);
    }
}
