/*
 * 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:
 * <http://www.opensourcephysics.org/>
 */

package org.opensourcephysics.display2d;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.util.Random;
import javax.swing.JFrame;
import org.opensourcephysics.display.DrawingPanel;
import org.opensourcephysics.display.Grid;
import org.opensourcephysics.display.MeasuredImage;
import org.opensourcephysics.display.OSPRuntime;

/**
 * A BinaryLattice is an array where each array element has a value of 0 or 1.
 *
 * The lattice is drawn as an array of rectangles to distinguish between the two possible values.
 *
 * @author     Wolfgang Christian
 * @created    February 11, 2003
 * @version    1.0
 */
public class BinaryLattice extends MeasuredImage implements ByteLattice {
  WritableRaster raster;
  Grid grid;
  byte[] packedData;
  int ny, nx;
  boolean visible = true;
  Color zeroColor = Color.red, oneColor = Color.blue;

  /**
   * Constructs a binary lattice with the given size.
   * @param _nx the number of values in x direction
   * @param _ny the number of values in y direction
   */
  public BinaryLattice(int _nx, int _ny) {
    ny = _ny;
    nx = _nx;
    int len = ((nx+7)/8)*ny; // each row starts on a byte boundary
    packedData = new byte[len];
    DataBuffer databuffer = new DataBufferByte(packedData, len);
    raster = Raster.createPackedRaster(databuffer, nx, ny, 1, null);
    // default colors are red and blue
    ColorModel colorModel = new IndexColorModel(1, 2, new byte[] {(byte) 255, (byte) 0}, new byte[] {(byte) 0, (byte) 0}, new byte[] {(byte) 0, (byte) 255});
    image = new BufferedImage(colorModel, raster, false, null);
    xmin = 0;
    xmax = nx;
    ymin = 0;
    ymax = ny;
    grid = new Grid(nx, ny, xmin, xmax, ymin, ymax);
    grid.setColor(Color.lightGray);
  }

  /**
   * Creates the default palette.
   */
  public void createDefaultColors() {
    zeroColor = Color.red;
    oneColor = Color.blue;
  }

  /**
   * Resize the lattice.
   * @param _nx number of x sites
   * @param _ny number of y sites
   */
  public void resizeLattice(int _nx, int _ny) {
    ny = _ny;
    nx = _nx;
    int len = ((nx+7)/8)*ny; // each row starts on a byte boundary
    packedData = new byte[len];
    DataBuffer databuffer = new DataBufferByte(packedData, len);
    raster = Raster.createPackedRaster(databuffer, nx, ny, 1, null);
    ColorModel colorModel = image.getColorModel();
    image = new BufferedImage(colorModel, raster, false, null);
    Color color = grid.getColor();
    grid = new Grid(nx, ny, xmin, xmax, ymin, ymax);
    setMinMax(xmin, xmax, ymin, ymax);
    grid.setColor(color);
  }

  /**
   * Randomizes the lattice values.
   */
  public void randomize() {
    Random random = new Random();
    random.nextBytes(packedData);
  }

  /**
   * Draws the lattice and the grid.
   * @param panel
   * @param g
   */
  public void draw(DrawingPanel panel, Graphics g) {
    if(!visible) {
      return;
    }
    if (!OSPRuntime.isMac()) {  //Rendering hint bug in Mac Snow Leopard 
      Graphics2D g2 = ((Graphics2D) g);
      g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
    }
    super.draw(panel, g);
    grid.draw(panel, g);
  }

  /**
   * Sets a block of data to new values.
   *
   * The lattice is resized to fit the new data if needed.
   *
   * @param val
   */
  public void setAll(byte val[][]) {
    if((getNx()!=val.length)||(getNy()!=val[0].length)) {
      resizeLattice(val.length, val[0].length);
    }
    setBlock(0, 0, val);
  }

  /**
   * Sets the lattice values and scale.
   *
   * The lattice is resized to fit the new data if needed.
   *
   * @param val int[][] the new values
   * @param xmin double
   * @param xmax double
   * @param ymin double
   * @param ymax double
   */
  public void setAll(byte val[][], double xmin, double xmax, double ymin, double ymax) {
    setAll(val);
    setMinMax(xmin, xmax, ymin, ymax);
  }

  /**
   * Scales the grid to the given values in world units.
   * @param xmin
   * @param xmax
   * @param ymin
   * @param ymax
   */
  public void setMinMax(double xmin, double xmax, double ymin, double ymax) {
    super.setMinMax(xmin, xmax, ymin, ymax);
    grid.setMinMax(xmin, xmax, ymin, ymax);
  }

