package Config;


import com.google.gson.Gson;
import com.google.zxing.MultiFormatReader;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.control.Alert;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.bytedeco.javacv.OpenCVFrameConverter;
import org.bytedeco.opencv.global.opencv_core;
import org.bytedeco.opencv.global.opencv_imgcodecs;
import org.bytedeco.opencv.global.opencv_imgproc;
import org.bytedeco.opencv.opencv_core.Mat;
import org.bytedeco.opencv.opencv_core.Rect;
import org.bytedeco.opencv.opencv_core.Size;
import org.bytedeco.opencv.opencv_core.UMat;
import org.tinylog.Logger;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.InputStream;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

import static org.bytedeco.opencv.global.opencv_core.CV_64F;

@SuppressWarnings("unused")
public class SomeCodes {
    public final static String currentDirectory = System.getProperty("user.dir");
    private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    public static final Java2DFrameConverter converter = new Java2DFrameConverter();
    public static final OpenCVFrameConverter.ToMat matconverter = new OpenCVFrameConverter.ToMat();
    public static final Gson gson = new Gson();
    public static final ConfigFile config = new ConfigFile();

    public static String GetDateTimeString(){
        return LocalDateTime.now().format(dtf);
    }

    public static Path GetLogsPath(){
        return Path.of(currentDirectory, "logs");
    }

    public static int[] FindIndexes(List<String> source, String value){
        if (source!=null && !source.isEmpty()){
            if (ValidString(value)){
                List<Integer> result = new ArrayList<>();
                for (int i=0; i<source.size(); i++){
                    if (source.get(i).equals(value)){
                        result.add(i);
                    }
                }
                if (!result.isEmpty()) return result.stream().mapToInt(i->i).toArray();
            }
        }
        return new int[0];
    }

    public static int FindFirstIndex(List<String> source, String value, int... avoidedindex){
        if (source!=null && !source.isEmpty()){
            if (ValidString(value)){
                for (int i=0; i<source.size(); i++){
                    if (source.get(i).equals(value)){
                        // ketemu, tapi cek dulu apakah masuk  avoidedindex

                        if (avoidedindex!=null && avoidedindex.length>0){
                            boolean found = false;
                            for (int j : avoidedindex){
                                if (j!=-1){
                                    if (i==j){
                                        found = true;
                                        break;
                                    }
                                }
                            }
                            if (found) continue;
                        }
                        return i;
                    }
                }
            }
        }
        return -1;
    }

    public static Image ConvertToImage(Mat mat, int width, int height){
        if (mat!=null){
            Mat resized = new Mat();
            opencv_imgproc.resize(mat, resized, new org.bytedeco.opencv.opencv_core.Size(width, height));
            BufferedImage img = converter.convert(matconverter.convert(resized));
            return SwingFXUtils.toFXImage(img, null);
        }
        return null;

    }

    public static String RectToString(Rect rect){
        if (rect!=null){
            return "X:"+rect.x()+",Y:"+rect.y()+",W:"+rect.width()+",H:"+rect.height();
        }
        return "";
    }


    /**
     * Find thumbfile in thumbs directory
     * @param sourcejpg source jpg file
     * @return thumbfile if found, or null if not found
     */
    public static String FindThumbfile(String sourcejpg){
        File sourcefile = new File(sourcejpg);
        Path thumbpath = Path.of(config.getThumbsDirectory());
        Path thumbfile = thumbpath.resolve(sourcefile.getName());
        if (Files.exists(thumbfile)){
            return thumbfile.toString();
        }
        return null;
    }

    public static void Wait(int millis){
        try{
            Thread.sleep(millis);
        } catch (Exception e){
            Logger.error("Error waiting: "+e.getMessage());
        }
    }

