import com.jogamp.opengl.GL2;
import com.jogamp.opengl.glu.GLU;
import com.jogamp.opengl.GLAutoDrawable;
import com.jogamp.opengl.GLCapabilities;
import com.jogamp.opengl.GLEventListener;
import com.jogamp.opengl.GLProfile;
import com.jogamp.opengl.awt.GLCanvas;

import javax.swing.JFrame;
import java.awt.Color;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;

import java.util.ArrayList;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.io.File;
import java.util.Collections;

import java.awt.image.BufferedImage;

public class OGL_Window implements GLEventListener, KeyListener, ActionListener {
    private int imgHeight = 200;
    
    private JFrame frame;
    private JPanel drawPanel;
    private JPanel drawImg;
    private JPanel drawQImg;
    
    private JButton vl;
    private JButton vr;
    private JButton vu;
    private JButton vd;
    
    private JButton b_load;
    private JButton b_gen;
    private JButton b_epoc;
    private JButton b_reset;
    private JButton b_save;

    private JComboBox s_input;
    private JTextField t_anz;
    private JTextField t_output;

    private final GLCanvas glcanvas;
    
    private ArrayList<RGB> image;
    private ArrayList<RGB> orig;
    private ArrayList<RGB> quants;
    
    private BufferedImage img;
    private BufferedImage qimg;
    
    private int azimut = 25;
    private int rot = 45;
    
    @Override
    public void display(GLAutoDrawable drawable) {
        final GL2 gl = drawable.getGL().getGL2();

        gl.glClear(GL2.GL_COLOR_BUFFER_BIT | GL2.GL_DEPTH_BUFFER_BIT | GL2.GL_STENCIL_BUFFER_BIT);

        final GLU glu = GLU.createGLU(gl);
        gl.glMatrixMode(GL2.GL_PROJECTION);
        gl.glLoadIdentity();
        double w = this.drawPanel.getWidth();
        double h = this.drawPanel.getHeight();
        glu.gluPerspective(45,w/h,0.1,100);
        gl.glMatrixMode(GL2.GL_MODELVIEW);
        gl.glLoadIdentity();
        glu.gluLookAt(
            0,0,3,
            0,0,0,
            0,1,0
        );
        gl.glRotatef(this.azimut,1,0,0);
        gl.glRotatef(-this.rot,0,1,0);

        gl.glLineWidth(5);
        gl.glBegin(GL2.GL_LINES);
        
        gl.glColor4f(0,0,0,1);
        gl.glVertex3f(0,0,0);
        gl.glColor4f(1,0,0,1);
        gl.glVertex3f(1,0,0);
        
        gl.glColor4f(0,0,0,1);
        gl.glVertex3f(0,0,0);
        gl.glColor4f(0,1,0,1);
        gl.glVertex3f(0,1,0);
        
        gl.glColor4f(0,0,0,1);
        gl.glVertex3f(0,0,0);
        gl.glColor4f(0,0,1,1);
        gl.glVertex3f(0,0,1);
        gl.glEnd();
                
        gl.glEnable(GL2.GL_BLEND);
        gl.glBlendFunc(GL2.GL_SRC_ALPHA, GL2.GL_ONE_MINUS_SRC_ALPHA);

        gl.glPointSize(8);
        gl.glBegin(GL2.GL_POINTS);
        for (RGB p : this.image) {
            gl.glColor4f(((float)(p.r))/256/4,((float)(p.g))/256/4,((float)(p.b))/256/4,0.15f);
            gl.glVertex3f(
                ((float)(p.r))/256,
                ((float)(p.g))/256,
                ((float)(p.b))/256
            );
        }
        gl.glEnd();
        
        gl.glPointSize(20);
        gl.glBegin(GL2.GL_POINTS);
        for (RGB p : this.quants) {
            gl.glColor4f(((float)(p.r))/256,((float)(p.g))/256,((float)(p.b))/256,1f);
            gl.glVertex3f(
                ((float)(p.r))/256,
                ((float)(p.g))/256,
                ((float)(p.b))/256
            );
        }
        gl.glEnd();
        
        gl.glPointSize(20);
        gl.glBegin(GL2.GL_POINTS);
        gl.glColor4ub((byte)0,(byte)0,(byte)0,(byte)255);
        gl.glVertex3f(0,0,0);
        gl.glEnd();
    }
    
