/*
 * Created on May 6, 2005
 *
 */
package com.kellegous.mas864.kernels;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.Random;

import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.vecmath.GMatrix;
import javax.vecmath.GVector;

import drej.GaussianKernel;
import drej.Kernel;
import drej.LinearKernel;
import drej.PolynomialKernel;
import drej.Regression;
import drej.Representer;

/**
 * @author knorton
 * Copyright (C) 2005 MIT Media Lab, All Right Reserved.
 */
public class ClassifierViz {
	private static double		LAMBDA = 0.01;
	
	private SampleFrame		sampleFrame = null;
	private KernelFrame		kernelFrame = null;
	
    private GMatrix			points = null;
    private GVector			groups = null;
    private Thread			backgr = null;
    private Object			locker = new Object();
    
    private KernelSelection[]	kernels = new KernelSelection[] {
            new KernelSelection("Linear Kernel",LinearKernel.KERNEL),           
            new KernelSelection("Gaussian Kernel (lambda=0.50)",new GaussianKernel(0.5)),
            new KernelSelection("Guassian Kernel (lambda=0.25)",new GaussianKernel(0.25)),
            new KernelSelection("Gaussian Kernel (lambda=0.13)",new GaussianKernel(0.13)),
            new KernelSelection("Guassian Kernel (lambda=0.01)",new GaussianKernel(0.01)),
            new KernelSelection("Second-order Polynomial Kernel",new PolynomialKernel(2)),
            new KernelSelection("Third-order Polynomial Kernel",new PolynomialKernel(3))
    };
    
    private ClassifierViz(int npoints) {
    		this.sampleFrame = new SampleFrame(800,400);
    		this.kernelFrame = new KernelFrame();        
        this.generatePoints(npoints);        
    }
    
    private void compute() {
        synchronized (this.locker) {
            if (this.backgr == null) {
                this.backgr = new Thread(this.sampleFrame.getPanel());
                this.backgr.start();   
            }
        }        
    }
    
    private int getClass(int x, int y) {
        int dx = x;//-(this.panel.getWidth()>>1);
        int dy = y-(this.sampleFrame.getPanel().getHeight()>>1);
        
        return (Math.sqrt(dx*dx + dy*dy) < 200)?1:-1;
        
    }
    
    private void generatePoints(int npoint) {
        Random 	r = new Random();
        GMatrix 	p = new GMatrix(2,npoint);
        GVector 	g = new GVector(npoint);
        
        for (int i=0;i<npoint;i++) {
            int rx = r.nextInt(this.sampleFrame.getPanel().getWidth());
            int ry = r.nextInt(this.sampleFrame.getPanel().getHeight());
            p.setColumn(i, new double[] {rx, ry });
            g.setElement(i,getClass(rx,ry));
        }
        this.points = p;
        this.groups = g;
    }
        
    private void show() {
    		this.sampleFrame.show();
        //this.kernFrame.show();
    }
    
    public static void main(String[] args) {
        new ClassifierViz(1000).show();
    }

    private static Color[]	COLOURS = new Color[] { new Color(00,33,99), new Color(99,00,33) };
    private static int[]		MAP_COLOURS = new int[] { 0x00ffcccc, 0x00ccccff };
    
    class KernelSelection {
        private String 	name;
        private Kernel	kernel;
        
        public KernelSelection(String name, Kernel kernel) {
            this.name = name;
            this.kernel = kernel;
        }
        
        public Kernel getKernel() {
            return this.kernel;
        }
        
        public String getName() {
            return this.name;
        }
        
        public String toString() {
            return this.name;
        }
    }
    
    class KernelFrame extends JFrame {
    		private Panel		panel = null;
    		private JScrollPane	scrol = null;
    		
    		public KernelFrame() {
    			this.panel = new Panel();
    			this.scrol = new JScrollPane();
    			
    			this.scrol.setPreferredSize(
    					new Dimension(400,400));
    			this.scrol.getViewport().add(this.panel);
    			this.getContentPane().add(this.scrol);
    			this.pack();
    		}

    		public void setKernelMatrix(GMatrix kernel, String name) {
    			this.setTitle(name);
    			this.setKernelMatrix(kernel);
    		}
    		
    		public void setKernelMatrix(GMatrix kernel) {
    			this.panel.setKernelMatrix(kernel);
    		}
    		
    		private void showFrame() {
    			this.show();
    		}
    		
	    class Panel extends JPanel {
	    		private BufferedImage		kernelImg = null;
	    		
	    		public Panel() {
	    			this.setPreferredSize(new Dimension(200,200));
	    		}
	    		