    /**
     * Make thumbfile from source jpg file
     * @param sourcejpg source jpg file
     * @param thumbsize thumbfile Size
     * @return thumbfile if success, or null if failed
     */
    public static String MakeThumbfile(String sourcejpg, Size thumbsize){
        try{
            File ff = new File(sourcejpg);
            if (ff.exists()){
                String thumbfile = Path.of(config.getPhotoDirectory(),"thumbs", ff.getName()).toString();
                File thumb = new File(thumbfile);
                if (thumb.exists()) {
                    return thumbfile;
                }

                Mat source = opencv_imgcodecs.imread(sourcejpg);
                if (source!=null && !source.empty()){
                    Mat resized = new Mat();
                    opencv_imgproc.resize(source, resized, thumbsize);
                    opencv_imgcodecs.imwrite(thumbfile, resized);
                    return thumbfile;
                }
            }
        } catch (Exception e){
            System.out.println("Error making thumbfile: "+sourcejpg+", Msg : "+e.getMessage());
        }
        return null;
    }

    public static String RemoveSpaces(String x){
        return x.replaceAll("\\s+","");
    }

    public static String LocalDateTimeToString(LocalDateTime x){
        return x.format(dtf);
    }

    /**
     * Extract resource file to current directory
     * @param filename resource file name
     * @return extracted file path if success, or null if failed
     */
    public static String ExtractResource(String filename){
        try{
            File destination = new File(currentDirectory, filename);
            if (destination.exists()){
                return destination.getAbsolutePath();
            }
            InputStream is = SomeCodes.class.getResourceAsStream(filename);
            if (is!=null){
                Files.copy(is, destination.toPath());
                Logger.info("Resource File extracted: "+filename);
                return destination.getAbsolutePath();
            }
        } catch (Exception e){
            Logger.error("Error extracting resource: "+filename+", Message : "+e.getMessage());
        }
        return null;
    }

    /**
     * Check if path is valid directory
     * @param path directory path
     * @return true if valid directory, false if not valid directory
     */
    public static boolean ValidDirectory(String path){
        if (ValidString(path)){
            File ff = new File(path);
            return ff.isDirectory();
        }
        return false;
    }

    /**
     * Check if port number is valid
     * @param port port number
     * @return true if valid port number, false if not valid port number
     */
    public static boolean ValidPortNumber(int port){
        return port>0 && port<65536;
    }

    /**
     * Convert string to integer
     * @param x string
     * @return integer value if success, or 0 if failed
     */
    public static int toInt(String x){
        try {
            return Integer.parseInt(x);
        } catch (Exception e){
            return 0;
        }
    }

    public static boolean toBoolean(String x){
        return x!=null && x.equalsIgnoreCase("true");
    }

    /**
     * Check if string is valid IPV4 address
     * @param ipaddress IPV4 address
     * @return true if valid IPV4 address, false if not valid IPV4 address
     */
    public static boolean ValidIPV4(String ipaddress){
        if (ValidString(ipaddress)){
            try{
                InetAddress inet = InetAddress.getByName(ipaddress);
                if (inet instanceof Inet4Address){
                    if (inet.getHostAddress().equals(ipaddress)){
                        return true;
                    }
                }
            } catch (Exception ignored) {
            }
        }
        return false;
    }

    /**
     * Check if string is valid IPV6 address
     * @param ipaddress IPV6 address
     * @return true if valid IPV6 address, false if not valid IPV6 address
     */
    public static boolean ValidIPV6(String ipaddress){
        if (ValidString(ipaddress)){
            try{
                InetAddress inet = InetAddress.getByName(ipaddress);
                if (inet instanceof Inet6Address){
                    if (inet.getHostAddress().equals(ipaddress)){
                        return true;
                    }
                }
            } catch (Exception ignored) {
            }
        }
        return false;
    }

    /**
     * Get file name from file path
     * @param filepath file path
     * @return file name if success, or empty string if failed
     */
    public static String GetFileName(String filepath){
        if (ValidString(filepath)){
            File ff = new File(filepath);
            if (ff.isFile()){
                return ff.getName();
            }
        }
        return "";
    }

    /**
     * Check if string is valid file
     * @param filename file name
     * @return true if valid file, false if not valid file
     */
    public static boolean ValidFile(String filename){
        if (ValidString(filename)){
            File ff = new File(filename);
            return ff.isFile();
        }
        return false;
    }

