package jamaica;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;

/** 
 * The painting class that draws the contents of a matrix in a window.
 * This class is derived from class JPanel in Java Swing package.
 *
 * @author Hanhua Feng - hanhua@cs.columbia.edu
 * @version $Id: Painter.java,v 1.16 2003/05/21 15:37:14 hanhua Exp $
 */
public class Painter extends JPanel {
    private static int[] grayscale = null;
    private JFrame frame;
    private Image image = null;

    /** default color table: black white color table */
    public static final int CM_BLACKWHITE = 0;
    /** default color table: gray scaled color table */
    public static final int CM_GRAYSCALE = 1;
    /** default color table: all light colors (with the maximum saturation) */
    public static final int CM_COLORS = 2;
    /** default color table: all dark colors (with the maximum saturation) */
    public static final int CM_DARKCOLORS = 3;
    /** default color table: hot colors from red to yellow to white */
    public static final int CM_HOTCOLORS = 4;
    /** default color table: warm colors from green to blue */
    public static final int CM_WARMCOLORS = 5;
    /** default color table: cold colors from blue to violet */
    public static final int CM_COLDCOLORS = 6;

    private static int frame_counter = 0;

    /** create a painting panel
     * @param frame    the parent frame where the painting panel is located
     */
    public Painter( JFrame frame ) {
        this.frame = frame;
    }

    /** create a color represented by an integer 
     * @param r       red value, 0.0-1.0
     * @param g       green value, 0.0-1.0
     * @param b       blue value, 0.0-1.0
     * @return        the color represented by an integer
     */
    public final static int color( double r, double g, double b ) {
        return ( 0xff & (int)(b * 255) )
            + ( ( 0xff & (int)(g * 255) ) << 8 )
            + ( ( 0xff & (int)(r * 255) ) << 16 );
    }

    /** create corresponding color map
     * @param type     the id of color maps
     * @return      the color map integer array
     */
    public final static int[] colorMap( int type ) {
        int[] cm;

        switch ( type ) 
        {
        case CM_BLACKWHITE:
            cm = new int[]{0, 0xFFFFFF};
            break;
        case CM_GRAYSCALE:
            cm = new int[256];
            for ( int i=0; i<256; i++ )
                cm[i] = (i<<16) | (i<<8) | i;
            break;
        case CM_COLORS:
            cm = new int[192];
            for ( int i=0; i<64; i++ )
                cm[i] = (i<<18) | ((63-i)<<10) | 0xff;
            for ( int i=0; i<64; i++ )
                cm[i+64] = (i<<10) | ((63-i)<<2) | 0xff0000;
            for ( int i=0; i<64; i++ )
                cm[i+128] = (i<<2) | ((63-i)<<18) | 0xff00;
            break;
        case CM_DARKCOLORS:
            cm = new int[192];
            for ( int i=0; i<64; i++ )
                cm[i] = (i<<18) | ((63-i)<<10);
            for ( int i=0; i<64; i++ )
                cm[i+64] = (i<<2) | ((63-i)<<18);
            for ( int i=0; i<64; i++ )
                cm[i+128] = (i<<10) | ((63-i)<<2);
            break;
        case CM_HOTCOLORS:
            cm = new int[128];
            for ( int i=0; i<64; i++ )
                cm[i] = 0xff0000 | (i<<10);
            for ( int i=0; i<64; i++ )
                cm[i+64] = 0xffff00 | (i<<2);
            break;
        case CM_WARMCOLORS:
            cm = new int[64];
            for ( int i=0; i<64; i++ )
                cm[i] = 0xff00 | (i<<2);
            break;
        case CM_COLDCOLORS:
            cm = new int[32];
            for ( int i=0; i<32; i++ )
                cm[i] = ((63-i)<<18) | (i<<2);
            break;            
        default:
            return new int[] {0};
        }

        return cm;
    }

    /** create a color map form a matrix by columns.  The entries of this
     * matrix is between 0.0 and 1.0.
     * @param map   a matrix whose rows are blue, green and red, respectively
     * @return      the color map integer array
     */
    public final static int[] colorMap( Matrix map ) {
        int n = map.width();
        int[] cm = new int[n];
        for ( int i=0; i<n; i++ )
            cm[i] = color( map.get(2,i), map.get(1,i), map.get(0,i) );
        return cm;
    }


    /** create an painting panel inside a new painting frame window
     * @param title  the title of the frame
     * @param width  the width of the painting panel
     * @param height the height of the painting panel
     * @return       the painter panel
     */
    public static Painter create( String title, int width, int height ) {
        try {
            UIManager.setLookAndFeel(
                UIManager.getCrossPlatformLookAndFeelClassName() );
        } catch (Exception e) {}

        JFrame frame = new JFrame( title );
        Painter pane = new Painter( frame );
        frame.getContentPane().add( pane, BorderLayout.CENTER );

        frame_counter++;

        frame.addWindowListener( 
            new WindowAdapter() 
            {
                public void windowClosing( WindowEvent e ) {
                    frame_counter--;
                    /* System.exit(0); */
                }
            });

        pane.newsize( width, height );

        frame.setLocationRelativeTo( null );
        frame.setVisible( true );

        return pane;
    }

    /* change the dimensions of the drawing panel */
    void newsize( int width, int height ) {
        setPreferredSize( new Dimension( width, height ) );
        image = new BufferedImage( width, height, BufferedImage.TYPE_INT_RGB );
        frame.pack();
    }

