/*
 * 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.Shape;
import java.util.Random;
import org.opensourcephysics.display.DrawingPanel;
import org.opensourcephysics.display.Measurable;

/**
 *  A TriangularBinaryLattice is an array where each array element has a value
 *  of 0 or 1.
 *
 * @author     Joshua Gould
 * @author     Wolfgang Christian
 * @created    May 13, 2003
 * @version    1.0
 */
public class TriangularBinaryLattice implements Measurable {
  byte[] packedData;
  int nrow, ncol;
  boolean visible = true;
  double xmin, ymin, xmax, ymax;
  double xminLattice, yminLattice, xmaxLattice, ymaxLattice;
  Color zeroColor = Color.red, oneColor = Color.blue;
  final static double SQRT3_OVER2 = Math.sqrt(3)/2.0;
  final static int BITS_PER_BYTE = 8;
  final static int radius = 3;
  final static int diameter = radius*2;

  /**
   *  Constructs a binary lattice with the given size.
   *
   * @param  _nrow  the number of rows
   * @param  _ncol  the number of columns
   */
  public TriangularBinaryLattice(int _nrow, int _ncol) {
    nrow = _nrow;
    ncol = _ncol;
    int len = ((ncol+(BITS_PER_BYTE-1))/BITS_PER_BYTE)*nrow; // each row starts on a byte boundary
    packedData = new byte[len];
    // default colors are red and blue
    // col in x direction, row in y direction
    xminLattice=xmin = 0;
    xmaxLattice=xmax = ncol-0.5;
    ymin = nrow*SQRT3_OVER2-SQRT3_OVER2;
    if(ymin==0) {
      ymin = SQRT3_OVER2; // FIXME
    }
    yminLattice=ymin;
    ymaxLattice=ymax = 0; // zero is on top
  }

  /**
   * Resize the lattice.
   * @param _nx number of x sites
   * @param _ny number of y sites
   */
  public void resizeLattice(int _nrow, int _ncol) {
    nrow = _nrow;
    ncol = _ncol;
    int len = ((ncol+(BITS_PER_BYTE-1))/BITS_PER_BYTE)*nrow; // each row starts on a byte boundary
    packedData = new byte[len];
    // default colors are red and blue
    // col in x direction, row in y direction
    xminLattice=xmin = 0;
    xmaxLattice=xmax = ncol-0.5;
    ymin = nrow*SQRT3_OVER2-SQRT3_OVER2;
    if(ymin==0) {
      ymin = SQRT3_OVER2; // FIXME
    }
    yminLattice=ymin;
    ymaxLattice=ymax = 0; // zero is on top
  }

  public void setVisible (boolean _vis) {
    this.visible = _vis;  
  }
  
  public boolean isMeasured() {
    return true;
  }

  public double getXMin() {
    return xmin;
  }

  public double getYMin() {
    return ymin;
  }

  public double getXMax() {
    return xmax;
  }

  public double getYMax() {
    return ymax;
  }

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

  /**
   *  Randomizes the lattice values with the specified probability. A probability
   *  of 1 indicates that all cells will be occupied.
   *
   * @param  probability  the probability of a site being occupied, between 0.0
   *      and 1.0.
   */
  public void randomize(double probability) {
    if((probability<0)||(probability>1)) {
      throw new IllegalArgumentException("Probability must be between 0 and 1"); //$NON-NLS-1$
    }
    Random random = new Random();
    for(int i = 0, size = packedData.length; i<size; i++) {
      byte packedcell = 0;
      for(int j = BITS_PER_BYTE; j>0; j--) {
        int mask = 0x80>>>(j-1);                  // start with 0x10000000 and shift right, 0x80 = 128
        double d = random.nextDouble();           // generates number between 0 inclusive and 1 exclusive
        if(d>=probability) {                      // set jth bit to 0
          packedcell = (byte) (packedcell&~mask); // AND bitwise with mask complement will clear index bit
        } else {                                  // set jth bit to 1
          packedcell = (byte) (packedcell|mask);  // OR bitwise with mask operation will set index bit
        }
      }
      packedData[i] = packedcell;
    }
  }

