package j3deditor.bin.hierarchy;


import j3deditor.bin.hierarchy.util.HNodeType;

import java.awt.Graphics;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Image;

import java.awt.Transparency;
import java.awt.image.BufferedImage;

import java.awt.image.PixelGrabber;

import javax.media.j3d.ImageComponent;
import javax.media.j3d.ImageComponent2D;
import javax.media.j3d.ImageComponent3D;

import javax.swing.ImageIcon;


/**
 * Utility for manipulating <code>javax.media.j3d.ImageComponent</code>.
 *
 * @author Risto Seene
 * 
 * @see javax.media.j3d.ImageComponent
 */
public class J3DeImageComponent extends HierarchyNode{

	/** number of <code>J3DeImageComponent</code> nodes */
	private static long numOfNodes = 0;

	/** actual <code>ImageComponent</code> object */
	private ImageComponent image;


	/**
	 * Creates an instance of <code>J3DeImageComponent</code>
	 * with the given <code>ImageComponent</code>.
	 *
	 * @param image
	 *	<code>ImageComponent</code> to be applied to this node
	 */
	public J3DeImageComponent(ImageComponent image){
		super("ImageComponent", HNodeType.IMAGE_COMPONENT, numOfNodes);
		numOfNodes++;

		this.image = image;
		setCapabilities(this.image);
	}


	/**
	 * Sets the capabilities for the <code>ImageComponent</code> object.
	 *
	 * <br><ul><li><code>ImageComponent.ALLOW_FORMAT_READ</code></li>
	 * <li><code>ImageComponent.ALLOW_IMAGE_READ</code></li>
	 * <li><code>ImageComponent.ALLOW_IMAGE_WRITE</code></li>
	 * <li><code>ImageComponent.ALLOW_SIZE_READ</code></li></ul>
	 *
	 * @see javax.media.j3d.ImageComponent#setCapability(int capability)
	 */
	private static void setCapabilities(ImageComponent image){
		image.setCapability(ImageComponent.ALLOW_FORMAT_READ);
		image.setCapability(ImageComponent.ALLOW_IMAGE_READ);
		image.setCapability(ImageComponent.ALLOW_IMAGE_WRITE);
		image.setCapability(ImageComponent.ALLOW_SIZE_READ);
	}

	/**
	 * Converts <code>Image</code> to <code>BufferedImage</code>.
	 * 
	 * @param image
	 * 	image to be converted
	 * @return <code>BufferedImage</code> created from <code>Image</code>
	 */
	public static BufferedImage toBufferedImage(Image image){
		if(image instanceof BufferedImage){
			return (BufferedImage)image;
		}

		image = new ImageIcon(image).getImage();
		boolean hasAlpha = hasAlpha(image);

		BufferedImage bimage = null;
		GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
		try{
			int transparency = Transparency.OPAQUE;
			if(hasAlpha == true){
				transparency = Transparency.BITMASK;
			}

			GraphicsDevice gs = ge.getDefaultScreenDevice();
			GraphicsConfiguration gc = gs.getDefaultConfiguration();
			bimage = gc.createCompatibleImage(image.getWidth(null), image.getHeight(null), transparency);
		} catch(HeadlessException e){
		}

		if(bimage == null){
			int type = BufferedImage.TYPE_INT_RGB;
			if(hasAlpha == true){
				type = BufferedImage.TYPE_INT_ARGB;
			}
			bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type);
		}

		Graphics g = bimage.createGraphics();

		g.drawImage(image, 0, 0, null);
		g.dispose();

