import java.util.Hashtable;
import rebelsky.compiler.lexer.Token;
import rebelsky.compiler.misc.Symbol;
import rebelsky.compiler.misc.SymbolTable;
import rebelsky.compiler.parser.Node;
import rebelsky.compiler.pascal.PascalIdentifier;
import rebelsky.compiler.pascal.PascalInteger;
import rebelsky.compiler.pascal.PascalNonterminals;
import rebelsky.compiler.pascal.PascalReal;
import rebelsky.compiler.pascal.PascalString;
import rebelsky.compiler.pascal.PascalTokens;
import rebelsky.pal.*;

/**
 * A simple example of a program that walks a Stupid parse tree and 
 * build a corresponding PAL program.
 *
 * @author Samuel A. Rebelsky
 * @version 0.6 of December 2002.
 */
public class StupidTranslator
{
  // +----------------------+----------------------------------------------
  // | Implementation Notes |
  // +----------------------+
/*
  I've defined each type as an object constant.  I'll use pointer 
  comparison (==) when I need to compare types.  I use objects
  rather than integers because (1) objects are easier to store in
  hash tables; (2) objects are easier to print.
 */

  // +-----------------+---------------------------------------------------
  // | Private Classes |
  // +-----------------+
  
  /**
   * All the fun information returned by an expression.
   */
  private static class ExpressionStuff
  {
    Instruction code;
    Object type;
    Variable loc;
  } // class ExpressionStuff

  /**
   * Types of functions.  Functions have both a parameter type 
   * and a result type.
   */
  private static class FunctionType
  {
    Object paramType;
    Object resultType;
  } // class FunctionType
  

  // +-----------+---------------------------------------------------------
  // | Constants |
  // +-----------+

  /** The type used for integers. */
  private static Object TYPE_INTEGER = "integer";
  /** The type used for real numbers. */
  private static Object TYPE_REAL = "real";
  /** The type used for single characters. */
  private static Object TYPE_CHAR = "char";
  /** The type used for boolean values. */
  private static Object TYPE_BOOLEAN = "boolean";
  /** The type used for string values. */
  private static Object TYPE_STRING = "string";
  /** The type used for undefined values. */
  private static Object TYPE_UNDEFINED = "undefined";


  // +------------------------+--------------------------------------------
  // | Private Helper Methods |
  // +------------------------+

  /**
   * Report an error.  A more concise way to say 
   * "throw new Exception(...)".
   */
  public static void boom(String msg) 
    throws Exception
  {
    throw new Exception(msg);
  } // boom(String)
  
  /**
   * Convert an identifier token that describes a type to the
   * corresponding type.
   */
  public static Object getType(Token t) 
    throws Exception
  {
    if (t == PascalTokens.TBOOLEAN) return  TYPE_BOOLEAN;
    if (t == PascalTokens.TCHAR) return  TYPE_CHAR;
    if (t == PascalTokens.TINTEGER) return  TYPE_INTEGER;
    if (t == PascalTokens.TREAL) return  TYPE_REAL;
    throw new Exception("Invalid type: " + t.toString());
  } // getType(token)


  // +-----------------------------+--------------------------------------
  // | Private Translation Methods |
  // +-----------------------------+

  /**
   * Translate an assignment statement.
   */
  public static InstructionSequence
  translateAssignment(Node assignment, SymbolTable symbols)
    throws Exception
  {
    // Get the variable.
    Symbol var = assignment.getChild(0).getSymbol();
    // Sanity check: Can't assign to variables that don't exist.
    if (!symbols.isSet(var, "location"))
      throw new Exception("Invalid variable: " + var);
    InstructionSequence code = new InstructionSequence();
    ExpressionStuff stuff = 
      translateExpression(assignment.getChild(1), symbols);
    code.add(stuff.code);
    if (stuff.type != symbols.get(var, "type"))
      throw new Exception("Cannot assign values of incompatible types.");
    code.add(new Move(stuff.loc, (Variable) symbols.get(var, "location")));
    return code;
  } // translateAssignment(Node, SymbolTable)

  public static Instruction 
  translateBooleanExpression(Node expression, SymbolTable symbols,
                             Label truelabel, Label falselabel)
    throws Exception
  {
    InstructionSequence instructions = new InstructionSequence();
    Symbol sym = expression.getSymbol();
    // Case 1: Constant true
    if (sym == PascalTokens.TTRUE)
      return new Jump(truelabel);
    // Case 2: Constant false
    else if (sym == PascalTokens.TFALSE)
      return new Jump(falselabel);
    // Case 3: Variable
    else if (sym instanceof PascalIdentifier)
      throw new Exception("Can't handle boolean identifiers yet.");
    // Case 4: Anything else.  Punt!
    else
      throw new Exception("Can't handle that kind of boolean expression.");
  } // translateBooleanExpression()

