//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\
// PLEASE DO NOT EDIT WITHOUT THE EXPLICIT CONSENT OF THE AUTHOR! \\
//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\

package ilog.language.design.kernel;

/**
 * @version     Last modified on Fri Oct 18 18:56:31 2002 by hak
 * @author      <a href="mailto:hak@ilog.fr">Hassan A&iuml;t-Kaci</a>
 * @copyright   &copy; 2000-2002 <a href="http://www.ilog.fr/">ILOG, S.A.</a>
 */

import ilog.language.design.types.*;
import ilog.language.design.base.*;

import java.util.ArrayList;

/**
 * This class represents functional applications <i>&agrave; la</i>
 * <greek>lambda</greek>-calculus. More precisely, it represents
 * multiple-argument applications. This enables an application to handle
 * many arguments without having to be "curryed" into a composition
 * of single-argument applications.
 */
public class Application extends ProtoExpression
{
  protected Expression _function;
  protected Expression[] _arguments;

  protected Expression _checkedFunction;
  protected Expression[] _checkedArguments;

  protected boolean _noCurrying = false;

  public Application (Expression function, Expression[] arguments)
    {
      _function = function;
      _arguments = arguments;
    }

  public Application (Expression function, Expression arg1, Expression arg2)
    {
      Expression[] arguments = { arg1, arg2 };
      _function = function;
      _arguments = arguments;
    }

  public Application (Expression function, Expression argument)
    {
      _function = function;
      _arguments = new Expression[1];
      _arguments[0] = argument;
    }

  public Application (Expression function, ArrayList arguments)
    {
      _function = function;
      if (!arguments.isEmpty())
        {
          _arguments = new Expression[arguments.size()];
          for (int i=_arguments.length; i-->0;)
            _arguments[i] = (Expression)arguments.get(i);
        }
      else
        {
          _arguments = new Expression[1];
          _arguments[0] = Constant.VOID();
        }      
    }

  public final boolean noCurrying ()
    {
      return _noCurrying;
    }

  public final Application setNoCurrying ()
    {
      return setNoCurrying(true);
    }

  public final Application setNoCurrying (boolean flag)
    {
      _noCurrying = flag;
      return this;
    }

  public final int numberOfSubexpressions ()
    {
      return _arguments.length+1;
    }

  public final Expression subexpression (int n) throws NoSuchSubexpressionException
    {
      if (n == 0)
        return _function;

      try
        {
          return _arguments[n-1];
        }
      catch (ArrayIndexOutOfBoundsException e)
        {
          throw new NoSuchSubexpressionException(this,n);
        }
    }

  public final Expression function ()
    {
      return _checkedFunction == null ? _function : _checkedFunction;
    }

  public final Expression[] arguments ()
    {
      return _arguments;
    }

  public final Expression argument (int n)
    {
      return _checkedArguments == null ? _arguments[n] : _checkedArguments[n];
    }

  public final void setFunction (Expression function)
    {
      _function = function;
    }

  public final void setArguments (Expression[] arguments)
    {
      _arguments = arguments;
    }

  public final int arity ()
    {
      return _arguments.length;
    }

  public final void setCheckedType ()
    {
      if (isSetCheckedType()) return;
      _function.setCheckedType();

      for (int i=_arguments.length; i-->0;)
        _arguments[i].setCheckedType();

      setCheckedType(type().copy());
    }

  public final void setCheckedType (Type type)
    {
      _checkedFunction = _function;
      _checkedArguments = _arguments;
      _checkedType = type;
    }

    public /*final*/ Expression sanitizeNames (ParameterStack parameters, ClassTypeHandle handle)
    {
      _function = _function.sanitizeNames(parameters,handle);

      for (int i=_arguments.length; i-->0;)
        _arguments[i] = _arguments[i].sanitizeNames(parameters,handle);
      
      return this;
    }

  public final void sanitizeSorts (Enclosure enclosure)
    {
      _function.sanitizeSorts(enclosure);
      
      for (int i=_arguments.length; i-->0;)
        _arguments[i].sanitizeSorts(enclosure);
    }

  public void typeCheck (TypeChecker typeChecker) throws TypingErrorException
    {
      if (typeCheckLocked()) return;

      Type[] argumentTypes = new Type[arity()];

      for (int i=arity(); i-->0;)
        {
          _arguments[i].typeCheck(typeChecker);
          argumentTypes[i] = _arguments[i].typeRef();
        }

      FunctionType functionType = new FunctionType(argumentTypes,_type).setNoCurrying(_noCurrying);
      _function.typeCheck(functionType,typeChecker);

      int arity = ((FunctionType)_function.type()).arity();

      if (arity < arity())
        _curryTypeCheck(arity,typeChecker);
    }
  
