package vgp.tutor.fractal;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.image.MemoryImageSource;

import jv.number.PuComplex;
import jv.number.PuInteger;
import jv.project.PjProject;
import jv.project.PvDisplayIf;
import jv.project.PvPickEvent;
import jv.vecmath.PdVector;
import jv.vecmath.PiVector;

/**
 * Demo project for working with pixel images. Project displays a Mandelbrot set.
 * Zoom into Mandelbrot set by marking a rectangle in display using mark-mode.
 * 
 * @author		Konrad Polthier
 * @version		26.09.99, 1.00 revised (kp) <br>
 *					26.09.99, 1.00 created (kp)
 */
public class PjFractalImage extends PjProject {
	/** Image to be used as background in display. */
	protected	Image					m_image;
	/** Producer of image m_image from pixel array. */
	private		MemoryImageSource mis;
	/** Pixel array which stores the image of a textured element. Only used when texture enabled. */
	private		PiVector				m_pix;
	/** Height of display canvas. */
	protected	int					m_imageHeight;
	/** Width of display canvas. */
	protected	int					m_imageWidth;
	/** Maximal number of iterations until sequence is decided to diverge. */
	protected	PuInteger			m_maxNumIter;
	/** Bounds of display in world coordinates. */
	protected	PdVector				m_bounds;
	/** Coarseness, i.e. size of pixel square which is colored uniformly. */
	protected	PuInteger			m_coarseness;

	public PjFractalImage() {
		super("Mandelbrot Set");
		m_pix				= new PiVector();
		m_bounds			= new PdVector(4);
		m_maxNumIter	= new PuInteger("Number of Iterations", this);
		m_coarseness	= new PuInteger("Coarseness", this);
		if (getClass() == PjFractalImage.class)
			init();
	}
	public void init() {
		super.init();
		m_image = null;
		m_pix.setSize(0);
		m_imageHeight	= 0;
		m_imageWidth	= 0;
		
		m_maxNumIter.setDefBounds(2, 200, 1, 5);
		m_maxNumIter.setDefValue(25);
		m_maxNumIter.init();
		
		m_coarseness.setDefBounds(1, 10, 1, 2);
		m_coarseness.setDefValue(2);
		m_coarseness.init();
	}
	public void start() {
		// getDisplay().addPickListener(this);
		computeImage(-2.5, -2., 1.5, 2.);
		update(this);
		PvDisplayIf disp = getDisplay();
		if (disp != null) {
			disp.setBackgroundImageFit(PvDisplayIf.IMAGE_RESIZE);
			disp.showBackgroundImage(true);
		}
		super.start();
	}
	/**
	 * Update the class whenever a child has changed.
	 * Method is usually invoked from the children.
	 */
	public boolean update(Object event) {
		PvDisplayIf disp = getDisplay();
		if (event == this) {
			if (m_image != null)
				disp.setBackgroundImage(m_image);
			else
				disp.setBackgroundImage((Image)null);
			disp.update(null);
			return true;
		} else if (event == m_maxNumIter) {
			computeImage(m_bounds.m_data[0], m_bounds.m_data[1], m_bounds.m_data[2], m_bounds.m_data[3]);
			disp.update(null);
			return true;
		} else if (event == m_coarseness) {
			computeImage(m_bounds.m_data[0], m_bounds.m_data[1], m_bounds.m_data[2], m_bounds.m_data[3]);
			disp.update(null);
			return true;
		}
		return super.update(event);
	}
	/**
	 * Mark a set of vertices of a geometry within a given bounding box.
	 * Method is called when user marks a rectangular array in display.
	 * @param		pickEvent		Everything in a 
	 * 									{@link jv.project.PvPickEvent#getMarkBox() PvPickEvent MarkBox}
	 * 									gets marked. 
	 */
	public void markVertices(PvPickEvent pickEvent) {
		PiVector markBox = pickEvent.getMarkBox();
		int [] box = markBox.m_data;
		double width	= m_bounds.m_data[2]-m_bounds.m_data[0];
		double height	= m_bounds.m_data[3]-m_bounds.m_data[1];
		
		double xMinNew = m_bounds.m_data[0] + width*box[0]/(m_imageWidth-1.);
		double xMaxNew = m_bounds.m_data[0] + width*box[2]/(m_imageWidth-1.);
		double yMinNew = m_bounds.m_data[1] + height*box[1]/(m_imageHeight-1.);
		double yMaxNew = m_bounds.m_data[1] + height*box[3]/(m_imageHeight-1.);
		computeImage(xMinNew, yMinNew, xMaxNew, yMaxNew);
		PvDisplayIf disp = getDisplay();
		disp.update(null);
	}
	public void computeImage(double xMin, double yMin, double xMax, double yMax) {
		m_bounds.set(xMin, yMin, xMax, yMax);
		Dimension dim = getDisplay().getSize();
		if (dim.height==-1 || dim.width==-1) {
			dim.height = 200;
			dim.width = 200;
		}
		if (mis==null || m_imageHeight!=dim.height || m_imageWidth!=dim.width) {
			m_imageHeight	= dim.height;
			m_imageWidth	= dim.width;
			m_pix.setSize(m_imageWidth*m_imageHeight);
			// Paint in pixel array m_pix.
			drawImage(m_pix.m_data, m_imageWidth, m_imageHeight, xMin, yMin, xMax, yMax);
			mis = new MemoryImageSource(m_imageWidth, m_imageHeight, m_pix.m_data, 0, m_imageWidth);
			// Enables the possibility to update the pixels in m_pix
			mis.setAnimated(true);
			// Create image which will be painted as background in PvDisplay.
			m_image = ((Component)m_display).createImage(mis);
		} else {
			// Paint in pixel array m_pix.
			drawImage(m_pix.m_data, m_imageWidth, m_imageHeight, xMin, yMin, xMax, yMax);
			// Update the image with the newly computed pixels
			mis.newPixels(0, 0, m_imageWidth, m_imageHeight);
		}
	}	
	/**
	 * This is Mandelbrot iteration f(z)=z*z+c.
	 * For each c check whether iteration diverges to infinity.
	 */
	public void drawImage(int [] pix, int width, int height,
								 double xMin, double yMin, double xMax, double yMax) {
		int i, j, k, col;
		double u, v, du, dv;
		boolean bDivergent = false;
		PuComplex z = new PuComplex();
		PuComplex c = new PuComplex();
		int maxIter = m_maxNumIter.getValue();
		int discr = m_coarseness.getValue();
		du = discr*(xMax-xMin)/(width-1.);
		dv = discr*(yMax-yMin)/(height-1.);
		int ind;
		v = yMin;
		for (i=0; i<height; i+=discr) {
			u = xMin;
			ind = i*width;
			for (j=0; j<width; j+=discr) {
				c.set(u, v);
				z.set(0., 0.);
				bDivergent = false;
				for (k=0; k<maxIter; k++) {
					// This is Mandelbrot iteration f(z)=z*z+c
					// For each c check whether iteration diverges to infinity
					z.sqr().add(c);
					if (z.sqrAbs() > 5.) {
						bDivergent = true;
						break;
					}
				}
				if (!bDivergent) {
					col = (255 << 24);
				} else {
					col = (255 << 24) |
							((50+205*k/maxIter)%255 << 16) |
							(0 << 8) |
							0;
				}
				for (k=Math.min(discr, width-j)-1; k>=0; k--)
					pix[ind++] = col;
				u += du;
			}
			int indPrev = ind-width;
			System.arraycopy(pix, indPrev, pix, ind, width*Math.min(discr, height-i-1));
			v += dv;
		}
	}
}