  /**
   * Translate a compound statement.
   *
   * @exception Exception
   *   If the tree does not type check.
   */
  public static InstructionSequence 
  translateCompoundStatement(Node statements, SymbolTable symbols)
    throws Exception
  {
    // Make sure that it's actually a list of statements.
    if (statements.getSymbol() != PascalNonterminals.COMPOUND_STATEMENT)
      throw new Exception("Didn't find compound statement!");
    // Set up the list of instructions.
    InstructionSequence instructions = new InstructionSequence();
    // Translate each statement in turn.
    int numStatements = statements.numChildren();
    for (int statementNum = 0; statementNum < numStatements; ++statementNum) {
      instructions.add(translateStatement(statements.getChild(statementNum),
                                          symbols));
    } // for
    return instructions;
  } // translateCompoundStatement(Node)

  /**
   * Translate a conditional statement.
   */
  public static InstructionSequence
  translateConditional(Node conditional, SymbolTable symbols)
    throws Exception
  {
    InstructionSequence code = new InstructionSequence();
    Label truepart = new Label("TRUE_PART");
    Label falsepart = new Label("FALSE_PART");
    Label end = new Label("END_IF");
    code.add(translateBooleanExpression(conditional.getChild(0), symbols,
                                        truepart, falsepart));
    code.add(truepart);
    code.add(translateStatement(conditional.getChild(1), symbols));
    code.add(new Jump(end));
    code.add(falsepart);
    code.add(translateStatement(conditional.getChild(2), symbols));
    code.add(new Jump(end));
    code.add(end);

    return code;
  } // translateConditional(Node, SymbolTable)

  /**
   * Translate a parse tree for an expression.  Returns a triplet
   * of code for expression, type of expression, and location of
   * result of expression.
   */
  public static ExpressionStuff
  translateExpression(Node expression, SymbolTable symbols)
    throws Exception
  {
    Symbol sym = expression.getSymbol();
    ExpressionStuff result = new ExpressionStuff();

    // Case 1: Identifier
    if (sym instanceof PascalIdentifier) {
      if (!symbols.isSet(sym, "location"))
        throw new Exception("Identifier " + sym + " is undefined.");
      result.type = symbols.get(sym, "type");
      result.code = new NoOp();
      result.loc = (Variable) symbols.get(sym, "location");
    } // Case 1: Identifier

    // Case 2: Integer
    else if (sym instanceof PascalInteger) {
      result.loc = new Temporary();
      result.code = new Move(new IConstant(((PascalInteger) sym).getValue()), 
                                           result.loc);
      result.type = TYPE_INTEGER;
    } // Case 2: Integer

    // Case 3: Real
    else if (sym instanceof PascalReal) {
      result.loc = new Temporary();
      result.code = new Move(new FConstant(((PascalReal) sym).getValue()), 
                                           result.loc);
      result.type = TYPE_REAL;
    } // Case 3: Real

    // Case 4: Operator applied to two symbols.
    else if (expression.numChildren() == 3) {
      InstructionSequence code = new InstructionSequence();
      result.loc = new Temporary();
      // Translate the two subexpressions and retain their code.
      ExpressionStuff left = 
        translateExpression(expression.getChild(0), symbols);
      ExpressionStuff right = 
        translateExpression(expression.getChild(2), symbols);
      code.add(left.code);
      code.add(right.code);
      // Sanity check: Are the two result types the same?
      if (left.type != right.type)
        throw new Exception("Cannot apply operation to mismatched types.");
      result.type = left.type;
      // Decide what operation to generate
      Symbol op = expression.getChild(1).getSymbol();
      if (op == PascalTokens.TPLUS) {
        if (result.type == TYPE_INTEGER)
          code.add(new IAdd(left.loc, right.loc, result.loc));
        else if (result.type == TYPE_REAL)
          code.add(new FAdd(left.loc, right.loc, result.loc));
        else
          throw new Exception("Cannot add variables of type " + left.type);
      } // addition
      result.code = code;
    } // Case 4: Operator

    // Case 5: Unknown
    else {
      throw new Exception("Unknown expression type.");
    }

    // That's it, we're done.
    return result;
  } // translateExpression(Node,Hashtable)

  /**
   * Translate a sequence of function declarations.  Update the
   * symbol table while translating.
   */
  public static InstructionSequence
  translateFunctions(Node functions, SymbolTable symbols)
    throws Exception
  {
    // Make sure that it's actually a list of functions.
    if (functions.getSymbol() != PascalNonterminals.FUNCTION_DECLARATIONS)
      throw new Exception("Didn't find sequence of function declarations!");
    // Set up the list of instructions.
    InstructionSequence instructions = new InstructionSequence();
    // Translate each statement in turn.
    int numFunctions = functions.numChildren();
    for (int functionNum = 0; functionNum < numFunctions; ++functionNum) {
      Node declaration = functions.getChild(functionNum);
      // MORE TO COME ...
    } // for
    return instructions;
  } // translateFunctions(Node,SymbolTable)