  protected final void _curryTypeCheck (int depth, TypeChecker typeChecker) throws TypingErrorException
    {
      Expression[] actualArguments = new Expression[depth];

      for (int i=depth; i-->0;)
        actualArguments[i] = _arguments[i];

      Expression[] remainingArguments = new Expression[arity()-depth];

      for (int i=remainingArguments.length; i-->0;)
        remainingArguments[i] = _arguments[i+depth];

      typeChecker.trail(this,_function,_arguments);
      _function = new Application(_function,actualArguments);
      _arguments = remainingArguments;

      _typeCheckLocked = false;
      typeCheck(typeChecker);
    }

  public final Expression shiftOffsets (int intShift, int realShift, int objectShift,
                                        int intDepth, int realDepth, int objectDepth)
    {
      _checkedFunction = _checkedFunction.shiftOffsets(intShift,realShift,objectShift,
                                                       intDepth,realDepth,objectDepth);

      for (int i=_checkedArguments.length; i-->0;)
        _checkedArguments[i].shiftOffsets(intShift,realShift,objectShift,
                                          intDepth,realDepth,objectDepth);

      return this;
    }

  protected final boolean _isFieldApplication ()
    {
      return (_checkedFunction instanceof Global
              && ((Global)_checkedFunction).definedEntry().isField());
    }
    
  public void compile (Compiler compiler)
    {
      FunctionType functionType = (FunctionType)_checkedFunction.checkedType();

      if (_compileBuiltIn(functionType,compiler))
        return;

      for (int i=_checkedArguments.length-1; i>=0; i--)
        _compileArgument(i,functionType,compiler);

      if (_isFieldApplication())
        {
          DefinedEntry entry = ((Global)_checkedFunction).definedEntry();
          compiler.generate(_getField(entry));

          if (arity() > 1)
            compiler.generate(new Apply(functionType).curryObject());
        }
      else
        {
          _checkedFunction.compile(compiler);
          compiler.generate(_apply(functionType));
        }

      _padResultIfNeeded(functionType,compiler);
    }

  protected Instruction _apply (FunctionType type)
    {
      return new Apply(type);
    }    

  protected final FieldInstruction _getField (DefinedEntry entry)
    {
      switch (entry.fieldSort())
        {
        case Type.INT_SORT:
          return new GetIntField(entry);
        case Type.REAL_SORT:
          return new GetRealField(entry);
        default:
          return new GetObjectField(entry);
        }
    }

  protected final boolean _compileBuiltIn (FunctionType functionType, Compiler compiler)
    {
      if (_checkedFunction instanceof Global)
        {
          CodeEntry entry = ((Global)_checkedFunction).checkedCodeEntry();

          if (entry.isBuiltIn())
            {
              BuiltinEntry builtinEntry = (BuiltinEntry)entry;
              Instruction builtin = builtinEntry.builtIn();
              FunctionType type = (FunctionType)(entry.type().copy());

              if (_checkedArguments.length == type.arity())
                {
                  if (builtin.isDummy())
                    return _compileDummyBuiltin(builtin,functionType,compiler);

                  for (int i=_checkedArguments.length-1; i>=0; i--)
                    {
                      _checkedArguments[i].compile(compiler);
                      _padArgumentIfNeeded(i,functionType,compiler);
                    }

                  compiler.generate(builtin);
                  _padResultIfNeeded(functionType,compiler);
                }
              else // necessarily: _checkedArguments.length < arity())
                _compileCurryedBuiltIn(functionType,compiler);

              return true;
            }
        }

      return false;
    }