		return bimage;
	}

	/**
	 * Tests if the specified image has alpha channel.
	 * 
	 * @param image
	 * 	image to be tested
	 * @return true if the specified image has alpha channel, otherwise false
	 */
	public static boolean hasAlpha(Image image){
		if(image instanceof BufferedImage){
			return ((BufferedImage)image).getColorModel().hasAlpha();
		}

		PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false);
		try{
			pg.grabPixels();
		} catch(InterruptedException e){
		}

		return pg.getColorModel().hasAlpha();
	}

	/**
	 * Returns an image from this ImageComponent.
	 * 
	 * @param index
	 * 	index of image (ImageComponent3D)
	 * @return image from the specified index
	 */
	public Image getImage(int index){
		if(this.image instanceof ImageComponent2D){
			return ((ImageComponent2D)this.image).getImage();
		}
		else if(this.image instanceof ImageComponent3D){
			return ((ImageComponent3D)this.image).getImage(index);
		}
		return null;
	}

	/**
	 * Applies the specified image to this ImageComponent.
	 * 
	 * @param image
	 * 	image to be applied
	 * @param index
	 * 	index of image (ImageComponent3D)
	 */
	public void setImage(Image image, int index){
		BufferedImage bufImg = this.toBufferedImage(image);

		if(this.image instanceof ImageComponent2D){
			((ImageComponent2D)this.image).set(bufImg);
		}
		else if(this.image instanceof ImageComponent3D){
			((ImageComponent3D)this.image).set(index, bufImg);
		}
	}

	/**
	 * Returns <code>ImageComponent</code> from
	 * this <code>J3DeImageComponent</code>.
	 * 
	 * @return <code>ImageComponent</code> from
	 * 	this <code>J3DeImageComponent</code>
	 */
	public ImageComponent getComponent(){
		return this.image;
	}

	/**
	 * Sets the specified <code>ImageComponent</code>
	 * to this <code>J3DeImageComponent</code>.
	 * 
	 * @param component
	 * 	ImageComponent
	 */
	public void setComponent(ImageComponent component){
		this.image = component;
		this.setCapabilities(this.image);
	}


	/**
	 * Resizes image according to the specified
	 * mipmap level.
	 * 
	 * @param image
	 * 	image that will be resized
	 * @param level
	 * 	mipmap level
	 * @return resized image
	 */
	private static Image getMipMapLevel(Image image, int level){
		if(level == 0){
			return image;
		}
		else {
			ImageIcon ii = new ImageIcon(image);
			int width = (int)(ii.getIconWidth() / Math.pow(2, level));
			int height = (int)(ii.getIconHeight() / Math.pow(2, level));
			if(width < 1){
				width = 1;
			}
		          if(height < 1){
		                    height = 1;
		          }
			return image.getScaledInstance(width, height, Image.SCALE_SMOOTH);
		}
	}

	/**
	 * Creates an array of <code>ImageComponent2D</code> objects
	 * with the size of "levels" using the specified image.
	 * 
	 * @param image
	 * 	image from which this ImageComponent will be created
	 * @param levels
	 * 	number of mipmap levels
	 * @return an array of <code>ImageComponent2D</code> objects
	 */
	public static ImageComponent2D[] createImageComponents(Image image, int levels){
		ImageComponent2D[] imgs = new ImageComponent2D[levels];
		int format = ImageComponent.FORMAT_RGB;

		for(int i = 0; i < levels; i++){
			BufferedImage img = toBufferedImage(getMipMapLevel(image, i));
			if(hasAlpha(img)){
				format = ImageComponent.FORMAT_RGBA;
			}
			imgs[i] = new ImageComponent2D(format, img);
			setCapabilities(imgs[i]);
		}

		return imgs;
	}

	/**
	 * Creates an array of <code>ImageComponent3D</code> objects
	 * with the size of "levels" using the specified images.
	 * 
	 * @param images
	 * 	images from which this ImageComponent will be created
	 * @param levels
	 * 	number of mipmap levels
	 * @return an array of <code>ImageComponent3D</code> objects
	 */
	public static ImageComponent3D[] createImageComponents(Image[] images, int levels){
		ImageComponent3D[] imgs = new ImageComponent3D[levels];
		
		for(int i = 0; i < levels; i++){
		          int format = ImageComponent.FORMAT_RGB;
		          BufferedImage[] img = new BufferedImage[images.length];
			for(int j=0; j<images.length; j++){
				img[j] = toBufferedImage(getMipMapLevel(images[j], i));
				if(hasAlpha(img[j])){
					format = ImageComponent.FORMAT_RGBA;
				}
				
			}
		          imgs[i] = new ImageComponent3D(format, img);
		          setCapabilities(imgs[i]);
		}

		return imgs;
	}

}
