/**
 * @(#)Gallery.java
 * 
 * The base class for both the Theme and Language galleries.
 *
 * @author Devrim Sahin
 * @version 1.00 21.12.2009
 */

package bin;
 
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
import javax.imageio.*;
import java.io.*;

public abstract class Gallery extends JFrame {
	
	/**
	 * <code>serialVersionUID</code><br>
	 * Created by Eclipse.
	 */
	private static final long serialVersionUID = 3871099024738801260L;

	/**
	 * <code>in</code><br>
     * The Insets object used for fitting the images in the window.
     */
    private Insets in;
    
	/**
	 * <code>imgW</code><br>
     * The width of the images shown in the gallery.
     */
    private int imgW;	
	
	/**
	 * <code>notFoundPic</code><br>
     * The picture shown when a picture could not be found.
     */
    private BufferedImage notFoundPic;
	
	/**
	 * <code>logo</code><br>
     * The logo which is shown on the top of the screen.
     */
    private BufferedImage logo;
	
	/**
	 * <code>pic</code><br>
     * The BufferedImage array storing the thumbnails.
     */
    private BufferedImage[] pic;
	
	/**
	 * <code>pic</code><br>
     * The BufferedImage array storing the reflections.
     */
    private BufferedImage[] reflection;
    
	/**
	 * <code>slideSpeed</code><br>
     * The sliding speed as a float.
     */
    private float slideSpeed = 0.0f;
	
	/**
	 * <code>relativeX</code><br>
     * The X position which determines the x position of
     * all the picture set.
     */
    private float relativeX = -10.0f;
    
	/**
	 * <code>x</code><br>
     * The x position of the cursor.
     */
    private int x=-1;
	
	/**
	 * <code>y</code><br>
     * The y position of the cursor.
     */
    private int y=-1;
	
	/**
	 * <code>indx</code><br>
     * Index of the item which is under the cursor. Initially 
     * and at the times when the cursor is over nothing, this 
     * variable is -1.
     */
    private int indx = -1;
	
	/**
	 * <code>font/code><br>
     * The font object used for displaying the name of the item.
     */
    private Font font = new Font(Font.SERIF,0,36);
	
	/**
	 * <code>groundColor</code><br>
     * The background color.
     */
    private Color groundColor;
    
	/**
	 * <code>paintLoop</code><br>
     * Thread for the paint loop.
     */
    private Thread paintLoop;
    
    /**
	 * <code>speedLoop</code><br>
     * Thread for the speed-position conversion loop.
     */
    private Thread speedLoop;
	
	/**
	 * The constructor with the default background color.
	 * @param header The header of the window.
	 * @param galleryName Name of the gallery.
	 * <br> This variable is used when finding the directory.
	 * @param size Count of the items the gallery will consist.
     */
    public Gallery(String header,String galleryName,int size) {
    	// Call the general constructor with background color of dark gray
		this(header,galleryName,size,Color.darkGray);
	}
	
	/**
	 * The constructor with a specified background color.
	 * @param header The header of the window.
	 * @param galleryName Name of the gallery.
	 * <br> This variable is used when finding the directory.
	 * @param size Count of the items the gallery will consist.
	 * @param red Red color value.
	 * @param green Green color value.
	 * @param blue Blue color value.
     */
    public Gallery(String header,String galleryName,int size,int red,int green,int blue) {
    	// Call the general constructor by generating a Color object
		this(header,galleryName,size,new Color(red,green,blue));
	}
	