    @Override
    public void dispose(GLAutoDrawable arg0) {}

    @Override
    public void init(GLAutoDrawable drawable) {} 

    @Override
    public void reshape(GLAutoDrawable drawable, int arg1, int arg2, int arg3, int arg4) {}
    
    @Override
    public void keyPressed(KeyEvent e) {}
    
    @Override
    public void keyTyped(KeyEvent e) {}
    
    @Override
    public void keyReleased(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_LEFT) {
            this.rot += 15;
        } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
            this.rot -= 15;
        } else if (e.getKeyCode() == KeyEvent.VK_UP) {
            this.azimut -= 15;
        } else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
            this.azimut += 15;
        } else {
            return;
        }
        
        this.glcanvas.repaint();
    }
    
    private OGL_Window() {
        this.image = new ArrayList<>();
        this.orig = new ArrayList<>();
        this.quants = new ArrayList<>();
        
        //getting the capabilities object of GL2 profile
        final GLProfile profile = GLProfile.get(GLProfile.GL2);
        GLCapabilities capabilities = new GLCapabilities(profile);
        capabilities.setBackgroundOpaque(false);

        // The canvas
        glcanvas = new GLCanvas(capabilities);
        glcanvas.addGLEventListener(this);
        glcanvas.setSize(500, 500);
        glcanvas.setBackground(new Color(220, 220, 220));
        glcanvas.addKeyListener(this);

        OGL_Window that = this;

        //creating frame
        frame = new JFrame("RGB Quantisierer");
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) { System.exit(0); }
        });
        frame.addComponentListener(new ComponentAdapter() {
           public void componentResized(ComponentEvent e) {
               int width = (int)that.frame.getSize().getWidth();
               int height = (int)that.frame.getSize().getHeight() - 160 - that.imgHeight;
               that.glcanvas.setSize(width, height);
               that.drawPanel.setSize(new Dimension(width, height));
           }
        });
        
        JPanel mainPanel = new JPanel();
        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
        
        drawPanel = new JPanel();
        JPanel view = new JPanel();
        JPanel steps = new JPanel();
        JPanel config = new JPanel();
        JPanel images = new JPanel();

        mainPanel.add(drawPanel);
        mainPanel.add(view);
        mainPanel.add(steps);
        mainPanel.add(config);
        mainPanel.add(images);
        
        vl = new JButton("<");
        vl.addActionListener(this);
        view.add(vl);
        vr = new JButton(">");
        vr.addActionListener(this);
        view.add(vr);
        vu = new JButton("^");
        vu.addActionListener(this);
        view.add(vu);
        vd = new JButton("v");
        vd.addActionListener(this);
        view.add(vd);
        
        b_load = new JButton("Datei laden");
        b_load.addActionListener(this);
        steps.add(b_load);
        
        b_gen = new JButton("Zufällige Punkte erzeugen");
        b_gen.addActionListener(this);
        steps.add(b_gen);

        b_epoc = new JButton("Epoche durchspielen");
        b_epoc.addActionListener(this);
        steps.add(b_epoc);

        b_reset = new JButton("Punkte versetzen");
        b_reset.addActionListener(this);
        steps.add(b_reset);
        
        b_save = new JButton("Datei speichern");
        b_save.addActionListener(this);
        steps.add(b_save);
        
        config.add(new JLabel("Input:"));
        s_input = new JComboBox(getJPGs());
        config.add(s_input);

        config.add(new JLabel("Anzahl Punkte:"));
        t_anz = new JTextField("32");
        t_anz.setPreferredSize(new Dimension(100,20));
        config.add(t_anz);

        config.add(new JLabel("Output:"));
        t_output = new JTextField("output.jpg");
        t_output.setPreferredSize(new Dimension(100,20));
        config.add(t_output);
        
        drawImg = new JPanel();
        drawImg.setPreferredSize(new Dimension(this.imgHeight + 50,this.imgHeight + 10));
        drawQImg = new JPanel();
        drawQImg.setPreferredSize(new Dimension(this.imgHeight + 50,this.imgHeight + 10));
        images.add(drawImg);
        images.add(drawQImg);
        
        frame.add(mainPanel);
        drawPanel.add(glcanvas);

        frame.pack();
        frame.setVisible(true);
    }
    
    /**
     * Button-Reaktivität
     * 
     * @param e ActionEvent
     */
    public void actionPerformed(ActionEvent e) {
        /**
         * Pfeilbuttons: Ändern der Ansicht
         */
        if (e.getSource() == this.vr) this.rot = (this.rot + 345) % 360;
        if (e.getSource() == this.vl) this.rot = (this.rot + 15) % 360;
        if (e.getSource() == this.vu) this.azimut = (this.azimut + 345) % 360;
        if (e.getSource() == this.vd) this.azimut = (this.azimut + 15) % 360;
        
        /**
         * Datei laden und RGB-Daten in ArrayList speichern
         */
        if (e.getSource() == this.b_load) {
            try {
                this.orig = Image.readFile("./inputs/" + (String)this.s_input.getSelectedItem());
                this.img = Image.loadFile("./inputs/" + (String)this.s_input.getSelectedItem());
                this.image = new ArrayList<>();
                for (RGB p : this.orig) {
                    // kopiere die originalen Daten, damit die Liste später gemischt werden kann
                    this.image.add(p);
                }
                
                this.drawImage(this.img, this.drawImg);
            } catch (Exception ex) {}
        }
        /**
         * Zufällige Punkte erzeugen
         */
        if (e.getSource() == this.b_gen) {
            this.quants = new ArrayList<>();
            for (int i = 0; i < Integer.parseInt(this.t_anz.getText()); i++) {
                this.quants.add(new RGB(true));
            }

            try {
                BufferedImage i = Image.writeFile("./inputs/" + (String)this.s_input.getSelectedItem(), "", this.orig, this.quants);
                this.drawImage(i, this.drawQImg);
            } catch (Exception ex) {}
        }
        /**
         * Epoche durchlaufen
         */
        if (e.getSource() == this.b_epoc) {
            Quant.doEpoch(this.image, this.quants);

            try {
                BufferedImage i = Image.writeFile("./inputs/" + (String)this.s_input.getSelectedItem(), "", this.orig, this.quants);
                this.drawImage(i, this.drawQImg);
            } catch (Exception ex) {}
        }
        /**
         * Nicht bewegte Punkte versetzen
         */
        if (e.getSource() == this.b_reset) {
            Quant.resetQuants(this.quants);

            try {
                BufferedImage i = Image.writeFile("./inputs/" + (String)this.s_input.getSelectedItem(), "", this.orig, this.quants);
                this.drawImage(i, this.drawQImg);
            } catch (Exception ex) {}
        }
        /**
         * quantifiziertes Bild speichern
         */
        if (e.getSource() == this.b_save) {
            try {
                Image.writeFile("./inputs/" + (String)this.s_input.getSelectedItem(), "./outputs/" + this.t_output.getText(), this.orig, this.quants);
            } catch (Exception ex) {}
        }

        // neue Anzeige rendern
        this.glcanvas.repaint();
    }
    
    private void drawImage(BufferedImage i, JPanel p) {
        int w = i.getWidth();
        int h = i.getHeight();
        
        if (w > h) {
            h = h * this.imgHeight / w;
            w = this.imgHeight;
        } else {
            w = w * this.imgHeight / h;
            h = this.imgHeight;
        }
        
        JLabel img = new JLabel(new ImageIcon(i.getScaledInstance(w,h,1)));
        img.setPreferredSize(new Dimension(this.imgHeight, this.imgHeight));
        p.removeAll();
        p.add(img);
        p.revalidate();
    }
    
    /**
     * Lies die vorhandenen JPG-Dateien aus dem Ordner inputs aus
     * 
     * @return String[] Dateinamen
     */
    private String[] getJPGs() {
        ArrayList<String> result = new ArrayList<String>();
        
        File d = new File("./inputs/");
        File[] list = d.listFiles();
        for(int i=0; i<list.length; i++) {
            String n = list[i].getName();
            // nur JPG-Bilder
            if (n.matches(".*\\.[jJ][pP][eE]?[gG]$")) {
                result.add(n);
            }
        }

        String[] ret = new String[result.size()];
        return result.toArray(ret);
    }

    public static void main() throws Exception {
        new OGL_Window();
    }
}