    /** draw a matrix in a panel with a color table and adjust the
     * dimensions of the panel accordingly
     * @param mat    the matrix
     * @param min    the value of the first color
     * @param max    the value of the last color
     * @param rgbtable the color table for intermediate values
     */
    public void draw( Matrix mat, double min, double max, int[] rgbtable ) {

        int n = mat.width(), m = mat.height();
        int [] row = new int[n+n+n];

        newsize( n, m );

        WritableRaster raster = ((BufferedImage)image).getRaster();

        double factor = (max > min) ? rgbtable.length / ( max - min ) : 0;

        for ( int i=0; i<m; i++ )
        {
            for ( int j=0; j<n; j++ )
            {
                int k = (int)Math.round((mat.get(i,j)-min)*factor);
                if ( k < 0 )
                    k = 0;
                if ( k >= rgbtable.length )
                    k = rgbtable.length - 1;
                row[j+j+j] = ( rgbtable[k] >> 16 ) & 0xff;
                row[j+j+j+1] = ( rgbtable[k] >> 8 ) & 0xff;
                row[j+j+j+2] = ( rgbtable[k] ) & 0xff;
            }
            raster.setPixels( 0, i, n, 1, row );
        }

        repaint();
    }

    private int[] getGrayScale() {
        if ( null == grayscale )
            grayscale = colorMap( CM_GRAYSCALE );

        return grayscale;
    }

    /** draw a matrix in a panel as a grayscaled image and adjust the
     * dimensions of the panel accordingly
     * @param mat    the matrix
     * @param min    the value of white
     * @param max    the value of black
     */
    public void draw( Matrix mat, double min, double max ) {
        draw( mat, min, max, getGrayScale() );
    }

    /** draw a matrix in a panel with a color table and adjust the
     * dimensions of the panel accordingly. The min and max values of
     * the matrix are mapped to white and black, respectively.
     * @param mat    the matrix
     * @param rgbtable the color table for intermediate values
     */
    public void draw( Matrix mat, int[] rgbtable ) {
        draw( mat, mat.min(), mat.max(), rgbtable );
    }

    /** draw a matrix in a panel as a grayscaled image and adjust the
     * dimensions of the panel accordingly
     * @param mat    the matrix
     */
    public void draw( Matrix mat ) {
        draw( mat, mat.min(), mat.max() );
    }

    /** draw a matrix in a panel over an existing image at some place, 
     * with a color table and a pixel mask.
     * @param mat    the matrix
     * @param bmk    the mask indicating whether a matrix entry is drawn.
     * @param min    the value of the first color
     * @param max    the value of the last color
     * @param rgbtable the color table for intermediate values
     * @param x      the X-coordinate
     * @param y      the Y-coordinate
     */
    public void overdraw( Matrix mat, BitArray bmk, double min, double max,
                          int[] rgbtable, int x, int y ) {

        WritableRaster raster = ((BufferedImage)image).getRaster();
        int n = mat.width(), m = mat.height();
        int[] rgb = new int[3];
        double factor = (max > min) ? rgbtable.length / ( max - min ) : 0;

        for ( int i=0; i<m; i++ )
        {
            for ( int j=0; j<n; j++ )
            {
                if ( bmk != null && !mat.getBitMask(bmk,i,j) )
                    continue;
                int k = (int)Math.round((mat.get(i,j)-min)*factor);
                if ( k < 0 )
                    k = 0;
                if ( k >= rgbtable.length )
                    k = rgbtable.length - 1;
                rgb[0] = ( rgbtable[k] >> 16 ) & 0xff;
                rgb[1] = ( rgbtable[k] >> 8 ) & 0xff;
                rgb[2] = ( rgbtable[k] ) & 0xff;
                raster.setPixel( x+j, y+i, rgb );
            }
        }

        repaint(); 
    }

    /** draw a matrix in a panel over an existing image at some place,
     * with a color table and a pixel mask.  The min and max values of
     * the matrix are mapped to white and black, respectively.
     * @param mat    the matrix
     * @param bmk    the mask indicating whether a matrix entry is drawn.
     * @param rgbtable the color table for intermediate values
     * @param x      the X-coordinate
     * @param y      the Y-coordinate
     */
    public void overdraw( Matrix mat, BitArray bmk, int[] rgbtable, 
                          int x, int y ) {
        overdraw( mat, bmk, mat.min(), mat.max(), rgbtable, x, y );
    }

    /** draw a matrix in a panel over an existing image at some place,
     * with a color table.  The min and max values of the matrix are
     * mapped to white and black, respectively.
     * @param mat    the matrix
     * @param rgbtable the color table for intermediate values
     * @param x      the X-coordinate
     * @param y      the Y-coordinate
     */
    public void overdraw( Matrix mat, int[] rgbtable, int x, int y ) {
        overdraw( mat, null, mat.min(), mat.max(), rgbtable, x, y );
    }

    /** draw a matrix in a panel over an existing image at the orgin,
     * with a pixel mask.  The min and max values of the matrix are
     * mapped to white and black, respectively.
     * @param mat    the matrix
     * @param bmk    the mask indicating whether a matrix entry is drawn.
     * @param rgbtable the color table for intermediate values
     */
    public void overdraw( Matrix mat, BitArray bmk, int[] rgbtable ) {
        overdraw( mat, bmk, mat.min(), mat.max(), rgbtable, 0, 0 );
    }

    public void paintComponent( Graphics g ) {
        super.paintComponent (g);

        Insets insets = getInsets();
        int width = getWidth() - insets.left - insets.right;
        int height = getHeight() - insets.top - insets.bottom;

        if ( null != image )
            g.drawImage( image, 0, 0, width, height, this );
    }

    /** return the number of plotter windows */
    public static int frameCount() {
        return frame_counter;
    }

    /* the test program */
    public static void main( String[] args ) {
        create( "test", 200, 200 ).draw( Matrix.random( 128, 512 ) );
    }
}
