/*
 * Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *   Free SoftwareFoundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.es.parser;

import java.io.*;
import java.net.*;
import java.util.*;

import com.caucho.util.*;
import com.caucho.vfs.*;
import com.caucho.es.*;

/**
 * Expr is an intermediate form representing an expression.
 */
class Expr {
  protected final static int TYPE_UNKNOWN = 0;
  protected final static int TYPE_ES      = TYPE_UNKNOWN + 1;
  protected final static int TYPE_STRING  = TYPE_ES + 1;
  protected final static int TYPE_NUMBER  = TYPE_STRING + 1;
  protected final static int TYPE_LONG    = TYPE_NUMBER;
  protected final static int TYPE_INTEGER = TYPE_LONG + 1;
  protected final static int TYPE_BOOLEAN = TYPE_INTEGER + 1;
  
  protected final static int TYPE_JAVA    = TYPE_BOOLEAN + 1;
  protected final static int TYPE_VOID    = TYPE_JAVA + 1;

  protected ParseClass cl;
  protected Block block;
  protected Function function;
  protected int type;
  protected Class javaType;
  protected boolean isTop;
  protected boolean noValue;
  
  private String filename;
  private int line;
  protected int withDepth;

  Expr(Block block)
  {
    this.block = block;
    this.withDepth = block.getWithDepth();
    this.function = block.function;
    this.cl = function.cl;
    this.filename = block.getFilename();
    this.line = block.getLine();

    type = TYPE_UNKNOWN;
  }

  String getFilename()
  {
    return filename;
  }

  int getLine()
  {
    return line;
  }

  void killValue()
  {
    noValue = true;
  }

  void setUsed()
  {
    getType();
  }

  void setTop()
  {
    noValue = true;
    isTop = true;
  }

  /**
   * Returns the javascript type of the expression.
   */
  int getType()
  {
    return type;
  }

  Expr getTypeExpr()
  {
    return null;
  }

  /**
   * Returns the Java class representing this type.
   */
  Class getJavaClass()
  {
    if (javaType != null)
      return javaType;
    
    Expr type = getTypeExpr();

    if (! (type instanceof TypeExpr)) {
      switch (getType()) {
      case TYPE_STRING:
        return String.class;

      case TYPE_INTEGER:
        return int.class;

      case TYPE_NUMBER:
        return double.class;

      case TYPE_BOOLEAN:
        return boolean.class;

      default:
        return ESBase.class;
      }
    }

    TypeExpr javaType = (TypeExpr) type;

    return javaType.getJavaClass();
  }

  boolean isSimple()
  {
    return false;
  }

  /**
   * True if the type of this expression is easily converted to a number.
   */
  boolean isNumeric()
  {
    int type = getType();
    
    return type >= TYPE_NUMBER && type <= TYPE_BOOLEAN;
  }

  boolean isNum()
  {
    int type = getType();
    
    return type == TYPE_NUMBER || type == TYPE_INTEGER;
  }

  /**
   * This expression will be used in a boolean context.
   */
  Expr setBoolean()
  {
    return new BooleanExpr(block, this);
  }
  
  Expr next(String iter, Expr lhs) throws ESException
  {
    return lhs.assign(new SpecialExpr(block, SpecialExpr.NEXT, iter));
  }

  /**
   * Gets the field of the current expr
   */
  Expr fieldReference(Expr expr)
  {
    return new FieldExpr(block, this, expr);
  }

  /**
   * Gets the field of the current expr
   */
  Expr fieldReference(ESId id)
    throws ESException
  {
    return new FieldExpr(block, this, new LiteralExpr(block, id));
  }
  
  /**
   * A unary op
   */
  Expr unaryOp(int op)
  {
    return new UnaryExpr(block, this, op);
  }
  
  /**
   * A unary op
   */
  Expr doVoid()
  {
    return new UnaryExpr(block, this, 'v');
  }
  
  /**
   * The typeof operator
   */
  Expr typeof()
  {
    return new UnaryExpr(block, this, 't');
  }
  
  /**
   * The delete operator
   */
  Expr delete()
    throws ESException
  {
    return BinaryExpr.create(block, this,
                             new LiteralExpr(block, ESBoolean.TRUE),
                             ',');
  }

