
// NumberTable.java
// Andrew Davison, ad@coe.psu.ac.th, April 2023

/* A number table is a grid of boxes containing the
   integers from 1 to n, with n determined by the 
   number of rows and columns supplied in the
   constructor.

   Each box is colored -- LIGHT_BLUE initially, turning
   to PINK if the box is 'invalidated' by a call to
   setValid().

   Used by ShowSieve.java to display the execution of 
   a primes sieve over time. Composites numbers are
   made 'invalid' as the sieving progresses.

   There is a short main() function included in this class
   as a test rig.
*/


import java.awt.*;
import javax.swing.*;
import javax.swing.table.*;


public class NumberTable 
{
  private static final Color LIGHT_BLUE = Color.decode("#99ccff");
                                  // 'valid' color
  private static final Color PINK = Color.decode("#ffb3d1");  // 'invalid'

  private JTable numTable;
  private int numRows, numCols;


  public NumberTable(int numRows,int numCols) 
  {
    Object[][] nums = new Object[numRows][numCols];
    this.numRows = numRows;
    this.numCols = numCols;

    // the text in each box is a number from 1 to n
    int counter = 1;
    for (int r = 0; r < numRows; r++) {
      for (int c = 0; c < numCols; c++) {
        nums[r][c] = ""+counter;
        counter++;
      }
    }

    Object[] columnLabels = new Object[numCols];
    for (int c = 0; c < numCols; c++)
       columnLabels[c] = "";   //no column labels


    // create the table
    numTable = new JTable(nums, columnLabels) {
      public Class<?> getColumnClass(int column) 
      {  return String.class; }
    };

    Font font = new Font("Verdana", Font.PLAIN, 12);
    numTable.setFont(font);
    numTable.setRowHeight(30);
    numTable.setTableHeader(null);
    numTable.setPreferredScrollableViewportSize(
                            numTable.getPreferredSize());

    /* modify the renderer so that if the text in a box
       starts with a space then it is colored invalid (PINK)
    */
    DefaultTableCellRenderer numRenderer = 
                   new DefaultTableCellRenderer()
    {
      public Component getTableCellRendererComponent(
                      JTable table, Object value,
                      boolean isSelected, boolean hasFocus,
                      int row, int column) 
      {
        Component c = super.getTableCellRendererComponent(
               table, value, isSelected, hasFocus, row, column);
        String s = (String)value;
        c.setBackground((s.charAt(0) == ' ') ? PINK : LIGHT_BLUE);
        return c;
      }
    };

    numRenderer.setHorizontalAlignment(JLabel.CENTER);
    numTable.setDefaultRenderer(String.class, numRenderer);


    // put the table inside a window
    JFrame f = new JFrame("Number Table");
    f.add(new JScrollPane(numTable));

    f.pack();
    f.setResizable(false);
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setVisible(true);
  }  // end of NumberTable()


  public int getValue(int row, int col)
  /* any spaces around the box text are trimmed,
     and the text is parsed as an integer
  */
  {
    if ((row < 0) || (row > numRows)) {
      System.out.println("Row value " + row + 
                            " out of range (1-" + numRows + ")");
      return 0;
    }
    if ((col < 0) || (col > numCols)) {
      System.out.println("Column value " + col + 
                            " out of range (1-" + numCols + ")");
      return 0;
    }
    String s = ((String)numTable.getValueAt(row, col)).trim();
    return Nut.parseInt(s);
  } // end of getValue()



  public void setValid(int row, int col, boolean isValid)
  /* A box is 'invalid' if its text contains a space at
     the beginning. A space is also added to the end of the text
     so that it remains centered inside the box.
  */
  {
    if ((row < 0) || (row > numRows)) {
      System.out.println("Row value " + row + 
                            " out of range (1-" + numRows + ")");
      return;
    }

    if ((col < 0) || (col > numCols)) {
      System.out.println("Column value " + col + 
                            " out of range (1-" + numCols + ")");
      return;
    }

    String s = ((String)numTable.getValueAt(row, col)).trim();
    if (isValid)
      numTable.setValueAt(s, row, col);
    else
      numTable.setValueAt(" " + s + " ", row, col);
  }  // end of setValid()



  public boolean isValid(int row, int col)
  // is the box at (row, col) valid?
  {
    if ((row < 0) || (row > numRows)) {
      System.out.println("Row value " + row + 
                            " out of range (1-" + numRows + ")");
      return false;
    }

    if ((col < 0) || (col > numCols)) {
      System.out.println("Column value " + col + 
                            " out of range (1-" + numCols + ")");
      return false;
    }

    String s = (String)numTable.getValueAt(row, col);
    return (s.charAt(0) != ' ');
  }  // end of isValid()



  public void setValid(int value, boolean isValid)
  // find the box containing value, and set its validity
  {
    int[] pos = findPos(value);   // returns [row, col]
    if (pos == null)
      System.out.println("Could not find " + value + " in the table");
    else
      setValid(pos[0], pos[1], isValid);
  }  // end of setValid()


  private int[] findPos(int value)
  // return [row, col] position for value, or null
  {
    if ((value < 1) || (value > numRows*numCols))
      return null;

    int tableVal = value - 1;   // since no '0' value

    int r = tableVal / numCols;   // integer division
    int c = tableVal - (numCols * r);
    return new int[] {r, c};
  }  // end of findPos()



  public boolean isValid(int value)
  // find the box containing value, and return its validity
  {
    int[] pos = findPos(value);   // [row, col]
    if (pos == null) {
      System.out.println("Could not find " + value + " in the table");
      return false;
    }
    else
      return isValid(pos[0], pos[1]);
  }  // end of isValid()


  // ---------------- test rig ---------------

  public static void main(String args[]) 
  {
    NumberTable nt = new NumberTable(5, 10);
    nt.setValid(4, false);
  }  // end of main()

}  // end of NumberTable class