//Ali Soleimani
//CS 3 Lab 4

//Extra credit implemented: can select size of spreadsheet at command line
//Can use scrollbars; default implementation of ScrollPane used.




import java.awt.*;
import java.awt.event.*;
import java.util.*;

//This is a main spreadsheet window; its main method opens one up.
public class Spreadsheet extends Frame {

  //Data members
  static int rows;           //Rows,columns in the sheet
  static int columns;
  Cell[][] shown;        //The actual cells displayed.  Basically labels.
  static TextField statusBar;   //The bar for entering new data.
  MyListener l;          //A mouselistener for the cells.
  MyInputListener i;     //An actionlistener for the status bar.
  Cell selected;         //The currently selected cell.
  
  public static void main(String args[]) {
    System.out.println("This program is fairly intuitive; but note that\n"+
		       "1) Formulas must be of the form X8+Y9, with only +,-,*,/ allowed.\n"+
		       "2) Recalculation is performed by clicking on the desired cell.\n"+
		       "3) Dependent cells are NOT automatically calculated."+"\n"+
		       "4) Run \"Spreadsheet x y\" for an x-by-y spreadsheet.\n" +
		       "5) If the sheet needs more area than in the window, it will display scrollbars.\n"+
		       "See the README for further documentation.");
    int r=10;
    int c=10;
    try {
      r=Integer.valueOf(args[0]).intValue();
      c=Integer.valueOf(args[1]).intValue();
    }
    catch(RuntimeException e) {}    //Catch the exception if we don't have args.
    Spreadsheet window1 = new Spreadsheet(r,c);
    window1.setSize(800,400);
    window1.setTitle("Ali's Spreadsheet");
    window1.show();
  }

  //The nasty constructor
  public Spreadsheet(int rowNum, int colNum) {


    if(rowNum>0) rows=rowNum;
    else rows=10;
    if(colNum>0) columns=colNum;
    else columns=10;

    Panel sheet=new Panel();
    ScrollPane sheetPane=new ScrollPane();
    l=new MyListener();
    i=new MyInputListener();
    shown= new Cell[rows+1][columns+1];
    statusBar = new TextField();
    statusBar.addActionListener(i);

    setLayout(new BorderLayout());
    sheet.setLayout(new GridLayout(rows+1,columns+1));
    sheetPane.add(sheet);
    addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {System.exit(1);} }
		      );
    add(statusBar, "North");
    add(sheetPane, "Center");

    for(int i=0; i<=rows; i++)
      for(int j=0; j<=columns; j++) {
	shown[i][j]=new Cell();
	if(i!=0 && j!=0) shown[i][j].addMouseListener(l);
	sheet.add(shown[i][j]);
      }
    for(int i=1; i<=columns; i++) {
      shown[0][i].setText("#"+String.valueOf(i));
    }
    for(int i=1; i<=rows; i++) {
      char c=(char)('A'+i-1);
      shown[i][0].setText(String.valueOf(c));
    }
    
    selected=shown[1][1]; selected.isSelected=true;
    
  }
  
  
  class MyInputListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      String text=statusBar.getText();
      selected.setText(text);
      selected.repaint();
      statusBar.setText(selected.getText());
      statusBar.selectAll();
    }
  }
  
  class MyListener extends MouseAdapter {
    public void mouseClicked(MouseEvent e) {
      Cell d=(Cell)e.getComponent();
      selected.isSelected=false;
      selected.repaint();
      selected=d;
      selected.isSelected=true;
      selected.reCalc();
      selected.repaint();
      statusBar.setText(selected.getText());
      statusBar.selectAll();
    }
  }
  







  //A static method that tells us whether a string is a formula
  static boolean isFormula(String s) {
    return s.startsWith("=");
  }
  
  //Similar for checking if a string is a number.
  static boolean isNumber(String s) {
    try {Float.valueOf(s);}
    catch(NumberFormatException n) {
      return false;}
    return true;
  }










class Cell extends Canvas {
  
  SheetData data;
  boolean isSelected;

  public Cell() {data=new SheetText("");}
  
  public String getText() {return data.getText();}  //Gets the text representation of the data 

  public float getValue() {return data.getValue();} //Gets the numerical value of the data 

  public void reCalc() {data.reCalc();} //Gets the string output of the data
  
