/*
 * Open Source Physics software is free software as described near the bottom of this code file.
 *
 * For additional information and documentation on Open Source Physics please see:
 * <https://www.compadre.org/osp/>
 */

package org.opensourcephysics.display;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.geom.AffineTransform;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.MemoryImageSource;

import org.opensourcephysics.display.DisplayRes;
import org.opensourcephysics.display.DrawingPanel;
import org.opensourcephysics.display.OSPRuntime;

/**
 * A ByteImage contains an array of bytes int[row][col] 
 * where each integer represents an image pixel.  The row index determines the y-location
 * of the pixel and the col index determines the x-location in the drawing panel.
 * 
 * @author Wolfgang Christian
 * @created March 3, 2012
 * @version 1.0
 */
public class ByteImage implements Measurable {
	byte[] imagePixels; // array that gets mapped onto the image
	MemoryImageSource imageSource;  // object that converts the array to an image
	Image image;       // image to be rendered in drawing panel
	int nrow, ncol;    // number of rows and column in array
	double xmin, xmax, ymin, ymax; // drawing scale
	boolean visible = true;
	boolean dirtyImage=true;  // true if array elements have changed
	
	/**
	 * Gets a two-color ByteImage with 0 -> red and 1 -> blue.
	 */
	static public ByteImage getBinaryImage(byte[][] data){
	  ColorModel colorModel = new IndexColorModel(1, 2, 
			  new byte[] {(byte) 255, (byte) 0}, 
			  new byte[] {(byte) 0, (byte) 0}, 
			  new byte[] {(byte) 0, (byte) 255});
	  return  new ByteImage(colorModel, data);
	}
	
	
	// Gets an ByteImage with the given color palette and data.
	static public ByteImage getColorImage(Color[] colors, byte[][] data){
		int n=colors.length;
	    byte [] reds = new byte[n];
	    byte [] greens = new byte[n];
	    byte [] blues = new byte[n];
	    for(int i = 0; i<n; i++) {
	      reds[i] = (byte) (colors[i].getRed());
	      greens[i] = (byte) (colors[i].getGreen());
	      blues[i] = (byte) (colors[i].getBlue());
	    }
	    ColorModel colorModel = new IndexColorModel(8, n, reds, greens, blues);
		  return  new ByteImage(colorModel, data);
	}

	/**
	 * Constructs ByteImage with the given data.
	 * 
	 * @param data 
	 */
	public ByteImage(byte[][] data) {
	    byte [] reds = new byte[256];
	    byte [] greens = new byte[256];
	    byte [] blues = new byte[256];
	    for(int i = 0; i<256; i++) {
	      double x = (i<128) ? (i-100)/255.0 : -1;
	      double val = Math.exp(-x*x*8);
	      reds[i] = (byte) (255*val);
	      x = (i<128) ? i/255.0 : (255-i)/255.0;
	      val = Math.exp(-x*x*8);
	      greens[i] = (byte) (255*val);
	      x = (i<128) ? -1 : (i-156)/255.0;
	      val = Math.exp(-x*x*8);
	      blues[i] = (byte) (255*val);
	    }
	    ColorModel colorModel = new IndexColorModel(8, 256, reds, greens, blues);
		nrow = data.length;
		ncol = data[0].length;
		imagePixels = new byte[nrow * ncol];
		for (int i = 0; i < nrow; i++) {
			byte[] row = data[i];
			System.arraycopy(row, 0, imagePixels, i * ncol, ncol);
		}
		imageSource = new MemoryImageSource(ncol, nrow,colorModel, imagePixels, 0, ncol);
		imageSource.setAnimated(true);
		image = Toolkit.getDefaultToolkit().createImage(imageSource);
		dirtyImage=false;
		xmin = 0;
		xmax = ncol;
		ymin = nrow;
		ymax = 0; // zero is on top
	}
	
	/**
	 * Constructs IntegerImage with the given ColorModel and data.
	 * 
	 * @param colorModel
	 * @param data 
	 */
	public ByteImage(ColorModel colorModel, byte[][] data) {
		nrow = data.length;
		ncol = data[0].length;
		imagePixels = new byte[nrow * ncol];
		for (int i = 0; i < nrow; i++) {
			byte[] row = data[i];
			System.arraycopy(row, 0, imagePixels, i * ncol, ncol);
		}
		imageSource = new MemoryImageSource(ncol, nrow,colorModel, imagePixels, 0, ncol);
		imageSource.setAnimated(true);
		image = Toolkit.getDefaultToolkit().createImage(imageSource);
		dirtyImage=false;
		xmin = 0;
		xmax = ncol;
		ymin = nrow;
		ymax = 0; // zero is on top
	}
	
	/**
	 * Sets new values assuming that the integer array has not changed.
	 * 
	 * @param val
	 */
	public void updateImage(byte[][] val) {
	  for (int i = 0; i < nrow; i++) {
		  byte[] row = val[i];
			System.arraycopy(row, 0, imagePixels, i * ncol, ncol);
	  }
      // image width is ncol and image height is nrow 
	  imageSource.newPixels(0, 0,ncol,nrow);  // not needed?
	  dirtyImage=true;
	}