    /**
     * Read file as byte array
     * @param filename file name
     * @return byte array if success, or null if failed
     */
    public static byte[] ReadFile(String filename){
        if (ValidFile(filename)){
            try{
                return Files.readAllBytes(Path.of(filename));
            } catch (Exception e){
                Logger.error("Error reading file: "+filename+", Msg : "+e.getMessage());
            }
        }
        return null;
    }



    /**
     * Resize Rect
     * @param original original Rect
     * @param scaleX scale factor
     * @param scaleY scale factor
     * @param Xoffset X offset, positive value to increase width, negative value to decrease width
     * @param Yoffset Y offset, positive value to increase height, negative value to decrease height
     * @return resized Rect if success, or null if failed
     */
    public static Rect ResizeRect(Rect original, double scaleX, double scaleY, int Xoffset, int Yoffset){
        if (original!=null){
            int newX = (int)(original.x()*scaleX);
            newX -= Xoffset;
            if (newX<0) newX = 0;
            int newY = (int)(original.y()*scaleY);
            newY -= Yoffset;
            if (newY<0) newY = 0;
            int newWidth = (int)(original.width()*scaleX);
            newWidth += Xoffset*2;
            int newHeight = (int)(original.height()*scaleX);
            newHeight += Yoffset*2;
            return new Rect(newX, newY, newWidth, newHeight);
        }
        return null;
    }

    public static UMat CropUMat(UMat source, Rect ROI){
        if (source!=null && !source.empty() && ValidROI(ROI)){
            int x = ROI.x();
            int y = ROI.y();
            int width = ROI.width();
            int height = ROI.height();

            if (x>=0 && y>=0 && width>0 && height>0){
                if (x+width>source.cols()) width = source.cols()-x;
                if (y+height>source.rows()) height = source.rows()-y;
                if (width>0 && height>0){
                    Rect crop = new Rect(x, y, width, height);
                    return new UMat(source, crop);
                }
            }
        }
        return null;
    }

    public static boolean Save(UMat source, String filename, int[] param){
        if (source!=null && !source.empty() && ValidString(filename)){
            try{
                return opencv_imgcodecs.imwrite(filename, source, param);
            } catch (Exception e){
                Logger.error("Error saving file: "+filename+", Msg : "+e.getMessage());
            }
        }
        return false;
    }

    /**
     * Concatenate byte arrays
     * @param args byte arrays
     * @return concatenated byte array if success, or null if failed
     */
    public static byte[] Concat(byte[]... args){
        if (args!=null && args.length>0){
            int total = 0;
            for(byte[] x : args){
                if (x!=null && x.length>0){
                    total += x.length;
                }
            }
            if (total>0){
                byte[] result = new byte[total];
                int offset = 0;
                for(byte[] x : args){
                    if (x!=null && x.length>0){
                        System.arraycopy(x, 0, result, offset, x.length);
                        offset += x.length;
                    }
                }
                return result;
            }
        }
        return null;
    }

    /**
     * Check if string is valid
     * @param x string
     * @return true if valid, false if not valid
     */
    public static boolean ValidString(String x){
        if (x!=null){
            return !x.isEmpty();
        }
        return false;
    }

    /**
     * Open picture in default viewer
     * @param filename picture file name
     */
    public static void OpenPictureInDefaultViewer(String filename){
        try{
            File ff = new File(filename);
            if (ff.exists()){
                String os = System.getProperty("os.name").toLowerCase();
                if (os.contains("win")){
                    //Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler "+ff.getAbsolutePath());
                    Runtime.getRuntime().exec(new String[]{"rundll32", "url.dll,FileProtocolHandler", ff.getAbsolutePath()});
                } else if (os.contains("mac")){
                    //Runtime.getRuntime().exec("open "+ff.getAbsolutePath());
                    Runtime.getRuntime().exec(new String[]{"open", ff.getAbsolutePath()});
                } else if (os.contains("nix") || os.contains("nux")){
                    //Runtime.getRuntime().exec("xdg-open "+ff.getAbsolutePath());
                    Runtime.getRuntime().exec(new String[]{"xdg-open", ff.getAbsolutePath()});
                }
            }
        } catch (Exception e){
            Logger.error("Error opening file: "+filename+", Msg : "+e.getMessage());
        }
    }