  protected boolean _compileDummyBuiltin (Instruction instruction, FunctionType functionType,
                                          Compiler compiler)
    {
      if (instruction == Instruction.DUMMY_AND)
        {
          And and = new And(_checkedArguments[0],_checkedArguments[1]);
          and.setCheckedType(checkedType());
          and.compile(compiler);
          return true;
        }

      if (instruction == Instruction.DUMMY_OR)
        {
          Or or = new Or(_checkedArguments[0],_checkedArguments[1]);
          or.setCheckedType(checkedType());
          or.compile(compiler);
          return true;
        }

      if (instruction == Instruction.DUMMY_SIZE)
        {
          _checkedArguments[0].compile(compiler);
          if (((ArrayType)_checkedArguments[0].checkedType()).isMap())
            compiler.generate(Instruction.MAP_SIZE);
          else
            compiler.generate(Instruction.ARRAY_SIZE);
          return true;
        }

      if (instruction == Instruction.DUMMY_EQU || instruction == Instruction.DUMMY_NEQ)
        {
          _checkedArguments[1].compile(compiler);

          if (_checkedArguments[1].checkedType().isBoxedType())
            if (_checkedArguments[1].sort() == Type.INT_SORT
                || _checkedArguments[1].sort() == Type.REAL_SORT)
              compiler.generateUnwrapper(_checkedArguments[1].sort());
          
          _checkedArguments[0].compile(compiler);

          if (_checkedArguments[0].checkedType().isBoxedType())
            if (_checkedArguments[0].sort() == Type.INT_SORT
                || _checkedArguments[0].sort() == Type.REAL_SORT)
              compiler.generateUnwrapper(_checkedArguments[0].sort());

          if (instruction == Instruction.DUMMY_EQU)
            switch (_checkedArguments[0].sort())
              {
              case Type.INT_SORT:
                compiler.generate(Instruction.EQU_II);
                break;
              case Type.REAL_SORT:
                compiler.generate(Instruction.EQU_RR);
                break;
              default:
                compiler.generate(Instruction.EQU_OO);
              }
          else
            switch (_checkedArguments[0].sort())
              {
              case Type.INT_SORT:
                compiler.generate(Instruction.NEQ_II);
                break;
              case Type.REAL_SORT:
                compiler.generate(Instruction.NEQ_RR);
                break;
              default:
                compiler.generate(Instruction.NEQ_OO);
              }

          _padResultIfNeeded(functionType,compiler);
          return true;
        }

      if (instruction == Instruction.DUMMY_STRCON)
        {
          _checkedArguments[1].compile(compiler);
          _compileArgument(0,functionType,compiler);

          compiler.generate(new StringConcatenation(_checkedArguments[0].checkedType()));
          return true;
        }

      if (instruction == Instruction.DUMMY_WRITE)
        {
          _checkedArguments[0].compile(compiler);
          switch (_checkedArguments[0].boxSort())
            {
            case Type.INT_SORT:
              compiler.generate(new WriteInt(_checkedArguments[0].checkedType()));
              break;
            case Type.REAL_SORT:
              compiler.generate(Instruction.WRITE_R);
              break;
            default:
              compiler.generate(new WriteObject(_checkedArguments[0].checkedType()));
            }
          _padResultIfNeeded(functionType,compiler);
          return true;
        }

      if (instruction == Instruction.DUMMY_SET_ADD || instruction == Instruction.DUMMY_SET_RMV)
        {
          _checkedArguments[1].compile(compiler);
          _checkedArguments[0].compile(compiler);

          switch (_checkedArguments[0].boxSort())
            {
            case Type.INT_SORT:
              compiler.generate(instruction == Instruction.DUMMY_SET_ADD
                                ? Instruction.SET_ADD_I
                                : Instruction.SET_RMV_I);
              break;
            case Type.REAL_SORT:
              compiler.generate(instruction == Instruction.DUMMY_SET_ADD
                                ? Instruction.SET_ADD_R
                                : Instruction.SET_RMV_O);
              break;
            default:
              compiler.generate(instruction == Instruction.DUMMY_SET_ADD
                                ? Instruction.SET_ADD_O
                                : Instruction.SET_RMV_O);
            }
          return true;
        }

      if (instruction == Instruction.DUMMY_BELONGS)
        {
          _checkedArguments[1].compile(compiler);
          _checkedArguments[0].compile(compiler);

          switch (_checkedArguments[0].boxSort())
            {
            case Type.INT_SORT:
              compiler.generate(Instruction.BELONGS_I);
              break;
            case Type.REAL_SORT:
              compiler.generate(Instruction.BELONGS_R);
              break;
            default:
              compiler.generate(Instruction.BELONGS_O);
            }
          return true;
        }

      if (instruction == Instruction.DUMMY_FIRST)
        {
          _checkedArguments[0].compile(compiler);

          switch (((SetType)_checkedArguments[0].checkedType()).baseType().boxSort())
            {
            case Type.INT_SORT:
              compiler.generate(Instruction.FIRST_I);
              break;
            case Type.REAL_SORT:
              compiler.generate(Instruction.FIRST_R);
              break;
            default:
              compiler.generate(Instruction.FIRST_O);
            }
          return true;
        }

      if (instruction == Instruction.DUMMY_LAST)
        {
          _checkedArguments[0].compile(compiler);

          switch (((SetType)_checkedArguments[0].checkedType()).baseType().boxSort())
            {
            case Type.INT_SORT:
              compiler.generate(Instruction.LAST_I);
              break;
            case Type.REAL_SORT:
              compiler.generate(Instruction.LAST_R);
              break;
            default:
              compiler.generate(Instruction.LAST_O);
            }
          return true;
        }

      if (instruction == Instruction.DUMMY_ORD)
        {
          _checkedArguments[1].compile(compiler);
          _checkedArguments[0].compile(compiler);

          switch (_checkedArguments[1].boxSort())
            {
            case Type.INT_SORT:
              compiler.generate(Instruction.ORD_I);
              break;
            case Type.REAL_SORT:
              compiler.generate(Instruction.ORD_R);
              break;
            default:
              compiler.generate(Instruction.ORD_O);
            }
          return true;
        }

      if (instruction == Instruction.DUMMY_NEXT)
        {
          _checkedArguments[1].compile(compiler);
          _checkedArguments[0].compile(compiler);

          switch (_checkedArguments[1].boxSort())
            {
            case Type.INT_SORT:
              compiler.generate(Instruction.NEXT_I);
              break;
            case Type.REAL_SORT:
              compiler.generate(Instruction.NEXT_R);
              break;
            default:
              compiler.generate(Instruction.NEXT_O);
            }
          return true;
        }
        
      if (instruction == Instruction.DUMMY_NEXT_OFFSET)
        {
          _checkedArguments[2].compile(compiler);  
          _checkedArguments[1].compile(compiler);
          _checkedArguments[0].compile(compiler);

          switch (_checkedArguments[1].boxSort())
            {
            case Type.INT_SORT:
              compiler.generate(Instruction.NEXT_I_OFFSET);
              break;
            case Type.REAL_SORT:
              compiler.generate(Instruction.NEXT_R_OFFSET);
              break;
            default:
              compiler.generate(Instruction.NEXT_O_OFFSET);
            }
          return true;
        }

      if (instruction == Instruction.DUMMY_NEXT_C)
        {
          _checkedArguments[1].compile(compiler);
          _checkedArguments[0].compile(compiler);

          switch (_checkedArguments[1].boxSort())
            {
            case Type.INT_SORT:
              compiler.generate(Instruction.NEXT_C_I);
              break;
            case Type.REAL_SORT:
              compiler.generate(Instruction.NEXT_C_R);
              break;
            default:
              compiler.generate(Instruction.NEXT_C_O);
            }
          return true;
        }

      if (instruction == Instruction.DUMMY_NEXT_C_OFFSET)
        {
          _checkedArguments[2].compile(compiler);  
          _checkedArguments[1].compile(compiler);
          _checkedArguments[0].compile(compiler);

          switch (_checkedArguments[1].boxSort())
            {
            case Type.INT_SORT:
              compiler.generate(Instruction.NEXT_C_I_OFFSET);
              break;
            case Type.REAL_SORT:
              compiler.generate(Instruction.NEXT_C_R_OFFSET);
              break;
            default:
              compiler.generate(Instruction.NEXT_C_O_OFFSET);
            }
          return true;
        }

      if (instruction == Instruction.DUMMY_PREV)
        {
          _checkedArguments[1].compile(compiler);
          _checkedArguments[0].compile(compiler);

          switch (_checkedArguments[1].boxSort())
            {
            case Type.INT_SORT:
              compiler.generate(Instruction.PREV_I);
              break;
            case Type.REAL_SORT:
              compiler.generate(Instruction.PREV_R);
              break;
            default:
              compiler.generate(Instruction.PREV_O);
            }
          return true;
        }

      if (instruction == Instruction.DUMMY_PREV_OFFSET)
        {
          _checkedArguments[2].compile(compiler);
          _checkedArguments[1].compile(compiler);            
          _checkedArguments[0].compile(compiler);

          switch (_checkedArguments[1].boxSort())
            {
            case Type.INT_SORT:
              compiler.generate(Instruction.PREV_I_OFFSET);
              break;
            case Type.REAL_SORT:
              compiler.generate(Instruction.PREV_R_OFFSET);
              break;
            default:
              compiler.generate(Instruction.PREV_O_OFFSET);
            }
          return true;
        }

      if (instruction == Instruction.DUMMY_PREV_C)
        {
          _checkedArguments[1].compile(compiler);
          _checkedArguments[0].compile(compiler);

          switch (_checkedArguments[1].boxSort())
            {
            case Type.INT_SORT:
              compiler.generate(Instruction.PREV_C_I);
              break;
            case Type.REAL_SORT:
              compiler.generate(Instruction.PREV_C_R);
              break;
            default:
              compiler.generate(Instruction.PREV_C_O);
            }
          return true;
        }

      if (instruction == Instruction.DUMMY_PREV_C_OFFSET)
        {
          _checkedArguments[2].compile(compiler);
          _checkedArguments[1].compile(compiler);            
          _checkedArguments[0].compile(compiler);

          switch (_checkedArguments[1].boxSort())
            {
            case Type.INT_SORT:
              compiler.generate(Instruction.PREV_C_I_OFFSET);
              break;
            case Type.REAL_SORT:
              compiler.generate(Instruction.PREV_C_R_OFFSET);
              break;
            default:
              compiler.generate(Instruction.PREV_C_O_OFFSET);
            }
          return true;
        }
        

      return true;
    }

