/* Author: Prasad U S
*
*
*/

package src;

import java.lang.Exception;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.ComponentOrientation;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;

import javax.imageio.ImageIO;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ProgressMonitor;
import javax.swing.Timer;
import javax.swing.UIManager;

import uk.co.chartbuilder.data.DataSet;
import uk.co.chartbuilder.examples.facialrecognition.ResultDataParser;
import uk.co.chartbuilder.examples.facialrecognition.ResultsChart;
import uk.co.chartbuilder.parser.ParserException;

public class Main extends JApplet implements ActionListener {

    public static String classification;
    public static final Dimension IDEAL_IMAGE_SIZE = new Dimension(48, 64);
    TSCD eigenFaces = new TSCD();
    FeatureSpace featureSpace = new FeatureSpace();
    private static final long serialVersionUID = 1L;
    JPanel main;
    ImageBackgroundPanel bkd;
    // JLabel jlClassthreshold;
    JProgressBar jlStatus;
    JList jlist;
    JButton jbLoadImage, jbTrain, jbProbe, jbCropImage;
    ImageIcon imageAverageFace;
    JLabel jlAverageFace;
    Container c;
    FaceItem faceCandidate;
    FaceBrowser faceBrowser = new FaceBrowser();
    private JScrollPane jspFaceBrowser;
    JButton jbDisplayFeatureSpace;
    //JTextField jtfClassthreshold;
    int classthreshold = 5;
    FeatureVector lastFV = null;
    ArrayList<Face> faces;
    DataSet resultsData;

    //public static void main(String[] args) {
    //Main m = new Main();
    //System.out.println("OOPS");
    //}
    public void generalInit(Container c) {
        c.setLayout(new BorderLayout());
        main = new JPanel();

        bkd = new ImageBackgroundPanel();
        c.add(bkd, "Center");

        //c.add(main, "Center");
        //main.add(bckgd);

        jbLoadImage = new JButton("Load Images");
        jbLoadImage.addActionListener(this);
        jbCropImage = new JButton("Crop Images");
        jbCropImage.addActionListener(this);
        jbCropImage.setEnabled(false);
        jbTrain = new JButton("Compute Eigen Vectors");
        jbTrain.setEnabled(false);
        jbTrain.addActionListener(this);
        jbProbe = new JButton("Identify Face");
        jbProbe.addActionListener(this);
        jbProbe.setEnabled(false);
        jbDisplayFeatureSpace = new JButton("Display Result Chart");
        jbDisplayFeatureSpace.addActionListener(this);
        jbDisplayFeatureSpace.setEnabled(false);
        //jlClassthreshold = new JLabel("Factor");
        // jtfClassthreshold = new JTextField(""+classthreshold);
        //jbClassthreshold = new JButton("Update Threshold");
        // jbClassthreshold.addActionListener(this);

        faceCandidate = new FaceItem();
        faceCandidate.setBorder(BorderFactory.createRaisedBevelBorder());

        jlAverageFace = new JLabel();
        jlAverageFace.setVerticalTextPosition(JLabel.BOTTOM);
        jlAverageFace.setHorizontalTextPosition(JLabel.CENTER);

        jlStatus = new JProgressBar(JProgressBar.HORIZONTAL, 0, 100);
        jlStatus.setBorder(BorderFactory.createEtchedBorder());
        jlStatus.setStringPainted(true);
        jlist = new JList();
        main.setLayout(new BorderLayout());
        JPanel right = new JPanel();

        jbLoadImage.setFont(new Font("Verdana", 30, 18));
        //jbCropImage.setFont(new Font("Cambria", 20, 28));
        jbTrain.setFont(new Font("Verdana", 30, 18));
        jbProbe.setFont(new Font("Verdana", 30, 18));
        jbDisplayFeatureSpace.setFont(new Font("Verdana", 30, 18));
        right.setLayout(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.anchor = GridBagConstraints.PAGE_START;

        gbc.gridy = 1;
        gbc.gridwidth = 4;
        gbc.ipady = 30;
        gbc.ipadx = 110;
        gbc.insets = new Insets(10, 20, 10, 20);

        //JLabel myp = new JLabel("Project ID: PW13A01");
        //myp.setFont(new Font("Tahoma", 20, 20));
        //right.add(myp);
        //gbc.gridy = 3;

        try {
            String imPath = System.getProperty("user.dir");
            imPath = imPath.replace('\\', '/');
            BufferedImage myPicture = ImageIO.read(new File(imPath + "/src/src/face.png"));
            JLabel picLabel = new JLabel(new ImageIcon(myPicture));
            //picLabel.setSize(250, 220);
            right.add(picLabel);
        } catch (IOException ex) {
            System.out.println("Image face.png missing\n" + ex);
        }


        right.add(jbLoadImage, gbc);
        //gbc.gridy = 1; right.add(jbCropImage, gbc);
        gbc.gridy = 4;
        right.add(jbTrain, gbc);
        gbc.gridy = 6;
        right.add(jbProbe, gbc);
        gbc.gridy = 8;
        right.add(jbDisplayFeatureSpace, gbc);
        //gbc.gridy = 5; gbc.gridwidth = 1; right.add(jlClassthreshold, gbc);
        // gbc.gridy = 5; gbc.gridwidth = 1; right.add(jtfClassthreshold, gbc);
        // gbc.gridy = 6; gbc.gridwidth = 2; right.add(jbClassthreshold, gbc);
       /* gbc.gridy = 7;
         gbc.weighty = 1.0;
         gbc.fill = GridBagConstraints.VERTICAL | GridBagConstraints.HORIZONTAL;
         right.add(jlAverageFace, gbc);  */

        c.add(right, BorderLayout.EAST);


        //Mark



    }

    //public Main(){
    //try {
    //UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
    //} catch (Exception exception) {}
    //}
    public void init() {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception exception) {
        }

        c = getContentPane();
        generalInit(c);
        setSize(800, 480);
    }