  /**
   * Sets a block of cells to new values.
   *
   * A cell is set to 1 if the value is >0; the cell is set to zero otherwise
   *
   * @param row_offset
   * @param col_offset
   * @param val  the array of values
   */

  /**
   * Sets a block of cells to new values.
   *
   * A cell is set to 1 if the value is >0; the cell is set to zero otherwise
   *
   * @param x_offset int
   * @param y_offset int
   * @param val int[][]
   */
  public void setBlock(int x_offset, int y_offset, int val[][]) {
    if((y_offset<0)||(y_offset+val[0].length>ny)) {
      throw new IllegalArgumentException("Row index out of range in binary lattice setBlock."); //$NON-NLS-1$
    }
    if((x_offset<0)||(x_offset+val.length>nx)) {
      throw new IllegalArgumentException("Column index out of range in binary lattice setBlock."); //$NON-NLS-1$
    }
    for(int iy = y_offset, my = val[0].length+y_offset; iy<my; iy++) {
      for(int ix = x_offset, mx = val.length+x_offset; ix<mx; ix++) {
        int arrayIndex = (ny-iy-1)*((nx+7)/8)+ix/8; // each row starts on a byte boundary
        byte packedcell = packedData[arrayIndex];
        int mask = 0x80>>>(ix%8);                   // start with 0x10000000 and shift right
        if(val[ix-x_offset][iy-y_offset]<=0) {
          packedcell = (byte) (packedcell&~mask);   // AND bitwise with mask complement will clear index bit
        } else {
          packedcell = (byte) (packedcell|mask);    // OR bitwise with mask operation will set index bit
        }
        packedData[arrayIndex] = packedcell;
      }
    }
  }

  /**
   * Sets a block of cells to new values.
   *
   * A cell is set to 1 if the value is >0; the cell is set to zero otherwise
   *
   * @param ix_offset
   * @param iy_offset
   * @param val  the value array
   */
  public void setBlock(int ix_offset, int iy_offset, byte val[][]) {
    if((iy_offset<0)||(iy_offset+val[0].length>ny)) {
      throw new IllegalArgumentException("Row index out of range in binary lattice setBlock."); //$NON-NLS-1$
    }
    if((ix_offset<0)||(ix_offset+val.length>nx)) {
      throw new IllegalArgumentException("Column index out of range in binary lattice setBlock."); //$NON-NLS-1$
    }
    for(int iy = iy_offset, my = val[0].length+iy_offset; iy<my; iy++) {
      for(int ix = ix_offset, mx = val.length+ix_offset; ix<mx; ix++) {
        int arrayIndex = (ny-iy-1)*((nx+7)/8)+ix/8; // each row starts on a byte boundary
        byte packedcell = packedData[arrayIndex];
        int mask = 0x80>>>(ix%8);                   // start with 0x10000000 and shift right
        if(val[ix-ix_offset][iy-iy_offset]<=0) {
          packedcell = (byte) (packedcell&~mask);   // AND bitwise with mask complement will clear index bit
        } else {
          packedcell = (byte) (packedcell|mask);    // OR bitwise with mask operation will set index bit
        }
        packedData[arrayIndex] = packedcell;
      }
    }
  }

  /**
   * Sets a block of data starting at (0,0) to new values.
   *
   * @param val
  */
  public void setBlock(byte[][] val) {
    setBlock(0, 0, val);
  }

  /**
   * Sets a column of cells to new values.
   *
   * A cell is set to 1 if the value is >0; the cell is set to zero otherwise
   *
   * @param ix
   * @param iy_offset
   * @param val  the array of values
   */
  public void setCol(int ix, int iy_offset, int val[]) {
    if((iy_offset<0)||(iy_offset+val.length>ny)) {
      throw new IllegalArgumentException("Row index out of range in binary lattice setCol."); //$NON-NLS-1$
    }
    if((ix<0)||(ix>=nx)) {
      throw new IllegalArgumentException("Column index out of range in binary lattice setCol."); //$NON-NLS-1$
    }
    for(int iy = iy_offset, nr = val.length+iy_offset; iy<nr; iy++) {
      int arrayIndex = (ny-iy-1)*((nx+7)/8)+ix/8; // each row starts on a byte boundary
      byte packedcell = packedData[arrayIndex];
      int mask = 0x80>>>(ix%8);                   // start with 0x10000000 and shift right
      if(val[iy-iy_offset]<=0) {
        packedcell = (byte) (packedcell&~mask);   // AND bitwise with mask complement will clear index bit
      } else {
        packedcell = (byte) (packedcell|mask);    // OR bitwise with mask operation will set index bit
      }
      packedData[arrayIndex] = packedcell;
    }
  }