	    		public void setKernelMatrix(GMatrix kernel) {
	    			this.setSize(kernel.getNumRow(),kernel.getNumCol());
	    			this.setPreferredSize(new Dimension(
	    					kernel.getNumRow(),kernel.getNumCol()));
	    			this.setBackground(Color.WHITE);
	    			BufferedImage bi = new BufferedImage(
	    					kernel.getNumRow(),
						kernel.getNumCol(),
						BufferedImage.TYPE_3BYTE_BGR);
	    			double max = Double.MIN_VALUE;
	    			double min = Double.MAX_VALUE;
	    			
	    			for (int i=0;i<kernel.getNumRow();i++) {
	    				for (int j=0;j<kernel.getNumCol();j++) {
	    					double v = kernel.getElement(i,j);
	    					if (v>max)
	    						max = v;
	    					if (v<min)
	    						min = v;
	    				}
	    			}
	    			
	    			for (int i=0;i<kernel.getNumRow();i++) {
	    				for (int j=0;j<kernel.getNumCol();j++) {
	    					int v = (int)(255.0*(kernel.getElement(i,j)-min)/(max-min));
	    					bi.setRGB(i,j,(v<<16)+(v<<8)+v);
	    				}
	    			}
	    			this.kernelImg = bi;
	    			showFrame();
	    			scrol.repaint();
	    			this.repaint();
	    		}
	    		
	    		public void paintComponent(Graphics g) {
	    			super.paintComponent(g);
	    			Graphics2D gg = (Graphics2D)g;
	    			if (this.kernelImg != null)
	    				gg.drawImage(this.kernelImg,0,0,this);
	    		}
	    }
    }
    
    class SampleFrame extends JFrame {
    		
    		private Panel		panel = null;
    		private JComboBox	combo = null;
    		
    		public SampleFrame(int w, int h) {
    			super("Geometrically Classified Random Samples");
    			this.panel = new Panel(w,h);
    			this.combo = new JComboBox();
    			
    			this.getContentPane().setLayout(new BorderLayout());
    			this.getContentPane().add(this.combo,BorderLayout.NORTH);
    			this.getContentPane().add(this.panel,BorderLayout.CENTER);
    			
    			this.setResizable(false);
    			this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    			
    			for (int i=0;i<kernels.length;i++)
    				this.combo.addItem(kernels[i]);
    			this.combo.addActionListener(this.panel);
    			
    			this.pack();
    		}
    	
    		public Panel getPanel() {
    			return this.panel;
    		}
    		
	    	class Panel extends JPanel implements ActionListener, Runnable {
	        
	        private BufferedImage		mapImg = null;
	        private int[]				mapPix = null;
	        
	        public Panel(int w, int h) {
	            this.mapPix = new int[w*h];
	            this.setPreferredSize(new Dimension(w,h));
	            this.setBackground(Color.WHITE);
	        }
	        
	        public void run() {
	            KernelSelection ks = (KernelSelection)combo.getSelectedItem();
	            if (ks==null || points == null || groups == null)
	                return;
	            
	            try {
	                combo.setEnabled(false);
	                kernelFrame.setKernelMatrix(
	                		Regression.kernelMatrix(points,ks.getKernel()),
						ks.getName());
	                Representer 	r = Regression.solve(points,groups,ks.getKernel(),LAMBDA);
	                for (int i=0;i<this.mapPix.length;i++) {
	                    int 		x = i%this.getWidth();
	                    int 		y = i/this.getWidth();
	                    double 	v = r.eval(new GVector(new double[] {x,y}));
	                    int		a = (Math.abs(v)>2)?0xff:100+(int)(Math.abs(v)*155.0)&0xff;
	                    this.mapPix[i] = MAP_COLOURS[(r.eval(new GVector(new double[]{x,y}))<0)?0:1]|(a<<24);
	                    if (x==0 && ((y&0x3)==0))
	                        this.repaint();
	                }
	                backgr = null;
	            }
	            finally {
	                combo.setEnabled(true);
	            }
	            this.repaint();
	        }
	                
	        public void actionPerformed(ActionEvent ev) {
	            if (points != null && groups != null)
	                compute();
	        }
	                
	        public void paintComponent(Graphics g) {
	            super.paintComponent(g);
	            Graphics2D gg = (Graphics2D)g;
	            gg.setRenderingHint(
	                    RenderingHints.KEY_ANTIALIASING,
	                    RenderingHints.VALUE_ANTIALIAS_ON);
	            
	            if (points == null || groups == null)
	                return;
	            
	            if (mapImg == null)
	                this.mapImg = new BufferedImage(this.getWidth(),this.getHeight(),BufferedImage.TYPE_INT_ARGB);
	            this.mapImg.setRGB(0,0,this.getWidth(),this.getHeight(),this.mapPix,0,this.getWidth());
	            gg.drawImage(this.mapImg,0,0,this);
	            
	            g.setColor(Color.GREEN);
	            for (int i=0;i<points.getNumCol();i++) {
	                gg.setColor(COLOURS[(groups.getElement(i)>=0)?0:1]);
	                gg.drawOval(
	                        (int)points.getElement(0,i)-2,
	                        (int)points.getElement(1,i)-2,
	                        4,4);                
	            }
	        }
	    	}
    }
}