	/**
	 * Sets an offset block to new values.
	 * 
	 * @param row_offset
	 * @param col_offset
	 * @param val
	 */
	public void setBlock(int row_offset, int col_offset, byte[][] val) {
		if(val==null) return;
		int block_nrow = val.length;
		int block_ncol = val[0].length;
		if ((row_offset < 0) || (row_offset + block_nrow > nrow)) {
			throw new IllegalArgumentException(
					"Row index out of range in IntegerImage setBlock."); //$NON-NLS-1$
		}
		if ((col_offset < 0) || (col_offset + block_ncol > ncol)) {
			throw new IllegalArgumentException(
					"Column index out of range in IntegerImage setBlock."); //$NON-NLS-1$
		}
		for (int ir =0; ir < block_nrow; ir++) {
			byte[] row=val[ir];
			int index = (ir+row_offset) * ncol +col_offset;
			System.arraycopy(row, 0, imagePixels, index, block_ncol);
		}
		// image width is ncol and image height is nrow 
		// imageSource.newPixels(col_offset, row_offset,block_ncol,block_nrow);  // not needed?
		dirtyImage=true;
	}

	/**
	 * Sets array elements in a row to new values.
	 * 
	 * @param row
	 * @param val
	 */
	public void setRow(int row, byte[] val) {
		if(val==null) return;
		if ((row < 0) || (row >= nrow)) {
			throw new IllegalArgumentException("Row index out of range in IntegerImage setRow."); //$NON-NLS-1$
		}
		if (val.length > ncol) {
			throw new IllegalArgumentException(
					"Column index out of range in IntegerImage setRow."); //$NON-NLS-1$
		}
		System.arraycopy(val, 0, imagePixels, row * ncol, val.length);
		// image width is ncol and image height is nrow 
		// imageSource.newPixels(0,row,val.length,1);  // not needed?
		dirtyImage=true;
	}

	/**
	 * Sets a column to new values.
	 * 
	 * @param col
	 * @param val
	 */
	public void setCol(int col, byte[] val) {
		if(val==null) return;
		if (val.length > nrow) {
			throw new IllegalArgumentException(
					"Row index out of range in IntegerImage setCol."); //$NON-NLS-1$
		}
		if ((col < 0) || (col >= ncol)) {
			throw new IllegalArgumentException(
					"Column index out of range in IntegerImage setCol."); //$NON-NLS-1$
		}
		for (int rindex = 0, nr = val.length; rindex < nr; rindex++) {
			imagePixels[rindex * ncol + col] = val[rindex];
		}
		// image width is ncol and image height is nrow 
		// imageSource.newPixels(col,0,1,val.length);  // not needed?
		dirtyImage=true;
	}

	/**
	 * Sets a cell to a new value.
	 */
	public void setCell(int row, int col, byte val) {
		imagePixels[row * ncol + col]=val;
		imageSource.newPixels(row, col,1,1);
		dirtyImage=true;
	}

	/**
	 * Draws the image.
	 * 
	 * @param panel
	 * @param g
	 */

	public void draw(DrawingPanel panel, Graphics g) {
		if (!visible) {
			return;
		}
		if(dirtyImage){
		  image = Toolkit.getDefaultToolkit().createImage(imageSource);
		}
		if (image == null) {
			panel.setMessage(DisplayRes.getString("Null Image")); //$NON-NLS-1$
			return;
		}
		Graphics2D g2 = (Graphics2D) g;
		AffineTransform gat = g2.getTransform(); // save graphics transform
		RenderingHints hints = g2.getRenderingHints();
		if (!OSPRuntime.isMac()) { // Rendering hint bug in Mac Snow Leopard
			g2.setRenderingHint(RenderingHints.KEY_DITHERING,
					RenderingHints.VALUE_DITHER_DISABLE);
			g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_OFF);
		}
		double sx = (xmax - xmin) * panel.xPixPerUnit / ncol;
		double sy = (ymax - ymin) * panel.yPixPerUnit / nrow;
		// translate origin to pixel location of (xmin,ymax)
		g2.transform(AffineTransform.getTranslateInstance(panel.leftGutter
				+ panel.xPixPerUnit * (xmin - panel.xmin), panel.topGutter
				+ panel.yPixPerUnit * (panel.ymax - ymax)));
		g2.transform(AffineTransform.getScaleInstance(sx, sy));  // scales image to world units
		g2.drawImage(image, 0, 0, panel);
		g2.setTransform(gat); // restore graphics conext
		g2.setRenderingHints(hints); // restore the hints
	}

	public boolean isMeasured() {
		if (image == null) {
			return false;
		}
		return true;
	}

	public double getXMin() {
		return xmin;
	}

	public double getXMax() {
		return xmax;
	}

	public double getYMin() {
		return ymin;
	}

	public double getYMax() {
		return ymax;
	}

	public void setXMin(double _xmin) {
		xmin = _xmin;
	}

	public void setXMax(double _xmax) {
		xmax = _xmax;
	}

	public void setYMin(double _ymin) {
		ymin = _ymin;
	}

	public void setYMax(double _ymax) {
		ymax = _ymax;
	}

	public void setMinMax(double _xmin, double _xmax, double _ymin, double _ymax) {
		xmin = _xmin;
		xmax = _xmax;
		ymin = _ymin;
		ymax = _ymax;
	}
}

/*
 * Open Source Physics software is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License (GPL) as
 * published by the Free Software Foundation; either version 2 of the License,
 * or(at your option) any later version.
 * 
 * Code that uses any portion of the code in the org.opensourcephysics package
 * or any subpackage (subdirectory) of this package must must also be be
 * released under the GNU GPL license.
 * 
 * This software is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
 * Suite 330, Boston MA 02111-1307 USA or view the license online at
 * http://www.gnu.org/copyleft/gpl.html
 * 
 * Copyright (c) 2019 The Open Source Physics project
 * https://www.compadre.org/osp
 */