  /**
   * Sets a column of cells to new values.
   *
   * A cell is set to 1 if the value is >0; the cell is set to zero otherwise
   *
   * @param ix
   * @param iy_offset
   * @param val  the array of values
   */
  public void setCol(int ix, int iy_offset, byte val[]) {
    if((iy_offset<0)||(iy_offset+val.length>ny)) {
      throw new IllegalArgumentException("Row index out of range in binary lattice setCol."); //$NON-NLS-1$
    }
    if((ix<0)||(ix>=nx)) {
      throw new IllegalArgumentException("Column index out of range in binary lattice setCol."); //$NON-NLS-1$
    }
    for(int iy = iy_offset, nr = val.length+iy_offset; iy<nr; iy++) {
      int arrayIndex = (ny-iy-1)*((nx+7)/8)+ix/8; // each row starts on a byte boundary
      byte packedcell = packedData[arrayIndex];
      int mask = 0x80>>>(ix%8);                   // start with 0x10000000 and shift right
      if(val[iy-iy_offset]<=0) {
        packedcell = (byte) (packedcell&~mask);   // AND bitwise with mask complement will clear index bit
      } else {
        packedcell = (byte) (packedcell|mask);    // OR bitwise with mask operation will set index bit
      }
      packedData[arrayIndex] = packedcell;
    }
  }

  /**
   * Sets a row of cells to new values starting at the given column.
   *
   * A cell is set to 1 if the value is >0; the cell is set to zero otherwise
   *
   * @param iy
   * @param ix_offset  the x offset
   * @param val the value array
   */
  public void setRow(int iy, int ix_offset, int val[]) {
    if((iy<0)||(iy>=ny)) {
      throw new IllegalArgumentException("Row index out of range in binary lattice setRow."); //$NON-NLS-1$
    }
    if((ix_offset<0)||(ix_offset+val.length>nx)) {
      throw new IllegalArgumentException("Column index out of range in binary lattice setRow."); //$NON-NLS-1$
    }
    for(int ix = ix_offset, nc = val.length+ix_offset; ix<nc; ix++) {
      int arrayIndex = (ny-iy-1)*((nx+7)/8)+ix/8; // each row starts on a byte boundary
      byte packedcell = packedData[arrayIndex];
      int mask = 0x80>>>(ix%8);                   // start with 0x10000000 and shift right
      if(val[ix-ix_offset]<=0) {
        packedcell = (byte) (packedcell&~mask);   // AND bitwise with mask complement will clear index bit
      } else {
        packedcell = (byte) (packedcell|mask);    // OR bitwise with mask operation will set index bit
      }
      packedData[arrayIndex] = packedcell;
    }
  }

  /**
   * Sets a row of cells to new values starting at the given column.
   *
   * A cell is set to 1 if the value is >0; the cell is set to zero otherwise
   *
   * @param iy
   * @param ix_offset  the x offset
   * @param val the value array
   */
  public void setRow(int iy, int ix_offset, byte val[]) {
    if((iy<0)||(iy>=ny)) {
      throw new IllegalArgumentException("Row index out of range in binary lattice setRow."); //$NON-NLS-1$
    }
    if((ix_offset<0)||(ix_offset+val.length>nx)) {
      throw new IllegalArgumentException("Column index out of range in binary lattice setRow."); //$NON-NLS-1$
    }
    for(int ix = ix_offset, nc = val.length+ix_offset; ix<nc; ix++) {
      int arrayIndex = (ny-iy-1)*((nx+7)/8)+ix/8; // each row starts on a byte boundary
      byte packedcell = packedData[arrayIndex];
      int mask = 0x80>>>(ix%8);                   // start with 0x10000000 and shift right
      if(val[ix-ix_offset]<=0) {
        packedcell = (byte) (packedcell&~mask);   // AND bitwise with mask complement will clear index bit
      } else {
        packedcell = (byte) (packedcell|mask);    // OR bitwise with mask operation will set index bit
      }
      packedData[arrayIndex] = packedcell;
    }
  }

  /**
   * Sets a cell at the given location to a new value.
   *
   * A cell should take on a value of 0 or 1.
   * @param ix
   * @param iy
   * @param val
   */
  public void setValue(int ix, int iy, int val) {
    if((iy<0)||(iy>=ny)||(ix<0)||(ix>=nx)) {
      throw new IllegalArgumentException("Cell row or column index out of range.  row="+iy+"  col="+ix); //$NON-NLS-1$ //$NON-NLS-2$
    }
    int arrayIndex = (ny-iy-1)*((nx+7)/8)+ix/8; // each row starts on a byte boundary
    byte packedcell = packedData[arrayIndex];
    int mask = 0x80>>>(ix%8);                   // start with 0x10000000 and shift right
    if(val<=0) {
      packedcell = (byte) (packedcell&~mask); // AND bitwise with mask complement will clear index bit
    } else {
      packedcell = (byte) (packedcell|mask);  // OR bitwise with mask operation will set index bit
    }
    packedData[arrayIndex] = packedcell;
  }