    public static MultiFormatReader qrreader;
    public static void LoadQRReader(){
        if (qrreader==null) {
            qrreader = new MultiFormatReader();
            Logger.info("QRReader loaded");
        }

    }


    /**
     * Make array from string arguments
     * @param args string arguments
     * @return array of strings if success, or empty array if failed
     */
    public static String[] MakeArray(String... args){
        if (args!=null && args.length>0){
            List<String> ll = new ArrayList<>();
            for(String x : args){
                if (ValidString(x)) ll.add(x);
            }
            return ll.toArray(new String[0]);
        }
        return new String[0];
    }

    public static boolean ValidBarCode(String value){
        if (value!=null && value.length()==10){
            boolean valid = true;
            for(int i=0; i<value.length(); i++){
                if (!Character.isDigit(value.charAt(i))){
                    valid = false;
                    break;
                }
            }
            return valid;
        }
        return false;
    }

    /**
     * Check if Valid PatientID found
     * Patient ID is 10 digits number
     * @param patientid Patient ID
     * @return true if valid, false if not valid
     */
    public static boolean ValidMedicalRecordId(String patientid){
        if (patientid!=null && !patientid.isEmpty()){
            boolean valid = true;
            for(int i=0; i<patientid.length(); i++){
                if (!Character.isDigit(patientid.charAt(i))){
                    valid = false;
                    break;
                }
            }
            return valid;
        } else return false;
    }