  public void setText(String s) {
    if (isFormula(s)) data=new SheetFormula(s);
    else if (isNumber(s)) data=new SheetNumber(s);
    else data=new SheetText(s);
  }
  
  public Dimension getMinimumSize() {
    return getPreferredSize();
  }

  public Dimension getPreferredSize() {
    return new Dimension(100,15);
  }
 
  public void paint(Graphics g) {
    Dimension d=getSize();
    g.drawRect(0,0,d.width-1, d.height-1);
    g.drawString(data.getOutput(), 2, d.height-3);
    if(isSelected) g.drawRect(1,1,d.width-3,d.height-3);
  }
  
}

//This is the base class for the formula, number, text classes.
abstract class SheetData {

  String text;   //The text representation--i.e. formula, #, string.
  


  String getText() {return text;}
  
  //Some subclasses might keep track of or calc these, so we leave them abstract. 
  abstract String getOutput();

  abstract float getValue();

  void reCalc() {};   //Recalculates the cell; does nothing by default.

}











class SheetFormula extends SheetData {
  
  int r1,c1,r2,c2, operator;     //Auxiliary variables for holding the formula.
  float lastValue;
  boolean isValidFormula;


  //Our constructor parses the string and divides it up so we can deal with it.
  SheetFormula(String s) {
    try {       //We may get a lot of out-of-bounds exceptions here.  Catch them all later.
      String working=s.substring(1, s.length()).trim();     //Discard =, whitspace
      StringTokenizer t=new StringTokenizer(working, "=+-/*", true);
      String first = t.nextToken().trim();  //Get first token, trimmed
      String operand = t.nextToken();       //Get operand
      String second = t.nextToken().trim();   //Get second token, trimmed.
      if(t.hasMoreTokens()) throw new RuntimeException(); //Formula was not as we expected
      if(operand.length()!=1) throw new RuntimeException(); //Not an operand.
      
      char o = operand.charAt(0);
      if(o=='+') operator=1;
      else if(o=='-') operator=2;
      else if(o=='*') operator=3;
      else if(o=='/') operator=4;
      else throw new RuntimeException();     //Operator was not valid.

      char a=first.toUpperCase().charAt(0);
      if (a<'A' || a-'A'>=Spreadsheet.rows) throw new RuntimeException();
      else r1=a-'A'+1;
      char b=second.toUpperCase().charAt(0);
      if (b<'A' || b-'A'>=Spreadsheet.rows) throw new RuntimeException();
      else r2=b-'A'+1;
        
      first=first.substring(1, first.length());       //Crop the row indicator
      second=second.substring(1, second.length());
      c1=Integer.valueOf(first).intValue();
      c2=Integer.valueOf(second).intValue();
      if(c1<=0 || c1>Spreadsheet.columns) throw new RuntimeException();
      if(c2<=0 || c2>Spreadsheet.columns) throw new RuntimeException();
      isValidFormula=true;        //We extracted a valid formula!!!
      text="="+a+Integer.toString(c1)+o+b+Integer.toString(c2);           //Make the formula nice and pretty.
    }
    catch(Exception e) {   //If we get any exceptions, then the formula is invalid
      text="INVALID";
      isValidFormula=false;
    }
    reCalc();
  }

  
  void reCalc() {
    if(isValidFormula) {
      float response=0;
      if(operator==1) response=shown[r1][c1].getValue() + shown[r2][c2].getValue();
      if(operator==2) response=shown[r1][c1].getValue() - shown[r2][c2].getValue();
      if(operator==3) response=shown[r1][c1].getValue() * shown[r2][c2].getValue();
      if(operator==4) response=shown[r1][c1].getValue() / shown[r2][c2].getValue();
      lastValue=response;
    }
    else lastValue=0;
  }
  
  float getValue() {
    return lastValue;
  }

  String getOutput() {
    return String.valueOf(lastValue);
  }
      
}







    
class SheetText extends SheetData {
  
  SheetText(String s) {
    text=s;
  }
  
  float getValue() {return 0;}

  String getOutput() {return text;}

}




  
class SheetNumber extends SheetData {

  float value;

  SheetNumber(String s) {
    try {
      value=Float.valueOf(s).floatValue();
    }
    catch(Exception e) {value=Float.NaN;}  //use not-a-number if we this doesn't work.
    text=String.valueOf(value);
  }


  float getValue() {return value;}

  String getOutput() {return String.valueOf(value);}
  
}
  
}

  

