package gnu.expr;
import gnu.mapping.*;
import gnu.bytecode.*;
import gnu.kawa.reflect.Invoke;

public class InlineCalls extends ExpWalker
{
  public static void inlineCalls (Expression exp, Compilation comp)
  {
    InlineCalls walker = new InlineCalls();
    walker.setContext(comp);
    walker.walk(exp);
  }

  /** Possibly convert a Symbol method call to invokeStatic or make. */
  Expression rewriteToInvocation(Symbol sym, ApplyExp aexp)
  {
    Expression[] args = aexp.args;
    String uri = sym.getNamespaceURI();
    if (uri == null || ! uri.startsWith("class:"))
      return null;
    String className = uri.substring(6);
    String methodName = sym.getName();
    ClassType typeInvoke = ClassType.make("gnu.kawa.reflect.Invoke");
    String invFieldName;
    Invoke invProc;
    boolean isNew = methodName.equals("new");
    if (isNew)
      {
	invFieldName = "make";
	invProc = gnu.kawa.reflect.Invoke.make;
      }
    else
      {
	invFieldName = "invokeStatic";
	invProc = gnu.kawa.reflect.Invoke.invokeStatic;
      }
    Field invField = typeInvoke.getDeclaredField(invFieldName);
    Declaration invDecl = new Declaration("invoke", invField);
    invDecl.noteValue(new QuoteExp(invProc));
    invDecl.setFlag(Declaration.IS_CONSTANT);
    Expression[] xargs = new Expression[args.length + (isNew ? 1 : 2)];
    System.arraycopy(args, 0, xargs, (isNew ? 1 : 2), args.length);
    xargs[0] = new QuoteExp(className);
    if (! isNew)
      xargs[1] = new QuoteExp(methodName);
    args = xargs;
    ApplyExp nexp = new ApplyExp(new ReferenceExp(invDecl), args);
    nexp.setLine(aexp);
    return invProc.inline(nexp, this);
  }

  protected Expression walkApplyExp(ApplyExp exp)
  {
    super.walkApplyExp(exp);
    LambdaExp lambda = null;
    int nargs = exp.getArgCount();
    if (exp.func instanceof LambdaExp)
      lambda = (LambdaExp) exp.func;
    Expression func = exp.func;
    Declaration decl = null;
    if (func instanceof ReferenceExp)
      {
	ReferenceExp rexp = (ReferenceExp) func;
        decl = rexp.binding;
        if (decl != null && ! decl.getFlag(Declaration.IS_UNKNOWN))
	  {
	    decl = Declaration.followAliases(decl);
	    if (decl.isIndirectBinding())
	      return exp;
            func = decl.getValue();
	    if (func instanceof LambdaExp) 
	      lambda = (LambdaExp) func;
	  }
	else if (rexp.getSymbol() instanceof Symbol)
	  {
	    Symbol symbol = (Symbol) rexp.getSymbol();
	    Expression inv = rewriteToInvocation(symbol, exp);
	    if (inv != null)
	      return inv;
	    Object fval = comp.getEnvironment().getFunction(symbol, null);
	    if (fval instanceof Procedure)
	      func = new QuoteExp(fval);
	    decl = null;
	  }
      }
    if (func instanceof QuoteExp && func != QuoteExp.undefined_exp)
      {
	Object fval = ((QuoteExp) func).getValue();
	if (! (fval instanceof Procedure))
	  return noteError(decl == null || fval == null ? "called value is not a procedure"
			   : ("calling " + decl.getName()
			      + " which is a "+fval.getClass().getName()));
	Procedure proc = (Procedure) fval;
	String msg = WrongArguments.checkArgCount(proc, nargs);
	if (msg != null)
	  return noteError(msg);
	if (proc instanceof CanInline)
	  return ((CanInline) proc).inline(exp, this);
	if (exp.getFlag(ApplyExp.INLINE_IF_CONSTANT))
	  {
	    Expression e = exp.inlineIfConstant(proc, this);
	    if (e != exp)
	      return walk(e);
	  }
	if (comp.inlineOk(proc))
	  {
	    if (proc instanceof Inlineable)
	      return new ApplyExp(proc, exp.getArgs());
	    PrimProcedure mproc
	      = PrimProcedure.getMethodFor(proc, decl, exp.args,
					   comp.getLanguage());
	    if (mproc != null)
	      {
		ApplyExp nexp;
		if (mproc.getStaticFlag() || decl == null)
		  nexp = new ApplyExp(mproc, exp.args);
		else if (decl.base == null)
		  return exp;
		else
		  {
		    Expression[] margs = new Expression[1 + nargs];
		    System.arraycopy(exp.getArgs(), 0, margs, 1, nargs);
		    margs[0] = new ReferenceExp(decl.base);
		    nexp = new ApplyExp(mproc, margs);
		  }
		return nexp.setLine(exp);
	      }
	  }
      }
    if (lambda != null)
      {
	int args_length = exp.args.length;
	String msg = WrongArguments.checkArgCount(lambda.getName(),
						  lambda.min_args,
						  lambda.max_args,
						  args_length);
	if (msg != null)
	  return noteError(msg);
      }
    return exp;
  }

  protected Expression walkIfExp (IfExp exp)
  {
    exp.walkChildren(this);
    Expression test = exp.test;
    if (test instanceof QuoteExp)
      {
	Language language = comp.getLanguage();
	if (language.isTrue(((QuoteExp) test).getValue()))
	  return exp.then_clause;
	else
	  return exp.else_clause == null ? QuoteExp.voidExp : exp.else_clause;
      }
    return exp;
  }

  protected Expression walkLetExp (LetExp exp)
  {
    Declaration decl = exp.firstDecl();
    for (int i = 0; i < exp.inits.length; i++, decl = decl.nextDecl())
      {
	Expression init0 = exp.inits[i];
	Expression init = walk(init0);
	exp.inits[i] = init;
	Expression dvalue = decl.value;
	if (dvalue == init0)
	  {
	    decl.value = dvalue = init;
	    if (! decl.getFlag(Declaration.TYPE_SPECIFIED))
	      decl.setType(dvalue.getType());
	  }
      }

    if (exitValue == null)
      exp.body = (Expression) walk(exp.body);
    return exp;
  }

  protected Expression walkSetExp (SetExp exp)
  {
    Declaration decl = exp.binding;
    boolean updateNeeded = false;
    if (decl != null)
      {
        Expression declValue = decl.getValue();
        if (declValue == exp.new_value)
          updateNeeded = true;
      }
    exp.walkChildren(this);
    if (updateNeeded)
      {
        decl.value = exp.new_value;
        if (exp.new_value instanceof LambdaExp)
          ((LambdaExp) exp.new_value).nameDecl = decl;
      }
    /*
    if (decl != null && ! decl.getFlag(Declaration.TYPE_SPECIFIED))
      {
	// This is a kludge to handle the a #!rest parameter that
	// is implicitly declared to be a Scheme <list>, but may be
	// assinged some other value, which is a legal Scheme idiom.
	// We could set implicitly set the parameter type to <list>,
	// but doing so improves type inference in the common case.
	Type declType = decl.getType();
	if (declType != null && ! exp.new_value.getType().isSubtype(declType))
	  decl.setType(Type.pointer_type);
      }
    */
    return exp;
  }

  protected Expression walkClassExp (ClassExp exp)
  {
    // Give name to object class.
    exp.setParts(this, comp);
    comp.addClass(exp.type);
    if (exp.isMakingClassPair())
      {
	exp.instanceType.setName(exp.type.getName()+"$class");
	comp.addClass(exp.instanceType);
      }
    return super.walkClassExp(exp);
  }
}