  /**
   * The assignment operator
   */
  Expr assign(Expr value)
    throws ESException
  {
    throw error("illegal left-hand-side of assignment");
  }

  CallExpr startCall()
    throws ESException
  {
    return new CallExpr(block, this, null, false);
  }

  CallExpr startNew()
    throws ESException
  {
    return new CallExpr(block, this, null, true);
  }
  
  /**
   * Handle autoincrement
   */
  Expr prefix(int op)
    throws ESException
  {
    return unaryOp('+').binaryOp(op, op,
                                 new LiteralExpr(block, ESNumber.create(1.0)));
  }
  
  /**
   * Handle autoincrement
   */
  Expr postfix(int op)
  {
    return unaryOp('+');
  }
  
  /**
   * A binary op
   */
  Expr binaryOp(int lex, int op, Expr rexpr)
    throws ESException
  {
    setUsed();
    rexpr.setUsed();
    
    if (lex != '=') {
      switch (op) {
      case '<':
      case '>':
      case Lexer.LEQ:
      case Lexer.GEQ:
      case Lexer.EQ:
      case Lexer.NEQ:
      case Lexer.STRICT_EQ:
      case Lexer.STRICT_NEQ:
        return BooleanBinaryExpr.create(block, this, rexpr, op);
        
      case '+':
        return PlusExpr.create(block, this, rexpr);
        
      default:
        return BinaryExpr.create(block, this, rexpr, op);
      }
    }
    else if (op == '=')
      return assign(rexpr);

    else
      return assign(binaryOp(op, op, rexpr));
  }

  Expr cast(Expr castType)
    throws ESException
  {
    return CastExpr.create(block, this, (TypeExpr) castType);
  }
        
  /**
   * The conditional ? :  operation
   */
  Expr conditional(Expr mexpr, Expr rexpr)
  {
    return new ConditionalExpr(block, this, mexpr, rexpr);
  }

  void printExpr() throws IOException
  {
    print();
  }

  void print() throws IOException
  {
    if (ESBase.class.isAssignableFrom(getJavaClass()) ||
        this instanceof LiteralExpr) {
      printImpl();

      if (isTop)
        cl.println(";");
      return;
    }
    
    switch (getType()) {
    case TYPE_NUMBER:
      if (! noValue)
        cl.print("ESNumber.create(");
      printNumImpl();
      if (! noValue)
        cl.print(")");
      break;
      
    case TYPE_INTEGER:
      if (! noValue)
        cl.print("ESNumber.create(");
      printInt32Impl();
      if (! noValue)
        cl.print(")");
      break;
      
    case TYPE_BOOLEAN:
      if (! noValue)
        cl.print("(");
      printBooleanImpl();
      if (! noValue)
        cl.print("?ESBoolean.TRUE:ESBoolean.FALSE)");
      break;

    case TYPE_STRING:
      if (ESBase.class.isAssignableFrom(getJavaClass()))
        printImpl();
      else {
        if (! noValue)
          cl.print("ESString.create(");
        printStringImpl();
        if (! noValue)
          cl.print(")");
      }
      break;

    case TYPE_JAVA:
      if (! noValue)
        cl.print("_env.wrap(");
      printJavaImpl();
      if (! noValue)
        cl.print(")");
      break;

    case TYPE_VOID:
      if (! noValue)
        cl.print("_env.wrap(");
      printJavaImpl();
      if (! noValue)
        cl.print(")");
      break;

    default:
      printImpl();
    }

    if (isTop)
      cl.println(";");
  }

  void printBoolean() throws IOException
  {
    switch (getType()) {
    case TYPE_NUMBER:
      cl.print("(");
      printNumImpl();
      cl.print("!=0.0)");
      break;
      
    case TYPE_INTEGER:
      cl.print("(");
      printInt32Impl();
      cl.print("!=0)");
      break;
      
    case TYPE_BOOLEAN:
      printBooleanImpl();
      break;

    case TYPE_JAVA:
      cl.print("(");
      printJava();
      cl.print("!=null)");
      break;
      
    default:
      print();
      cl.print(".toBoolean()");
    }
    
    if (isTop)
      cl.println(";");
  }