    public void actionPerformed(ActionEvent arg0) {



        if (arg0.getSource() == jbLoadImage) {
            loadImage();



        } else if (arg0.getSource() == jbCropImage) {
            crop();
        } else if (arg0.getSource() == jbTrain) {
            train();
        } else if (arg0.getSource() == jbProbe) {
            probe();
        } else if (arg0.getSource() == jbDisplayFeatureSpace) {
            displayFeatureSpace();
        }
        //else if(arg0.getSource() == jbClassthreshold) updateThreshold();
    }

    //public void updateThreshold() {
    // classthreshold = Integer.parseInt(jtfClassthreshold.getText());
    //}
    private void displayFeatureSpace() {
        double[][] features = featureSpace.get3dFeatureSpace(lastFV);
        ResultDataParser parser = new ResultDataParser(features);
        try {
            parser.parse();
        } catch (ParserException pe) {
            System.out.println(pe.toString());
            System.exit(1);
        }


        JFrame frame = new JFrame("3D Face Recognition Results Chart");
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.getContentPane().setLayout(new BorderLayout());

        resultsData = parser;
        Canvas resultsCanvas = ResultsChart.getCanvas();
        JPanel resultsPanel = new JPanel();
        resultsPanel.setOpaque(false);
        resultsPanel.setLayout(new BorderLayout());
        resultsPanel.add(resultsCanvas, BorderLayout.CENTER);

        frame.getContentPane().add(resultsPanel, BorderLayout.CENTER);

        JLabel lbl = new JLabel("3D Face Recognition");
        lbl.setBackground(Color.BLACK);
        lbl.setForeground(Color.WHITE);
        lbl.setOpaque(true);
        lbl.setFont(lbl.getFont().deriveFont(Font.BOLD));
        frame.getContentPane().add(lbl, BorderLayout.SOUTH);
        ResultsChart resultsChart =
                new ResultsChart(resultsCanvas, resultsData);

        frame.setSize(800, 720);
        frame.setResizable(false);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private void probe() {
        double et = 0;
        try {
            JFileChooser fc = new JFileChooser();
            fc.setDialogTitle("Load a file");
            fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
            if (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
                //Calculate the time complexity of matching
                long startTime = System.currentTimeMillis();  //Starting time

                jlStatus.setString("Loading Files");
                jlStatus.paintImmediately(jlStatus.getVisibleRect());
                File file = fc.getSelectedFile();



                Face f = new Face(file);
                f.load(true);


                int numVecs = 10;
                double[] rslt = eigenFaces.getEigenFaces(f.picture, numVecs);
                FeatureVector fv = new FeatureVector();
                fv.setFeatureVector(rslt);

                classification = featureSpace.knn(FeatureSpace.EUCLIDEAN_DISTANCE, fv, classthreshold);
                f.classification = classification;
                f.description = "Query face image.";

                faceBrowser.highlightClassifiedAs(classification);

                FeatureSpace.fd_pair[] faceDistances = featureSpace.orderByDistance(FeatureSpace.EUCLIDEAN_DISTANCE, fv);

                //Not matching scenario
                //Distance more than 3000
                FeatureSpace.fd_pair fd = faceDistances[0];
                long st = System.currentTimeMillis();
                et = st - startTime;
                et /= 1000.0;
                if (fd.dist > 800) {
                    Exception e = new Exception();
                    throw e;
                } else {

                    if (et >= 8) {
                        Exception e1 = new Exception();
                        throw e1;
                    }
                }
                //Not matching scenario ends

                faceBrowser.orderAs(faceDistances);

                lastFV = fv;

                jlStatus.setIndeterminate(false);
                jlStatus.setString("Face matched to " + classification);
                jlStatus.paintImmediately(jlStatus.getVisibleRect());
                faceCandidate.setFace(f);
                faceCandidate.setVisible(true);


                long stopTime = System.currentTimeMillis();  //Stopping time
                long elapsedTime = stopTime - startTime;  //Calculate time elapsed in milliseconds
                JOptionPane.showMessageDialog(FrontEnd.frame, "Time Complexity of match: " + ((double) (elapsedTime / 1000.0)) + " seconds.\nFace matched to " + classification + ".");
                //Display the time as a message dialog in seconds
            }
        } catch (MalformedURLException e) {
            System.err.println("There was a problem opening a file : " + e.getMessage());
            e.printStackTrace(System.err);
        } catch (Exception e) {
            JOptionPane.showMessageDialog(this, "Error: Image not matched to any of the database images!!\nNo match found!!\nTime elapsed: " + et + " seconds.");
        }
    }

    private void loadImage() {
        try {


            faces = new ArrayList<Face>();

            JFileChooser fc = new JFileChooser();
            fc.setDialogTitle("Load a file");
            fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
            if (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {

                c.remove(bkd);
                c.add(main, "Center");

                main.add(jlStatus, BorderLayout.SOUTH);
                main.add(faceCandidate, BorderLayout.NORTH);
                faceCandidate.setVisible(false);
                faceCandidate.setBackground(Color.WHITE);
                faceCandidate.setOpaque(true);
                jspFaceBrowser = new JScrollPane(faceBrowser);
                main.add(jspFaceBrowser, BorderLayout.CENTER);

                repaint();
                jlStatus.setString("Loading Files");
                jlStatus.paintImmediately(jlStatus.getVisibleRect());
                ArrayList<File> trainingSet = new ArrayList<File>();

                File folder = fc.getSelectedFile();
                //System.out.println("1	"+folder);
                FileFilter dirFilter = new FileFilter() {
                    public boolean accept(File pathname) {
                        return pathname.exists() && pathname.isDirectory();
                    }
                };
                FileFilter jpgFilter = new FileFilter() {
                    public boolean accept(File pathname) {
                        String filename = pathname.getName();
                        boolean jpgFile = (filename.toUpperCase().endsWith("JPG")
                                || filename.toUpperCase().endsWith("JPEG"));
                        return pathname.exists() && pathname.isFile() && jpgFile;
                    }
                };

                File[] folders = folder.listFiles(dirFilter);
                //System.out.println("2	"+folders);
                trainingSet.clear();
                faceBrowser.empty();

                for (int i = 0; i < folders.length; i++) {				//For each folder in the training set directory
                    File[] files = folders[i].listFiles(jpgFilter);
                    System.out.println("3	" + files);
                    for (int j = 0; j < files.length; j++) {
                        trainingSet.add(files[j]);
                    }
                }

                File[] files = trainingSet.toArray(new File[1]);

                jlist.setListData(files);
                //there is no image files in the folderwai
                //System.out.println(files);
                for (int i = 0; i < files.length; i++) {
                    //System.out.println(files[0]);
                    Face f = new Face(files[i]);
                    f.description = "Face image in database.";
                    f.classification = files[i].getParentFile().getName();
                    faceBrowser.addFace(f);
                    faces.add(f);
                }

                jlStatus.setIndeterminate(false);
                jlStatus.setString(files.length + " files loaded from " + folders.length + " folders.");
                jlStatus.paintImmediately(jlStatus.getVisibleRect());


                jspFaceBrowser.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
                main.invalidate();

                jbTrain.setEnabled(true);
                jbCropImage.setEnabled(true);
            }

        } catch (MalformedURLException e) {
            System.err.println("There was a problem opening a file : " + e.getMessage());
            e.printStackTrace(System.err);
        }
    }

    private void crop() {
        int count = 0;
        for (Face f : faces) {
            int val = (count * 100) / faces.size();
            jlStatus.setValue(val);
            jlStatus.setString(val + "%");
            jlStatus.paintImmediately(jlStatus.getVisibleRect());
            try {
                f.load(true);
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
            count++;
            jlStatus.paintImmediately(jlStatus.getVisibleRect());
        }
        jlStatus.setValue(0);
        faceBrowser.refresh();
    }

    private void train() {
        final ProgressTracker progress = new ProgressTracker();

        Runnable calc = new Runnable() {
            public void run() {
                featureSpace = new FeatureSpace();

                eigenFaces.processTrainingSet(faces.toArray(new Face[0]), progress);

                for (Face f : faces) {

                    int numVecs = 10;
                    double[] rslt = eigenFaces.getEigenFaces(f.picture, numVecs); //
                    featureSpace.insertIntoDatabase(f, rslt);
                }

                jbProbe.setEnabled(true);
                jbDisplayFeatureSpace.setEnabled(true);

                imageAverageFace = new ImageIcon(getAverageFaceImage());
                jlAverageFace.setVisible(true);
                //jlAverageFace.setIcon(imageAverageFace);
                //jlAverageFace.setText("Face Average");
            }
        };

        progress.run(main, calc, "Training");


    }

    public void saveImage(File f, BufferedImage img) throws IOException {

        Iterator writers = ImageIO.getImageWritersByFormatName("jpg");
        ImageWriter writer = (ImageWriter) writers.next();

        ImageOutputStream ios = ImageIO.createImageOutputStream(f);
        writer.setOutput(ios);

        writer.write(img);

        ios.close();
    }

    public BufferedImage getAverageFaceImage() {
        return Main.CreateImageFromMatrix(eigenFaces.averageFace.getRowPackedCopy(), IDEAL_IMAGE_SIZE.width);
    }

    public static BufferedImage CreateImageFromMatrix(double[] img, int width) {
        int[] grayImage = new int[img.length];
        double[] scales = (double[]) img.clone();
        Arrays.sort(scales);
        double min = scales[0];
        double max = scales[scales.length - 1];

        for (int i = 0; i < grayImage.length; i++) {
            double v = img[i];
            v -= min;
            v /= (max - min);
            short val = (short) (v * 255);
            grayImage[i] = (val << 16) | (val << 8) | (val);
        }
        BufferedImage bi = new BufferedImage(width, img.length / width, BufferedImage.TYPE_INT_RGB);
        bi.setRGB(0, 0, width, img.length / width, grayImage, 0, width);
        return bi;
    }

    class ProgressTracker {

        Thread thread;
        int task = 0;
        private ProgressMonitor progressMonitor;
        private Timer timer;
        private String sProgress;
        private boolean bFinished;

        public void advanceProgress(final String message) {
            task++;
            System.out.println(message);
            sProgress = "Task " + task + ": " + message;
        }

        class TimerListener implements ActionListener {

            public void actionPerformed(ActionEvent evt) {
                progressMonitor.setProgress(1);
                progressMonitor.setNote(sProgress);
                if (progressMonitor.isCanceled() || bFinished) {

                    timer.stop();
                }
            }
        }

        public void run(JComponent parent, final Runnable calc, String title) {
            bFinished = false;
            progressMonitor = new ProgressMonitor(parent,
                    title, "", 0, 100);
            progressMonitor.setProgress(0);
            progressMonitor.setMillisToDecideToPopup(0);

            timer = new Timer(100, new TimerListener());

            final SwingWorker worker = new SwingWorker() {
                public Object construct() {
                    thread = new Thread(calc);
                    thread.setPriority(Thread.MIN_PRIORITY);
                    thread.start();
                    return null;
                }
            };
            worker.start();
            timer.start();

        }

        public void finished() {
            bFinished = true;
            progressMonitor.close();
            timer.stop();
        }
    }
}