  /**
   *  Draws the lattice.
   *
   * @param  panel
   * @param  g
   */
  public void draw(DrawingPanel panel, Graphics g) {
    if(!visible) {
      return;
    }
    // drawableDelegate.draw(panel, g);
    Shape oldClip = g.getClip();
    g.setClip(null);
    double xScale=(xmax-xmin)/(xmaxLattice-xminLattice);
    double yScale=-(ymax-ymin)/(ymaxLattice-yminLattice);
    int row = 0;
    int column = 0;
    for(int i = 0, size = packedData.length; i<size; i++) {
      byte packedCell = packedData[i];
      for(int j = BITS_PER_BYTE; (j>0)&&(column<ncol); j--) {
        byte val = (byte) (packedCell>>>(j-1));
        int one_or_zero = (val&1);
        if(one_or_zero==0) {
          g.setColor(zeroColor);
        } else {
          g.setColor(oneColor);
        }
        // each row is sqrt(3)/2 offset from previous row
        if((row%2)==1) {
          double x=(column+0.5)*xScale+xmin;
          double y=row*SQRT3_OVER2*yScale+ymin;
          g.fillOval(panel.xToPix(x)-radius, panel.yToPix(y)-radius, diameter, diameter); // shift to right by 0.5
        } else {
          double x=column*xScale+xmin;
          double y=row*SQRT3_OVER2*yScale+ymin;
          g.fillOval(panel.xToPix(x)-radius, panel.yToPix(y)-radius, diameter, diameter);
        }
        if(column==(ncol-1)) {
          column = 0;
          row++;
          break;
        }
        column++;
      }
    }
    g.setClip(oldClip);
  }