    /**
     * Check Region of Interest is a valid Rect
     * @param ROI Region of Interest
     * @return true if valid, false if not valid
     */
    public static boolean ValidROI(Rect ROI){
        if (ROI!=null){
            if (ROI.x()>=0){
                if (ROI.y()>=0){
                    if (ROI.width()>0){
                        if (ROI.height()>0){
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    public static void Print(String... x){
        if (x!=null && x.length>0){
            for(String xx : x){
                System.out.println(xx);
            }
        }
    }

    /**
     * Check if Region of Interest is inside UMat
     * @param ROI Region of Interest
     * @param mat UMat
     * @return true if inside, false if not inside
     */
    public static boolean ROIInsideUMat(Rect ROI, UMat mat){
        if (ValidROI(ROI)){
            if (mat!=null){
                return ROI.x()>=0 && ROI.y()>=0 &&
                        ROI.x()+ROI.width()<=mat.cols() &&
                        ROI.y()+ROI.height()<=mat.rows();
            }
        }
        return false;
    }

    /**
     * Check if Region of Interest 1 is same with Region of Interest 2
     * @param ROI1 Region of Interest 1
     * @param ROI2 Region of Interest 2
     * @return true if same, false if not same
     */
    public static boolean IsSameROI(Rect ROI1, Rect ROI2){
        if (ValidROI(ROI1) && ValidROI(ROI2)){
            return ROI1.x()==ROI2.x() && ROI1.y()==ROI2.y() &&
                    ROI1.width()==ROI2.width() && ROI1.height()==ROI2.height();
        }
        return false;
    }

    /**
     * Check if Rect 1 is inside Rect 2
     * @param smaller Rect 1
     * @param bigger Rect 2
     * @return true if inside, false if not inside
     */
    public static boolean IsInsideRect(Rect smaller, Rect bigger){
        if (smaller!=null && bigger!=null){
            return smaller.x()>=bigger.x() && smaller.y()>=bigger.y() &&
                    smaller.x()+smaller.width()<=bigger.x()+bigger.width() &&
                    smaller.y()+smaller.height()<=bigger.y()+bigger.height();
        }
        return false;
    }

    /**
     * Make directory if not exists
     * @param path directory path
     */
    public static void MakeDirectory(String path){
        if (ValidString(path)){
            File ff = new File(path);
            if (!ff.isDirectory()){
                try{
                    Files.createDirectories(ff.toPath());
                    Logger.info("Directory created: "+path);
                } catch (Exception e){
                    Logger.info("Error creating directory: "+path+", Msg : "+e.getMessage());
                }
            } else Logger.info("Directory exists: "+path);
        }


    }

    public static short ToShort(String x){
        try{
            return Short.parseShort(x);
        } catch (Exception e){
            return 0;
        }
    }

    public static void SetText(Object obj, String text){
        if (obj!=null && text!=null){
            if (Platform.isFxApplicationThread()){
                if (obj instanceof Label){
                    ((Label) obj).setText(text);
                } else if (obj instanceof TextArea){
                    ((TextArea) obj).setText(text);
                } else if (obj instanceof TextField){
                    ((TextField) obj).setText(text);
                }
            } else{
                Platform.runLater(()->{
                    if (obj instanceof Label){
                        ((Label) obj).setText(text);
                    } else if (obj instanceof TextArea){
                        ((TextArea) obj).setText(text);
                    } else if (obj instanceof TextField){
                        ((TextField) obj).setText(text);
                    }
                });
            }
        }
    }

    public static void LabelVisible(Label label, boolean visible){
        if (label!=null){
            if (visible){
                if (Platform.isFxApplicationThread()){
                    label.setVisible(true);
                } else{
                    Platform.runLater(()->{
                        label.setVisible(true);
                    });
                }
            } else {
                if (Platform.isFxApplicationThread()){
                    label.setVisible(false);
                } else{
                    Platform.runLater(()->{
                        label.setVisible(false);
                    });
                }
            }
        }
    }

    public static void ShowAlert(Alert.AlertType type, String title, String header, String content){
        Alert alert = new Alert(type);
        alert.setTitle(title);
        alert.setHeaderText(header);
        alert.setContentText(content);
        alert.showAndWait();
    }

    public static double CalculateSharpness(String filename){
        if (ValidFile(filename)){
            try(Mat mat = opencv_imgcodecs.imread(filename)){
                return CalculateSharpness(new UMat(mat));
            } catch (Exception e){
                Logger.error("Error calculating sharpness: "+filename+", Msg : "+e.getMessage());
            }
        }
        return 0;
    }

    public static double FindLowestValue(double... values){
        if (values!=null && values.length>0){
            double lowest = values[0];
            for(double x : values){
                if (x>=0){
                    if (x<lowest){
                        lowest = x;
                    }
                }

            }
            return lowest;
        }
        return 0;
    }

    public static boolean ValidDouble(String x){
        try{
            double xx =  Double.parseDouble(x);
            return true;
        } catch (Exception ignored){
        }
        return false;
    }

    /**
     * Calculate sharpness of image
     * @param mat image in UMat format, expected in gray scale
     * @return sharpness value
     */
    public static double CalculateSharpness(UMat mat){

        if (mat!=null && !mat.empty()){
            if (mat.channels()!=1){
                UMat grey = new UMat();
                opencv_imgproc.cvtColor(mat, grey, opencv_imgproc.COLOR_BGR2GRAY);
                mat = grey;
            }

            opencv_imgproc.equalizeHist(mat, mat);

            UMat laplacian = new UMat();

            opencv_imgproc.Laplacian(mat, laplacian, CV_64F);

            UMat mean = new UMat(1,1, CV_64F);
            UMat stddev = new UMat(1,1, CV_64F);

            opencv_core.meanStdDev(laplacian, mean, stddev);

            Mat _std = new Mat();
            stddev.copyTo(_std);

            double value = _std.createIndexer().getDouble(0,0);

            return Math.pow(value,2);
        }
        return 0;
    }

    public static boolean IsBlurred(UMat mat, double threshold){
        return CalculateSharpness(mat)<threshold;
    }
}