    /**
	 * The constructor with a specified background color.
	 * @param header The header of the window.
	 * @param galleryName Name of the gallery.
	 * <br> This variable is used when finding the directory.
	 * @param size Count of the items the gallery will consist.
	 * @param gndClr The ground color object.
     */
    private Gallery(String header,String galleryName,int size,Color gndClr) {
    	// Call the super constructor with the header
    	super(header);
    	// Set the ground color
    	groundColor = gndClr;
    	// Set the size
    	setSize(700,350);
    	// Make it unresizable
		setResizable (false);
		// Center to the screen
		setLocationRelativeTo(null);
		// Ignore repainting, let us do it
		setIgnoreRepaint(true);
		
		// Add a mouseListener ...
		addMouseListener (new MouseAdapter() {
			// ... to ...
			public void mouseReleased(MouseEvent e) {
				// ... call onClick() method when ... 
				if (indx!=-1)
					// ... the mouse is on something
					onClick(indx);
			}
		});
		
		// Add a mouseMotionListener
		addMouseMotionListener (new MouseMotionAdapter() {
			// When the mouse is moved
			public void mouseMoved(MouseEvent e) {
				// Store x position
	            x = e.getX();
	            // Store y position
	            y = e.getY();
	            // Recalculate the speed
	            slideSpeed = (float)Math.pow(1-x/350.0,3)*300;
				// Set the cursor to "hand" if it is on something, "default" otherwise
				setCursor((indx==-1)?Cursor.DEFAULT_CURSOR:Cursor.HAND_CURSOR);
            }
		});
		
		// Create the array for the images
		pic = new BufferedImage[size];
		// Create the array for the reflections
		reflection = new BufferedImage[size];

		// Try to ...
		try {
			// ... load the notFoundPic ...
            notFoundPic = ImageIO.read(new File("images/gallery/"+galleryName+"/notFound.jpg"));
            // ... and the logo
            logo = ImageIO.read(new File("images/gallery/logo.png"));
        // If one of these pictures could not be loaded
        } catch (IOException e) {
        	// Dispose the window
        	dispose();
        	// Leave the code
        	return;
        }
        // Calculate the width
        imgW = notFoundPic.getWidth();
        // Calculate the total width of the image set, including the offsets
		final int length = (imgW+10)*pic.length-10;
		// For each picture in the array
		for (int n = 0; n<pic.length;n++) {
			// Try to ...
			try {
				// ...  load the picture
	            pic[n] = ImageIO.read(new File("images/gallery/"+galleryName+"/"+n+".jpg"));
	        // If the picture is not there
	        } catch (IOException e) {
	        	// We have notFoundPic instead
	        	pic[n] = notFoundPic;
	        }
			
			// Create the reflection for this image
			reflection[n] = new BufferedImage(imgW,70, BufferedImage.TYPE_INT_ARGB);
			// Get its Graphics object
	        Graphics2D rg = reflection[n].createGraphics();
	        rg.translate(0,-70);
	        // Draw the real image
	        rg.drawRenderedImage(pic[n],null);
	        // Set the composite to AlphaComposite
	        rg.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_IN));
	        // Set the brush to another gradient
	        rg.setPaint(new GradientPaint(0, 140,
	                                      new Color(0.0f, 0.0f, 0.0f, 0.8f),
	                                      0, 70,
	                                      new Color(0.0f, 0.0f, 0.0f, 0.0f))
	        );
			// Fill the rectangle with the alpha thing
	        rg.fillRect(0, 0, imgW, 140);
	        // Dispose the Graphics object
	        rg.dispose();
		}
		
		// Show the window
		setVisible(true);
		// Create 2 buffers
		createBufferStrategy(2);
		// Get the insets
		in = getInsets();
		// Set the size again
		setSize(700+in.left+in.right,350+in.top+in.bottom);
		// Center the screen again
		setLocationRelativeTo(null);
		// If the length is larger than
		if (length>700) {
			// Then define a Thread ...
			speedLoop = new Thread() {
				// ... which will ...
				public void run() {
					// ... continuously ...
					while(isVisible()) {
						// Calculate the relativeX
						relativeX = clamp(relativeX + slideSpeed,-(imgW+10)*pic.length+700,10);
						// And wait for 50 milliseconds
						AnimatedNotifier.wait(50);
					}
				}
			};
		// Start the Thread
		speedLoop.start();
		// If the length is smaller than 700
		} else
			// We don't need another Thread to manage scrolling, center the items to the screen
			relativeX = 350-length/2;
		// Create another Thread ...
		paintLoop = new Thread() {
			// ... to render the gallery to the screen
			public void run() {
				// Get the BufferStrategy
				BufferStrategy buffy = getBufferStrategy();
				// Create a Graphics2D object
				Graphics2D g2d;
				// Then continuously ...
				while (isVisible())
				// ...try to...
				try {
					// ... get the current Graphics2D object ...
					g2d = (Graphics2D) buffy.getDrawGraphics();
					// ... give it text-antialiasing feature ...
					g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
					// ... and draw to it
					draw(g2d);
					// Dispose the Graphics2D object
					g2d.dispose();
					// Show the buffer
					buffy.show();
				// If an exception is thrown ignore it
				} catch (Exception e) {}
			}
		};
		// Start the thread
		paintLoop.start();
    }
    
    /**
	 * <code>draw</code><br>
	 * The method which manages the drawing jobs.
	 * @param g2d Graphics2D object to draw on.
     */
    private void draw (Graphics2D g2d) {
    	// Translate the canvas to the inner part of the screen
    	g2d.translate(in.left,in.top);
    	// Set the "brush" to a gradient which starts with black and ends with groundColor
    	g2d.setPaint(new GradientPaint(0, 0, Color.black, 0, 350, groundColor));
    	// Fill the screen
    	g2d.fillRect(0,0,700,350);
    	// For now, nothing is selected
    	boolean selected = false;
    	// For each image
		for (int n = 0; n<pic.length;n++) {
			// Define its x position
			int posX = (imgW+10)*n + (int) relativeX;
			// If it is out of the window
			if (posX < -imgW || posX > 700)
				// Advance to the next one
				continue;
			
			// The "elevation" is by default zero
			int elevation=0;
			// But if we are in the horizontal bounds
			if (x>posX && x<imgW+posX) {
				// Calculate the elevation
		        elevation = Math.round(2*10*Math.max(0,imgW/2-Math.abs(posX-x+imgW/2))/imgW);
		        // If we are out of the vertical boundary
		        if (y>260-elevation+in.top || y<120-elevation+in.top)
		        	// Nothing is selected
	        		indx = -1;
	        	// If we are on the picture
	        	else {
	        		// Set the index
		        	indx=n;
		        	// Make the boolean "selected" true
		        	selected=true;
		        	// Calculate the average color
					groundColor = averageColor(pic[n],x-posX,y-120+elevation-in.top);
	        	}
			}
			// Draw the picture
			g2d.drawImage(pic[n],null,posX,120-elevation);
			// And draw its reflection
        	g2d.drawImage(reflection[n],posX,330+elevation,reflection[n].getWidth(),-70,null);
		}
		// If nothing is selected
		if (!selected)
			// Then set the index to -1
			indx=-1;
		// Create another gradient
		g2d.setPaint(new GradientPaint(0,0,new Color(0.0f,0.0f,0.0f,0.7f),250,0,new Color(0.0f,0.0f,0.0f,0.0f)));
		// Create a shadow at the left
		g2d.fillRect(0, 0, 250, 350);
		// Create one more gradient
		g2d.setPaint(new GradientPaint(450,0,new Color(0.0f,0.0f,0.0f,0.0f),700,0,new Color(0.0f,0.0f,0.0f,0.7f)));
		// Create a shadow at the right
		g2d.fillRect(450,0, 700, 350);
		// If something is hovered
		if (indx != -1) {
			// Set the color to white
			g2d.setColor(Color.WHITE);
			// Also set the font this time
			g2d.setFont(font);
			// Draw some info String about it
			g2d.drawString(onMouseOver(indx),180,80);
		}
		// Draw the logo
		g2d.drawImage(logo,null,80,20) ;
    }
    
    /**
	 * <code>onMouseOver</code><br>
	 * A method which is invoked when an item is hovered.
	 * The child classes have to determine a proper String
	 * explanation for the item regarding its index number.
	 * @param i The index of the hovered item.
	 * @return A String to represent the object on the screen.
     */
    public abstract String onMouseOver (int i);
    
    /**
	 * <code>onClick</code><br>
	 * A method which is invoked when an item is clicked.
	 * The child classes have to determine a the things to 
	 * do when an item is clicked regarding its index number.
	 * @param i The index of the clicked item.
     */
    public abstract void onClick (int i);
        
    /**
	 * <code>clamp</code><br>
	 * A method to "clamp" a float value between two integers.
	 * @param a The float to clamp.
	 * @param min The minimum value.
	 * @param max The maximum value.
	 * @return float The clamped value.
     */
    private static float clamp (float a,int min,int max) {
    	return (a>max)?max:(a<min)?min:a;
    }
    
    /**
	 * <code>averageColor</code><br>
	 * A method to find the average color image small 
	 * neighborhood of the specified coordinates on 
	 * the specified image.
	 * @param bi The BufferedImage to work on.
	 * @param x The x coordinate.
	 * @param y The y coordinate.
	 * @return A Color object to represent the average color.
     */
    private Color averageColor (BufferedImage bi,int x,int y) {
		// Define the neighborhood as 20 pixels
		final int a=10;
		// Define r,g,b and pixel to represent red,
		// green,blue and all the color itself
        int r=0, g=0, b=0, pixel;
		// Get width and height of the image
		int biw = bi.getWidth(), bih = bi.getHeight();
		// Determine minimum and maximum x values
		int xmin = (x<a)?0:x-a;
		int xmax = (x>biw-a)?biw:x+a;
		// Determine minimum and maximum y values
		int ymin = (y<a)?0:y-a;
		int ymax = (y>bih-a)?bih:y+a;
		// From xmin up to xmax
        for (int xx=xmin; xx < xmax; xx++) {
        	// And from ymin up to ymax
            for (int yy=ymin; yy < ymax; yy++) {
            	// Get the RGB values and store it in pixel
                pixel = bi.getRGB(xx, yy);
				// Analyze red, green and blue and add them
                r += pixel >> 16 & 0xFF;
                g += pixel >> 8 & 0xFF;
                b += pixel & 0xFF;
            }
        }
        // Define how many pixels are added
        int count = (xmax-xmin)*(ymax-ymin);
        // Divide each color to count
        r /= count;
        g /= count;
        b /= count;			
		// Create a new Color object and return it
        return new Color(r << 16 | g << 8 | b);
    }
}