  /**
   *  Scales this lattice 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) {
    this.xmin = xmin;
    this.xmax = xmax;
    this.ymin = ymin;
    this.ymax = 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
   */
  public void setBlock(int row_offset, int col_offset, int val[][]) {
    if((row_offset<0)||(row_offset+val.length>nrow)) {
      throw new IllegalArgumentException("Row index out of range in binary lattice setBlock."); //$NON-NLS-1$
    }
    if((col_offset<0)||(col_offset+val[0].length>ncol)) {
      throw new IllegalArgumentException("Column index out of range in binary lattice setBlock."); //$NON-NLS-1$
    }
    for(int rindex = row_offset, nr = val.length+row_offset; rindex<nr; rindex++) {
      for(int cindex = col_offset, nc = val[0].length+col_offset; cindex<nc; cindex++) {
        int arrayIndex = rindex*((ncol+7)/8)+cindex/8; // each row starts on a byte boundary
        byte packedcell = packedData[arrayIndex];
        int mask = 0x80>>>(cindex%8);                  // start with 0x10000000 and shift right
        if(val[rindex-row_offset][cindex-col_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  row_offset
   * @param  col_offset
   * @param  val         the array of values
   */
  public void setBlock(int row_offset, int col_offset, byte val[][]) {
    if((row_offset<0)||(row_offset+val.length>nrow)) {
      throw new IllegalArgumentException("Row index out of range in binary lattice setBlock."); //$NON-NLS-1$
    }
    if((col_offset<0)||(col_offset+val[0].length>ncol)) {
      throw new IllegalArgumentException("Column index out of range in binary lattice setBlock."); //$NON-NLS-1$
    }
    for(int rindex = row_offset, nr = val.length+row_offset; rindex<nr; rindex++) {
      for(int cindex = col_offset, nc = val[0].length+col_offset; cindex<nc; cindex++) {
        int arrayIndex = rindex*((ncol+7)/8)+cindex/8; // each row starts on a byte boundary
        byte packedcell = packedData[arrayIndex];
        int mask = 0x80>>>(cindex%8);                  // start with 0x10000000 and shift right
        if(val[rindex-row_offset][cindex-col_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  row_offset
   * @param  col
   * @param  val         the array of values
   */
  public void setCol(int row_offset, int col, int val[]) {
    if((row_offset<0)||(row_offset+val.length>nrow)) {
      throw new IllegalArgumentException("Row index out of range in binary lattice setCol."); //$NON-NLS-1$
    }
    if((col<0)||(col>=ncol)) {
      throw new IllegalArgumentException("Column index out of range in binary lattice setCol."); //$NON-NLS-1$
    }
    for(int rindex = row_offset, nr = val.length+row_offset; rindex<nr; rindex++) {
      int arrayIndex = rindex*((ncol+7)/8)+col/8; // each row starts on a byte boundary
      byte packedcell = packedData[arrayIndex];
      int mask = 0x80>>>(col%8);                  // start with 0x10000000 and shift right
      if(val[rindex-row_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  row_offset
   * @param  col
   * @param  val         the array of values
   */
  public void setCol(int row_offset, int col, byte val[]) {
    if((row_offset<0)||(row_offset+val.length>nrow)) {
      throw new IllegalArgumentException("Row index out of range in binary lattice setCol."); //$NON-NLS-1$
    }
    if((col<0)||(col>=ncol)) {
      throw new IllegalArgumentException("Column index out of range in binary lattice setCol."); //$NON-NLS-1$
    }
    for(int rindex = row_offset, nr = val.length+row_offset; rindex<nr; rindex++) {
      int arrayIndex = rindex*((ncol+7)/8)+col/8; // each row starts on a byte boundary
      byte packedcell = packedData[arrayIndex];
      int mask = 0x80>>>(col%8);                  // start with 0x10000000 and shift right
      if(val[rindex-row_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  row
   * @param  col_offset  the colum offset
   * @param  val         the value
   */
  public void setRow(int row, int col_offset, int val[]) {
    if((row<0)||(row>=nrow)) {
      throw new IllegalArgumentException("Row index out of range in binary lattice setRow."); //$NON-NLS-1$
    }
    if((col_offset<0)||(col_offset+val.length>ncol)) {
      throw new IllegalArgumentException("Column index out of range in binary lattice setRow."); //$NON-NLS-1$
    }
    for(int cindex = col_offset, nc = val.length+col_offset; cindex<nc; cindex++) {
      int arrayIndex = row*((ncol+7)/8)+cindex/8; // each row starts on a byte boundary
      byte packedcell = packedData[arrayIndex];
      int mask = 0x80>>>(cindex%8);               // start with 0x10000000 and shift right
      if(val[cindex-col_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  row
   * @param  col_offset  the colum offset
   * @param  val         the value
   */
  public void setRow(int row, int col_offset, byte val[]) {
    if((row<0)||(row>=nrow)) {
      throw new IllegalArgumentException("Row index out of range in binary lattice setRow."); //$NON-NLS-1$
    }
    if((col_offset<0)||(col_offset+val.length>ncol)) {
      throw new IllegalArgumentException("Column index out of range in binary lattice setRow."); //$NON-NLS-1$
    }
    for(int cindex = col_offset, nc = val.length+col_offset; cindex<nc; cindex++) {
      int arrayIndex = row*((ncol+7)/8)+cindex/8; // each row starts on a byte boundary
      byte packedcell = packedData[arrayIndex];
      int mask = 0x80>>>(cindex%8);               // start with 0x10000000 and shift right
      if(val[cindex-col_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 lattice cell to a new value. A cell should take on a value of 0 or
   *  1.
   *
   * @param  _row
   * @param  _col
   * @param  val
   */
  public void setCell(int _row, int _col, int val) {
    if((_row<0)||(_row>=nrow)||(_col<0)||(_col>=ncol)) {
      throw new IllegalArgumentException("Cell row or column index out of range.  row="+_row+"  col="+_col); //$NON-NLS-1$ //$NON-NLS-2$
    }
    int arrayIndex = _row*((ncol+7)/8)+_col/8; // each row starts on a byte boundary
    byte packedcell = packedData[arrayIndex];
    int mask = 0x80>>>(_col%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;
  }

  /**
   *  Gets a lattice cell value. Cell values are zero or one.
   *
   * @param  _row
   * @param  _col
   * @return       the cell value.
   */
  public byte getCell(int _row, int _col) {
    byte packedcell = packedData[_row*((ncol+7)/8)+_col/8]; // each row starts on a byte boundary
    int mask = 0x80>>>(_col%8);                             // start with 0x10000000 and shift right
    if((packedcell&mask)>0) {
      return 1;
    }
    return 0;
  }

  /**
   *  Sets the color palette. The color at the 0th index of the array is set to
   *  the zero color.
   *
   * @param  colors
   */
  public void setColorPalette(Color[] colors) {
    if(colors.length!=2) {
      throw new IllegalArgumentException("Array must have length of 2"); //$NON-NLS-1$
    }
    zeroColor = colors[0];
    oneColor = colors[1];
  }

  /**
   *  Sets the color for a single index.
   *
   * @param  i      the value to set the color for.
   * @param  color
   */
  public void setIndexedColor(int i, Color color) {
    if(i==0) {
      zeroColor = color;
    } else {
      oneColor = color;
    }
  }

  public String toString() {
    StringBuffer sb = new StringBuffer(nrow*ncol+nrow);
    int row = 0;
    int column = 0;
    for(int i = 0, size = packedData.length; i<size; i++) {
      byte packedCell = packedData[i];
      for(int j = BITS_PER_BYTE; (j>0)&&(column<ncol); j--) {
        byte val = (byte) (packedCell>>>(j-1));
        int one_or_zero = (val&1);
        if(one_or_zero==0) {
          sb.append("0");    //$NON-NLS-1$
        } else {
          sb.append("1");    //$NON-NLS-1$
        }
        if(column==(ncol-1)) {
          if(i!=(size-1)) {
            sb.append("\n"); //$NON-NLS-1$
          }
          column = 0;
          row++;
          break;
        }
        column++;
      }
    }
    return sb.toString();
  }

}

/*
 * 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
 */