  /**
   * Translate the global variable declarations.  Update the symbol table 
   * while translating.  Since translation of the globals does not generate 
   * any code (or shouldn't generate any code), this returns nothing.
   
   * @exception Exception
   *   If the tree does not type check.
   */
  public static void 
  translateGlobals(Node declarations, SymbolTable symbols)
    throws Exception
  {
    // Make sure that it's a collections of declarations
    if (declarations.getSymbol() != PascalNonterminals.VARIABLE_DECLARATIONS)
      throw new Exception("Didn't find declarations");
    // Deal with each declaration in turn.
    int numDeclarations = declarations.numChildren();
    for (int decNum = 0; decNum < numDeclarations; ++decNum) {
      Node dec = declarations.getChild(decNum);
      if (dec.getSymbol() != PascalNonterminals.VARIABLE_DECLARATION)
        throw new Exception("Didn't find declaration where I expected it in " 
                            + dec.getSymbol());
      int numIds = dec.numChildren() - 1;
      Object type = getType((Token) dec.getChild(numIds).getSymbol());
      for (int idNum = 0; idNum < numIds; idNum++) {
        Token identifier = (Token) dec.getChild(idNum).getSymbol();
        symbols.set(identifier, "type", type); // may throw an exception
        symbols.set(identifier, "location", 
                    new MemLoc(new IConstant(idNum)));
      } // for (idNum)
    } // for (decNum)
  } // translateGlobals(Node, SymbolTable)

  /**
   * Translate a statement.
   */
  public static Instruction
  translateStatement(Node statement, SymbolTable symbols)
    throws Exception
  {
    if (statement == null) {
      return new NoOp();
    } // null statement
    if (statement.getSymbol() == PascalNonterminals.COMPOUND_STATEMENT) {
      return translateCompoundStatement(statement, symbols);
    } // compound statement
    else if (statement.getSymbol() == PascalNonterminals.ASSIGNMENT) {
      return translateAssignment(statement, symbols);
    }
    else if (statement.getSymbol() == PascalNonterminals.PROCEDURE_CALL) {
      Token proc = (Token) statement.getChild(0).getSymbol();
      if (proc == StupidTokens.TREAD) {
        Symbol variable = statement.getChild(1).getSymbol();
        if (!symbols.isSet(variable, "type"))
          throw new Exception("Variable " + variable + " is not defined.");
        Object type = symbols.get(variable,"type");
        if (type == TYPE_INTEGER)
          return new IRead((Variable) symbols.get(variable,"location"));
        else if (type == TYPE_REAL)
          return new FRead((Variable) symbols.get(variable,"location"));
        else
          throw new Exception("Cannot read variables of type " + type + ".");
      } // if (proc == StupidTokens.TREAD)
      else if (proc == StupidTokens.TWRITE) {
        // Get the expression
        Node expression = statement.getChild(1);
        // Special case: write a string
        if (expression.getSymbol() instanceof PascalString)
          return new SWrite(((PascalString) expression.getSymbol()).getName());
        // Prepare to generate a sequence of instructions.
        InstructionSequence instructions = new InstructionSequence();
        ExpressionStuff result = 
          translateExpression(expression, symbols);
        instructions.add(result.code);
        if (result.type == TYPE_INTEGER)
          instructions.add(new IWrite(result.loc));
        else if (result.type == TYPE_REAL)
          instructions.add(new FWrite(result.loc));
        else
          throw new Exception("Invalid type: " + result.type);
        return instructions;
      } // if (proc == StupidTokens.TWRITE)
      else {
        throw new Exception("Cannot call procedure '" + proc + "'");
      } // Unknown procedure call
    } // procedure call
    else {
      throw new Exception("Unexpected type of statement.");
    } // unknown statement type
  } // translateStatement(Node, SymbolTable)


  // +----------------+----------------------------------------------------
  // | Public Methods |
  // +----------------+

  /**
   * Translate the parse tree for a program.  What fun.
   *
   * @exception Exception
   *   If the tree does not type check.
   */
  public static InstructionSequence translate(Node program)
    throws Exception
  {
    // Make sure that it's a program.
    if (program.getSymbol() != PascalNonterminals.PROGRAM)
      boom("Not a program!");

    // Initialize the symbol table
    SymbolTable symbols = new SymbolTable();
    symbols.set(PascalTokens.TTRUE, "type", TYPE_BOOLEAN);
    symbols.set(PascalTokens.TFALSE, "type", TYPE_BOOLEAN);

    // Set up the main sequence of instructions.
    InstructionSequence code = new InstructionSequence();
    Label body = new Label("BODY");
    code.add(new Jump(body));

    // Check the variable declarations and update the symbol table.
    translateGlobals(program.getChild(0), symbols);

    // Check the function declarations and update the symbol table.
    code.add(translateFunctions(program.getChild(1), symbols));

    // Check the main body using the updated symbol table.
    code.add(body);
    code.add(translateCompoundStatement(program.getChild(2), symbols));
    code.add(new Halt());

    // That's it, we're done.
    return code;
  } // translate(Node)

} // class StupidTranslator