  public void setValue(int ix, int iy, byte val) {
    setValue(ix, iy, (int) val);
  }

  /**
   * Gets a value from the given location.
   *
   * Cell values are zero or one.
   *
   * @param ix
   * @param iy
   * @return the cell value.
   */
  public byte getValue(int ix, int iy) {
    byte packedcell = packedData[(ny-iy-1)*((nx+7)/8)+ix/8]; // each row starts on a byte boundary
    int mask = 0x80>>>(ix%8);                                // start with 0x10000000 and shift right
    if((packedcell&mask)>0) {
      return 1;
    }
    return 0;
  }

  /**
   * Gets the number of x entries.
   * @return nx
   */
  public int getNx() {
    return nx;
  }

  /**
   * Gets the number of y entries.
   * @return ny
   */
  public int getNy() {
    return ny;
  }

  /**
   * Sets the show grid option.
   *
   * @param showGrid
   */
  public void setShowGrid(boolean showGrid) {
    grid.setVisible(showGrid);
  }

  /**
   * Sets color palette.
   *
   * @param colors
   */
  public void setColorPalette(Color[] colors) {
    zeroColor = colors[0];
    oneColor = colors[1];
    ColorModel colorModel = new IndexColorModel(1, 2, new byte[] {(byte) zeroColor.getRed(), (byte) oneColor.getRed()}, // red
      new byte[] {(byte) zeroColor.getGreen(), (byte) oneColor.getGreen()},  // green
        new byte[] {(byte) zeroColor.getBlue(), (byte) oneColor.getBlue()}); // blue
    image = new BufferedImage(colorModel, raster, false, null);
  }

  /**
   * Sets the color for a single index.
   * @param i
   * @param color
   */
  public void setIndexedColor(int i, Color color) {
    if(i==0) {
      zeroColor = color;
    } else {
      oneColor = color;
    }
    ColorModel colorModel = new IndexColorModel(1, 2, new byte[] {(byte) zeroColor.getRed(), (byte) oneColor.getRed()}, // red
      new byte[] {(byte) zeroColor.getGreen(), (byte) oneColor.getGreen()},  // green
        new byte[] {(byte) zeroColor.getBlue(), (byte) oneColor.getBlue()}); // blue
    image = new BufferedImage(colorModel, raster, false, null);
  }

  /**
   * Sets the grid color.
   * @param color
   */
  public void setGridLineColor(Color color) {
    grid.setColor(color);
  }

  public void setShowGridLines(boolean showGridLines) {
    grid.setVisible(showGridLines);
  }

  /**
   * Gets closest index from the given x world coordinate.
   *
   * @param x double the coordinate
   * @return int the index
   */
  public int xToIndex(double x) {
    int nx = getNx();
    double xMin = getXMin();
    double xMax = getXMax();
    double deltaX = (x-xMin)/(xMax-xMin);
    int ix = (int) (deltaX*nx);
    if(ix<0) {
      return 0;
    }
    if(ix>=nx) {
      return nx-1;
    }
    return ix;
  }

  /**
   * Gets closest index from the given y world coordinate.
   *
   * @param y double the coordinate
   * @return int the index
   */
  public int yToIndex(double y) {
    int ny = getNy();
    double yMin = getYMin();
    double yMax = getYMax();
    double deltaY = (y-yMin)/(yMax-yMin);
    int iy = (int) (deltaY*ny);
    if(iy<0) {
      return 0;
    }
    if(iy>=ny) {
      return ny-1;
    }
    return iy;
  }

  /**
   * Determines the lattice index (row-major order) from given x and y world coordinates
   * Returns -1 if the world coordinates are outside the lattice.
   *
   * @param x
   * @param y
   * @return index
   */
  public int indexFromPoint(double x, double y) {
    int nx = getNx();
    int ny = getNy();
    double xMin = getXMin();
    double xMax = getXMax();
    double yMin = getYMin();
    double yMax = getYMax();
    double deltaX = (x-xMin)/(xMax-xMin);
    double deltaY = (y-yMin)/(yMax-yMin);
    int ix = (int) (deltaX*nx);
    int iy = (int) (deltaY*ny);
    if((ix<0)||(iy<0)||(ix>=nx)||(iy>=ny)) {
      return -1;
    }
    return iy*nx+ix;
  }

  public JFrame showLegend() {
    return null;
  }

}

/*
 * 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) 2007  The Open Source Physics project
 *                     http://www.opensourcephysics.org
 */