  protected final void _compileArgument (int i, FunctionType functionType, Compiler compiler)
    {
      if (functionType.domain(i).kind() == Type.FUNCTION)
        _checkedArguments[i].pad((FunctionType)functionType.domain(i)).compile(compiler);
      else
        _checkedArguments[i].compile(compiler);

      _padArgumentIfNeeded(i,functionType,compiler);
    }

  protected final void _padArgumentIfNeeded (int i, FunctionType functionType, Compiler compiler)
    {
      if (functionType.domainIsBoxed(i))
        {
          if (!_checkedArguments[i].checkedType().isBoxedType())
            compiler.generateWrapper(_checkedArguments[i].sort());
        }
      else
        if (_checkedArguments[i].checkedType().isBoxedType())
          compiler.generateUnwrapper(_checkedArguments[i].sort());       
    }

  protected final void _padResultIfNeeded (FunctionType functionType, Compiler compiler)
    {
      if (functionType.rangeIsBoxed())
        {
          if (!_checkedType.isBoxedType())
            compiler.generateUnwrapper(sort());
        }
      else
        if (_checkedType.isBoxedType())
          compiler.generateWrapper(sort());
    }

  protected final void _compileCurryedBuiltIn (FunctionType curryedType, Compiler compiler)
    {
      FunctionType curryedRange = (FunctionType)curryedType.range();
      FunctionType uncurryedType = curryedType.uncurry();

      int actualArity   = curryedType.arity();
      int missingArity  = curryedRange.arity();
      int expectedArity = actualArity + missingArity;

      Parameter [] dummyParameters = new Parameter [missingArity];
      Local     [] dummyArguments  = new Local     [missingArity];
      Expression[] newArguments    = new Expression[expectedArity];

      for (int i=0; i<missingArity; i++)
        {
          dummyParameters[i] = new Parameter();
          dummyParameters[i].setCheckedType(curryedRange.domain(i));
          dummyArguments[i] = new Local(dummyParameters[i]);
        }

      ((Global)_checkedFunction).resetCheckedType(uncurryedType);
      Application application = new Application(_checkedFunction,newArguments);
      application.setCheckedType(curryedRange.range());
      Abstraction abstraction = new Abstraction(dummyParameters,application);
      abstraction.setNonExitable();
      abstraction.setSortedArities();

      int intArity = abstraction.intArity();
      int realArity = abstraction.realArity();
      int objectArity = abstraction.objectArity();
      int ia = 0, ra = 0, oa = 0;

      for (int i=0; i<missingArity; i++)
        {
          switch (dummyParameters[i].boxSort())
            {
            case Type.INT_SORT:
              dummyArguments[i].setOffset(intArity-1-(ia++));
              break;
            case Type.REAL_SORT:
              dummyArguments[i].setOffset(realArity-1-(ra++));
              break;
            default:
              dummyArguments[i].setOffset(objectArity-1-(oa++));
            }

          newArguments[actualArity+i] = dummyArguments[i];
        }
      
      for (int i=0; i<actualArity; i++)
        newArguments[i] = _checkedArguments[i].shiftOffsets(ia,ra,oa);

      abstraction.compile(compiler);
    }

  public String toString ()
    {
      String s = _function + "(";

      for (int i=0; i<arity(); i++)
        {
          s += _arguments[i];
          if (i < arity()-1) s += ",";
        }

      return s + ")";      
    }

  // Added by PV
  public String toTypedString ()
    {
      String s = _function.toTypedString() + "(";

      for (int i=0; i<arity(); i++)
        {
          s += _arguments[i].toTypedString();
          if (i < arity()-1) s += ",";
        }

      return typed(s + ")");      
    }

  public final Application methodApplication (Expression object)
    {
      Expression[] newArgs = new Expression[arity()+1];

      for (int i=arity(); i-->1;)
        newArgs[i] = _arguments[i];

      newArgs[0] = object;

      return new Application(_function,newArgs);
    }

}