  void printInt32() throws IOException
  {
    switch (getType()) {
    case TYPE_INTEGER:
      printInt32Impl();
      break;
      
    case TYPE_NUMBER:
      cl.print("((int)");
      printNumImpl();
      cl.print(")");
      break;
      
    case TYPE_BOOLEAN:
      cl.print("(");
      printBooleanImpl();
      cl.print("?1:0)");
      break;
      
    default:
      printImpl();
      cl.print(".toInt32()");
    }
  }
  
  void printInt64() throws IOException
  {
    printInt32();
  }

  void printNum() throws IOException
  {
    switch (getType()) {
    case TYPE_NUMBER:
      printNumImpl();
      break;
      
    case TYPE_INTEGER:
      cl.print("((double)");
      printInt32Impl();
      cl.print(")");
      break;
      
    case TYPE_BOOLEAN:
      cl.print("(");
      printBooleanImpl();
      cl.print("?1.0:0.0)");
      break;
      
    default:
      printImpl();
      cl.print(".toNum()");
    }
  }

  /**
   * Prints the expression as a java object.
   */
  void printJava() throws IOException
  {
    switch (getType()) {
    case TYPE_INTEGER:
      printInt32Impl();
      break;

    case TYPE_BOOLEAN:
      printBooleanImpl();
      break;

    case TYPE_STRING:
      printStringImpl();
      break;

    case TYPE_NUMBER:
      printNumImpl();
      break;

    case TYPE_JAVA:
      printJavaImpl();
      break;
      
    default:
      print();
      cl.print(".toJavaObject()");
      break;
    }
  }

  /**
   * Prints a string value
   */
  void printStr() throws IOException
  {
    print();
    cl.print(".toStr()");
  }

  void printJavaString() throws IOException
  {
    switch (getType()) {
    case TYPE_STRING:
      if (this instanceof LiteralExpr) {
        printStringImpl();
      }
      else {
        cl.print("String.valueOf(");
        printStringImpl();
        cl.print(")");
      }
      break;

    case TYPE_JAVA:
      if (getJavaClass().equals(String.class))
        printJavaImpl();
      else {
        cl.print("String.valueOf(");
        printJavaImpl();
        cl.print(")");
      }
      break;

      // JavaScript's double printing differs from Java's, so 
      // we need to convert to the JavaScript object.
    default:
      print();
      cl.print(".toStr().toString()");
      break;
    }
  }

  void printJavaClass(Class type)
    throws IOException
  {
    if (type.isArray()) {
      printJavaClass(type.getComponentType());
      cl.print("[]");
    }
    else
      cl.print(type.getName());
  }

  /**
   * Print where the result is a string.
   */
  void printString() throws IOException
  {
    switch (getType()) {
    case TYPE_INTEGER:
      printInt32Impl();
      break;

    case TYPE_BOOLEAN:
      printBooleanImpl();
      break;

    case TYPE_STRING:
      printStringImpl();
      break;

    case TYPE_JAVA:
      printJavaImpl();
      break;

      // JavaScript's double printing differs from Java's, so 
      // we need to convert to the JavaScript object.
    case TYPE_NUMBER:
    default:
      print();
      cl.print(".valueOf()");
      break;
    }
  }

  void printImpl() throws IOException
  {
    throw new RuntimeException("" + this);
  }

  void printBooleanImpl() throws IOException
  {
    throw new RuntimeException("" + this);
  }

  void printNumImpl() throws IOException
  {
    throw new RuntimeException("" + this);
  }

  void printInt32Impl() throws IOException
  {
    throw new RuntimeException("" + this);
  }

  void printInt64Impl() throws IOException
  {
    throw new RuntimeException("" + this);
  }

  void printStringImpl() throws IOException
  {
    throw new RuntimeException("no string impl for " + getClass());
  }

  void printJavaImpl() throws IOException
  {
    throw new RuntimeException("" + this);
  }

  void printLiteral(ESBase literal) throws IOException
  {
    cl.printLiteral(literal);
  }

  void exprStatement(Function fun) throws ESException
  {
  }

  private ESException error(String msg)
  {
    return block.error(msg);
  }
}
