package jxl.biff.formula;

import common.Assert;
import common.Logger;
import java.util.Stack;
import jxl.Cell;
import jxl.WorkbookSettings;
import jxl.biff.WorkbookMethods;

class TokenFormulaParser implements Parser {
  private static Logger logger = Logger.getLogger(TokenFormulaParser.class);
  
  private byte[] tokenData;
  
  private Cell relativeTo;
  
  private int pos;
  
  private ParseItem root;
  
  private Stack tokenStack;
  
  private ExternalSheet workbook;
  
  private WorkbookMethods nameTable;
  
  private WorkbookSettings settings;
  
  public TokenFormulaParser(byte[] data, Cell c, ExternalSheet es, WorkbookMethods nt, WorkbookSettings ws) {
    this.tokenData = data;
    this.pos = 0;
    this.relativeTo = c;
    this.workbook = es;
    this.nameTable = nt;
    this.tokenStack = new Stack();
    this.settings = ws;
  }
  
  public void parse() throws FormulaException {
    parseSubExpression(this.tokenData.length);
    this.root = this.tokenStack.pop();
    Assert.verify(this.tokenStack.empty());
  }
  
  private void parseSubExpression(int len) throws FormulaException {
    int tokenVal = 0;
    Token t = null;
    Stack ifStack = new Stack();
    int endpos = this.pos + len;
    while (this.pos < endpos) {
      tokenVal = this.tokenData[this.pos];
      this.pos++;
      t = Token.getToken(tokenVal);
      if (t == Token.UNKNOWN)
        throw new FormulaException(FormulaException.unrecognizedToken, tokenVal); 
      Assert.verify((t != Token.UNKNOWN));
      if (t == Token.REF) {
        CellReference cr = new CellReference(this.relativeTo);
        this.pos += cr.read(this.tokenData, this.pos);
        this.tokenStack.push(cr);
        continue;
      } 
      if (t == Token.REF) {
        CellReferenceError cr = new CellReferenceError();
        this.pos += cr.read(this.tokenData, this.pos);
        this.tokenStack.push(cr);
        continue;
      } 
      if (t == Token.REFV) {
        SharedFormulaCellReference cr = new SharedFormulaCellReference(this.relativeTo);
        this.pos += cr.read(this.tokenData, this.pos);
        this.tokenStack.push(cr);
        continue;
      } 
      if (t == Token.REF3D) {
        CellReference3d cr = new CellReference3d(this.relativeTo, this.workbook);
        this.pos += cr.read(this.tokenData, this.pos);
        this.tokenStack.push(cr);
        continue;
      } 
      if (t == Token.AREA) {
        Area a = new Area();
        this.pos += a.read(this.tokenData, this.pos);
        this.tokenStack.push(a);
        continue;
      } 
      if (t == Token.AREAV) {
        SharedFormulaArea a = new SharedFormulaArea(this.relativeTo);
        this.pos += a.read(this.tokenData, this.pos);
        this.tokenStack.push(a);
        continue;
      } 
      if (t == Token.AREA3D) {
        Area3d a = new Area3d(this.workbook);
        this.pos += a.read(this.tokenData, this.pos);
        this.tokenStack.push(a);
        continue;
      } 
      if (t == Token.NAME) {
        Name n = new Name();
        this.pos += n.read(this.tokenData, this.pos);
        this.tokenStack.push(n);
        continue;
      } 
      if (t == Token.NAMED_RANGE) {
        NameRange nr = new NameRange(this.nameTable);
        this.pos += nr.read(this.tokenData, this.pos);
        this.tokenStack.push(nr);
        continue;
      } 
      if (t == Token.INTEGER) {
        IntegerValue i = new IntegerValue();
        this.pos += i.read(this.tokenData, this.pos);
        this.tokenStack.push(i);
        continue;
      } 
      if (t == Token.DOUBLE) {
        DoubleValue d = new DoubleValue();
        this.pos += d.read(this.tokenData, this.pos);
        this.tokenStack.push(d);
        continue;
      } 
      if (t == Token.BOOL) {
        BooleanValue bv = new BooleanValue();
        this.pos += bv.read(this.tokenData, this.pos);
        this.tokenStack.push(bv);
        continue;
      } 
      if (t == Token.STRING) {
        StringValue sv = new StringValue(this.settings);
        this.pos += sv.read(this.tokenData, this.pos);
        this.tokenStack.push(sv);
        continue;
      } 
      if (t == Token.MISSING_ARG) {
        MissingArg ma = new MissingArg();
        this.pos += ma.read(this.tokenData, this.pos);
        this.tokenStack.push(ma);
        continue;
      } 
      if (t == Token.UNARY_PLUS) {
        UnaryPlus up = new UnaryPlus();
        this.pos += up.read(this.tokenData, this.pos);
        addOperator(up);
        continue;
      } 
      if (t == Token.UNARY_MINUS) {
        UnaryMinus um = new UnaryMinus();
        this.pos += um.read(this.tokenData, this.pos);
        addOperator(um);
        continue;
      } 
      if (t == Token.PERCENT) {
        Percent p = new Percent();
        this.pos += p.read(this.tokenData, this.pos);
        addOperator(p);
        continue;
      } 
      if (t == Token.SUBTRACT) {
        Subtract s = new Subtract();
        this.pos += s.read(this.tokenData, this.pos);
        addOperator(s);
        continue;
      } 
      if (t == Token.ADD) {
        Add s = new Add();
        this.pos += s.read(this.tokenData, this.pos);
        addOperator(s);
        continue;
      } 
      if (t == Token.MULTIPLY) {
        Multiply s = new Multiply();
        this.pos += s.read(this.tokenData, this.pos);
        addOperator(s);
        continue;
      } 
      if (t == Token.DIVIDE) {
        Divide s = new Divide();
        this.pos += s.read(this.tokenData, this.pos);
        addOperator(s);
        continue;
      } 
      if (t == Token.CONCAT) {
        Concatenate c = new Concatenate();
        this.pos += c.read(this.tokenData, this.pos);
        addOperator(c);
        continue;
      } 
      if (t == Token.POWER) {
        Power p = new Power();
        this.pos += p.read(this.tokenData, this.pos);
        addOperator(p);
        continue;
      } 
      if (t == Token.LESS_THAN) {
        LessThan lt = new LessThan();
        this.pos += lt.read(this.tokenData, this.pos);
        addOperator(lt);
        continue;
      } 
      if (t == Token.LESS_EQUAL) {
        LessEqual lte = new LessEqual();
        this.pos += lte.read(this.tokenData, this.pos);
        addOperator(lte);
        continue;
      } 
      if (t == Token.GREATER_THAN) {
        GreaterThan gt = new GreaterThan();
        this.pos += gt.read(this.tokenData, this.pos);
        addOperator(gt);
        continue;
      } 
      if (t == Token.GREATER_EQUAL) {
        GreaterEqual gte = new GreaterEqual();
        this.pos += gte.read(this.tokenData, this.pos);
        addOperator(gte);
        continue;
      } 
      if (t == Token.NOT_EQUAL) {
        NotEqual ne = new NotEqual();
        this.pos += ne.read(this.tokenData, this.pos);
        addOperator(ne);
        continue;
      } 
      if (t == Token.EQUAL) {
        Equal e = new Equal();
        this.pos += e.read(this.tokenData, this.pos);
        addOperator(e);
        continue;
      } 
      if (t == Token.PARENTHESIS) {
        Parenthesis p = new Parenthesis();
        this.pos += p.read(this.tokenData, this.pos);
        addOperator(p);
        continue;
      } 
      if (t == Token.ATTRIBUTE) {
        Attribute a = new Attribute(this.settings);
        this.pos += a.read(this.tokenData, this.pos);
        if (a.isSum()) {
          addOperator(a);
          continue;
        } 
        if (a.isIf())
          ifStack.push(a); 
        continue;
      } 
      if (t == Token.FUNCTION) {
        BuiltInFunction bif = new BuiltInFunction(this.settings);
        this.pos += bif.read(this.tokenData, this.pos);
        addOperator(bif);
        continue;
      } 
      if (t == Token.FUNCTIONVARARG) {
        VariableArgFunction vaf = new VariableArgFunction(this.settings);
        this.pos += vaf.read(this.tokenData, this.pos);
        if (vaf.getFunction() != Function.ATTRIBUTE) {
          addOperator(vaf);
          continue;
        } 
        vaf.getOperands(this.tokenStack);
        Attribute ifattr = null;
        if (ifStack.empty()) {
          ifattr = new Attribute(this.settings);
        } else {
          ifattr = ifStack.pop();
        } 
        ifattr.setIfConditions(vaf);
        this.tokenStack.push(ifattr);
        continue;
      } 
      if (t == Token.MEM_FUNC) {
        MemFunc memFunc = new MemFunc();
        this.pos += memFunc.read(this.tokenData, this.pos);
        Stack oldStack = this.tokenStack;
        this.tokenStack = new Stack();
        parseSubExpression(memFunc.getLength());
        ParseItem[] subexpr = new ParseItem[this.tokenStack.size()];
        int i = 0;
        while (!this.tokenStack.isEmpty()) {
          subexpr[i] = this.tokenStack.pop();
          i++;
        } 
        memFunc.setSubExpression(subexpr);
        this.tokenStack = oldStack;
        this.tokenStack.push(memFunc);
      } 
    } 
  }
  
  private void addOperator(Operator o) {
    o.getOperands(this.tokenStack);
    this.tokenStack.push(o);
  }
  
  public String getFormula() {
    StringBuffer sb = new StringBuffer();
    this.root.getString(sb);
    return sb.toString();
  }
  
  public void adjustRelativeCellReferences(int colAdjust, int rowAdjust) {
    this.root.adjustRelativeCellReferences(colAdjust, rowAdjust);
  }
  
  public byte[] getBytes() {
    return this.root.getBytes();
  }
  
  public void columnInserted(int sheetIndex, int col, boolean currentSheet) {
    this.root.columnInserted(sheetIndex, col, currentSheet);
  }
  
  public void columnRemoved(int sheetIndex, int col, boolean currentSheet) {
    this.root.columnRemoved(sheetIndex, col, currentSheet);
  }
  
  public void rowInserted(int sheetIndex, int row, boolean currentSheet) {
    this.root.rowInserted(sheetIndex, row, currentSheet);
  }
  
  public void rowRemoved(int sheetIndex, int row, boolean currentSheet) {
    this.root.rowRemoved(sheetIndex, row, currentSheet);
  }
}
