/*****
 * application.cc
 * Andy Hammerlindl 2005/05/20
 *
 * An application is a matching of arguments in a call expression to formal
 * parameters of a function.  Since the language allows default arguments,
 * keyword arguments, rest arguments, and anything else we think of, this
 * is not a simple mapping.
 *****/

#include "application.h"
#include "exp.h"
#include "coenv.h"
#include "runtime.h"
#include "runarray.h"
#if defined(MIKTEX)
#include <functional>
#endif

using namespace types;
using absyntax::varinit;
using absyntax::arrayinit;
using absyntax::arglist;

namespace trans {

// Lower scores are better.  Packed is added onto the other qualifiers so
// we may score both exact and casted packed arguments.
const score FAIL=0, EXACT=1, CAST=2;
const score PACKED=2;

bool castable(env &e, formal& target, formal& source) {
  return target.Explicit ? equivalent(target.t,source.t)
    : e.castable(target.t,source.t, symbol::castsym);
}

score castScore(env &e, formal& target, formal& source) {
  return equivalent(target.t,source.t) ? EXACT :
    (!target.Explicit &&
     e.fastCastable(target.t,source.t)) ? CAST : FAIL;
}


void restArg::transMaker(coenv &e, Int size, bool rest) {
  // Push the number of cells and call the array maker.
  e.c.encode(inst::intpush, size);
  e.c.encode(inst::builtin, rest ? run::newAppendedArray :
             run::newInitializedArray);
}

void restArg::trans(coenv &e, temp_vector &temps)
{
  // Push the values on the stack.
  for (mem::list<arg *>::iterator p = inits.begin(); p != inits.end(); ++p)
    (*p)->trans(e, temps);

  if (rest)
    rest->trans(e, temps);

  transMaker(e, (Int)inits.size(), (bool)rest);
}

class maximizer {
  app_list l;

  // Tests if x is as good (or better) an application as y.
  bool asgood(application *x, application *y) {
    // Matches to open signatures are always worse than matches to normal
    // signatures.
    if (x->sig->isOpen)
      return y->sig->isOpen;
    else if (y->sig->isOpen)
      return true;

    assert (x->scores.size() == y->scores.size());

    // Test if each score in x is no higher than the corresponding score in
    // y.
    return std::equal(x->scores.begin(), x->scores.end(), y->scores.begin(),
                      std::less_equal<score>());
  }

  bool better(application *x, application *y) {
    return asgood(x,y) && !asgood(y,x);
  }

  // Add an application that has already been determined to be maximal.
  // Remove any other applications that are now not maximal because of its
  // addition.
  void addMaximal(application *x) {
    app_list::iterator y=l.begin();
    while (y!=l.end())
      if (better(x,*y))
        y=l.erase(y);
      else
        ++y;
    l.push_front(x);
  }

  // Tests if x is maximal.
  bool maximal(application *x) {
    for (app_list::iterator y=l.begin(); y!=l.end(); ++y)
      if (better(*y,x))
        return false;
    return true;
  }

public:
  maximizer() {}

  void add(application *x) {
    if (maximal(x))
      addMaximal(x);
  }

  app_list result() {
    return l;
  }
};

ty *restCellType(signature *sig) {
  formal& f=sig->getRest();
  if (f.t) {
    array *a=dynamic_cast<array *>(f.t);
    if (a)
      return a->celltype;
  }

  return 0;
}

void application::initRest() {
  formal& f=sig->getRest();
  if (f.t) {
    ty *ct = restCellType(sig);
    if (!ct)
      vm::error("formal rest argument must be an array");

    rf=formal(ct, symbol::nullsym, false, f.Explicit);
  }
  if (f.t || sig->isOpen) {
    rest=new restArg();
  }
}

//const Int REST=-1;
const Int NOMATCH=-2;

Int application::find(symbol name) {
  formal_vector &f=sig->formals;
  for (size_t i=index; i<f.size(); ++i)
    if (f[i].name==name && args[i]==0)
      return (Int)i;
  return NOMATCH;
}

bool application::matchDefault() {
  if (index==args.size())
    return false;
  else {
    formal &target=getTarget();
    if (target.defval) {
      args[index]=new defaultArg(target.t);
      advanceIndex();
      return true;
    }
    else
      return false;
  }
}

bool application::matchArgumentToRest(env &e, formal &source,
                                      varinit *a, size_t evalIndex)
{
  if (rest) {
    score s=castScore(e, rf, source);
    if (s!=FAIL) {
      rest->add(seq.addArg(a, rf.t, evalIndex));
      scores.push_back(s+PACKED);
      return true;
    }
  }
  return false;
}

bool application::matchAtSpot(size_t spot, env &e, formal &source,
                              varinit *a, size_t evalIndex)
{
  formal &target=sig->getFormal(spot);
  if(target.t->kind == types::ty_error) return false;

  score s=castScore(e, target, source);

  if (s == FAIL)
    return false;
  else if (sig->formalIsKeywordOnly(spot) && source.name == symbol::nullsym)
    return false;
  else {
    // The argument matches.
    args[spot]=seq.addArg(a, target.t, evalIndex);
    if (spot==index)
      advanceIndex();
    scores.push_back(s);
    return true;
  }
}

bool application::matchArgument(env &e, formal &source,
                                varinit *a, size_t evalIndex)
{
  assert(!source.name);

  if (index==args.size())
    // Try to pack into the rest array.
    return matchArgumentToRest(e, source, a, evalIndex);
  else
    // Match here, or failing that use a default and try to match at the next
    // spot.
    return matchAtSpot(index, e, source, a, evalIndex) ||
      (matchDefault() && matchArgument(e, source, a, evalIndex));
}

bool application::matchNamedArgument(env &e, formal &source,
                                     varinit *a, size_t evalIndex)
{
  assert(source.name);

  Int spot=find(source.name);
  return spot!=NOMATCH && matchAtSpot(spot, e, source, a, evalIndex);
}

bool application::complete() {
  if (index==args.size())
    return true;
  else if (matchDefault())
    return complete();
  else
    return false;
}

bool application::matchRest(env &e, formal &source, varinit *a,
                            size_t evalIndex) {
  // First make sure all non-rest arguments are matched (matching to defaults
  // if necessary).
  if (complete())
    // Match rest to rest.
    if (rest) {
      formal &target=sig->getRest();
      score s=castScore(e, target, source);
      if (s!=FAIL) {
        rest->addRest(seq.addArg(a, target.t, evalIndex));
        scores.push_back(s);
        return true;
      }
    }
  return false;
}

// When the argument should be evaluated, possibly adjusting for a rest
// argument which occurs before named arguments.
size_t adjustIndex(size_t i, size_t ri)
{
  return i < ri ? i : i+1;
}

bool application::matchSignature(env &e, types::signature *source,
                                 arglist &al) {
  formal_vector &f=source->formals;

#if 0
  cout << "num args: " << f.size() << endl;
  cout << "num keyword-only: " << sig->numKeywordOnly << endl;
#endif

  size_t ri = al.rest.val ? al.restPosition : f.size();

  // First, match all of the named (non-rest) arguments.
  for (size_t i=0; i<f.size(); ++i)
    if (f[i].name)
      if (!matchNamedArgument(e, f[i], al[i].val, adjustIndex(i,ri)))
        return false;

  // Then, the unnamed.
  for (size_t i=0; i<f.size(); ++i)
    if (!f[i].name)
      if (!matchArgument(e, f[i], al[i].val, adjustIndex(i,ri)))
        return false;

  // Then, the rest argument.
  if (source->hasRest())
    if (!matchRest(e, source->getRest(), al.rest.val, ri))
      return false;

  // Fill in any remaining arguments with their defaults.
  return complete();
}

bool application::matchOpen(env &e, signature *source, arglist &al) {
  assert(rest);

  // Pack all given parameters into the rest argument.
  formal_vector &f=source->formals;
  for (size_t i = 0; i < f.size(); ++i)
    if (al[i].name)
      // Named arguments are not handled by open signatures.
      return false;
    else
      rest->add(seq.addArg(al[i].val, f[i].t, i));

  if (source->hasRest())
    rest->addRest(new varinitArg(al.rest.val, source->getRest().t));

  return true;
}

application *application::match(env &e, function *t, signature *source,
                                arglist &al) {
  assert(t->kind==ty_function);
  application *app=new application(t);

  bool success = t->getSignature()->isOpen ?
    app->matchOpen(e, source, al) :
    app->matchSignature(e, source, al);

  //cout << "MATCH " << success << endl;

  return success ? app : 0;
}

void application::transArgs(coenv &e) {
  temp_vector temps;

  for(arg_vector::iterator a=args.begin(); a != args.end(); ++a)
    (*a)->trans(e,temps);

  if (rest)
    rest->trans(e,temps);
}

bool application::exact() {
  if (sig->isOpen)
    return false;
  for (score_vector::iterator p = scores.begin(); p != scores.end(); ++p)
    if (*p != EXACT)
      return false;
  return true;
}

bool application::halfExact() {
  if (sig->isOpen)
    return false;
  if (scores.size() != 2)
    return false;
  if (scores[0] == EXACT && scores[1] == CAST)
    return true;
  if (scores[0] == CAST && scores[1] == EXACT)
    return true;
  return false;
}

// True if any of the formals have names.
bool namedFormals(signature *sig)
{
  formal_vector& formals = sig->formals;
  size_t n = formals.size();
  for (size_t i = 0; i < n; ++i) {
    if (formals[i].name)
      return true;
  }
  return false;
}

// Tests if arguments in the source signature can be matched to the formals
// in the target signature with no casting or packing.
// This allows overloaded args, but not named args.
bool exactMightMatch(signature *target, signature *source)
{
  // Open signatures never exactly match.
  if (target->isOpen)
    return false;

#if 0
  assert(!namedFormals(source));
#endif

  formal_vector& formals = target->formals;
  formal_vector& args = source->formals;

  // Sizes of the two lists.
  size_t fn = formals.size(), an = args.size();

  // Indices for the two lists.
  size_t fi = 0, ai = 0;

  while (fi < fn && ai < an) {
    if (equivalent(formals[fi].t, args[ai].t)) {
      // Arguments match, move to the next.
      ++fi; ++ai;
    } else if (formals[fi].defval) {
      // Match formal to default value.
      ++fi;
    } else {
      // Failed to match formal.
      return false;
    }
  }

  assert(fi == fn || ai == an);

  // Packing array arguments into the rest formal is inexact.  Do not allow it
  // here.
  if (ai < an)
    return false;

  assert(ai == an);

  // Match any remaining formal to defaults.
  while (fi < fn)
    if (formals[fi].defval) {
      // Match formal to default value.
      ++fi;
    } else {
      // Failed to match formal.
      return false;
    }

  // Non-rest arguments have matched.
  assert(fi == fn && ai == an);

  // Try to match the rest argument if given.
  if (source->hasRest()) {
    if (!target->hasRest())
      return false;

    if (!equivalent(source->getRest().t, target->getRest().t))
      return false;
  }

  // All arguments have matched.
  return true;
}

// Tries to match applications without casting.  If an application matches
// here, we need not attempt to match others with the slower, more general
// techniques.
app_list exactMultimatch(env &e,
                         types::overloaded *o,
                         types::signature *source,
                         arglist &al)
{
  assert(source);

  app_list l;

  // This can't handle named arguments.
  if (namedFormals(source))
    return l; /* empty */

  for (ty_vector::iterator t=o->sub.begin(); t!=o->sub.end(); ++t)
    {
      if ((*t)->kind != ty_function)
        continue;

      function *ft = (function *)*t;

      // First we run a test to see if all arguments could be exactly matched.
      // If this returns false, no such match is possible.
      // If it returns true, an exact match may or may not be possible.
      if (!exactMightMatch(ft->getSignature(), source))
        continue;

      application *a=application::match(e, ft, source, al);

      // Consider calling
      //   void f(A a=new A, int y)
      // with
      //   f(3)
      // This matches exactly if there is no implicit cast from int to A.
      // Otherwise, it does not match.
      // Thus, there is no way to know if the
      // match truly is exact without looking at the environment.
      // In such a case, exactMightMatch() must return true, but there is no
      // exact match.  Such false positives are eliminated here.
      //
      // Consider calling
      //   void f(int x, real y=0.0, int z=0)
      // with
      //   f(1,2)
      // exactMightMatch() will return true, matching 1 to x and 2 to z, but the
      // application::match will give an inexact match of 1 to x to 2 to y, due
      // to the cast from int to real.  Therefore, we must test for exactness
      // even after matching.
      if (a && a->exact())
        l.push_back(a);
    }

  //cout << "EXACTMATCH " << (!l.empty()) << endl;
  return l;
}

bool halfExactMightMatch(env &e,
                         signature *target, types::ty *t1, types::ty *t2)
{
  formal_vector& formals = target->formals;
  if (formals.size() < 2)
    return false;
  if (formals.size() > 2) {
    // We should probably abort the whole matching in this case.  For now,
    // return true and let the usual matching handle it.
    return true;
  }

  assert(formals[0].t);
  assert(formals[1].t);

  // These casting tests if successful will be repeated again by
  // application::match.  It would be nice to avoid this somehow, but the
  // additional complexity is probably not worth the minor speed improvement.
  if (equivalent(formals[0].t, t1))
    return e.fastCastable(formals[1].t, t2);
  else
    return equivalent(formals[1].t, t2) && e.fastCastable(formals[0].t, t1);
}

// Most common after exact matches are cases such as
//   2 + 3.4   (int, real) --> (real, real)
// that is, binary operations where one of the operands matches exactly and the
// other does not.  This function searches for these so-called "half-exact"
// matches.  This should only be called after exactMultimatch has failed.
app_list halfExactMultimatch(env &e,
                             types::overloaded *o,
                             types::signature *source,
                             arglist &al)
{
  assert(source);

  app_list l;


  // Half exact is only in the case of two arguments.
  formal_vector& formals = source->formals;
  if (formals.size() != 2 || source->hasRest())
    return l; /* empty */

  // This can't handle named arguments.
  if (namedFormals(source))
    return l; /* empty */

  // Alias the two argument types.
  types::ty *t1 = formals[0].t;
  types::ty *t2 = formals[1].t;

  assert(t1); assert(t2);

  for (ty_vector::iterator t=o->sub.begin(); t!=o->sub.end(); ++t)
    {
      if ((*t)->kind != ty_function)
        continue;

      function *ft = (function *)*t;

#if 1
      if (!halfExactMightMatch(e, ft->getSignature(), t1, t2))
        continue;
#endif

      application *a=application::match(e, ft, source, al);

#if 1
      if (a && a->halfExact())
        l.push_back(a);
#endif
    }

  return l;
}

// Simple check if there are too many arguments to match the candidate
// function.
// A "tooFewArgs" variant was also implemented at some point, but did
// not give any speed-up.
bool tooManyArgs(types::signature *target, types::signature *source) {
  return source->getNumFormals() > target->getNumFormals() &&
    !target->hasRest();
}

// The full overloading resolution system, which handles casting of arguments,
// packing into rest arguments, named arguments, etc.
app_list inexactMultimatch(env &e,
                           types::overloaded *o,
                           types::signature *source,
                           arglist &al)
{
  assert(source);

  app_list l;


#define DEBUG_GETAPP 0
#if DEBUG_GETAPP
  //cout << "source: " << *source << endl;
  //cout << "ARGS: " << source->getNumFormals() << endl;
  bool perfect=false;
  bool exact=false;
  bool halfExact=false;
#endif

  for(ty_vector::iterator t=o->sub.begin(); t!=o->sub.end(); ++t) {
    if ((*t)->kind==ty_function) {
#if DEBUG_GETAPP
      function *ft = dynamic_cast<function *>(*t);
      signature *target = ft->getSignature();
      if (equivalent(target, source))
        perfect = true;
#endif

      // Check if there are two many arguments to match.
      if (tooManyArgs((*t)->getSignature(), source))
        continue;

      application *a=application::match(e, (function *)(*t), source, al);
      if (a)
        l.push_back(a);

#if DEBUG_GETAPP
      if (a && !namedFormals(source)) {
        assert(a->exact() == exactlyMatchable(ft->getSignature(), source));
        if (a->halfExact() && !namedFormals(source)) {
          assert(halfExactMightMatch(e, target, source->getFormal(0).t,
                                     source->getFormal(1).t));
        }

      }
      if (a && a->exact())
        exact = true;
      if (a && a->halfExact())
        halfExact = true;
#endif
    }
  }

#if DEBUG_GETAPP
  cout << (perfect     ? "PERFECT" :
           exact       ? "EXACT" :
           halfExact   ? "HALFEXACT" :
           "IMPERFECT")
       << endl;
#endif

  if (l.size() > 1) {
    // Return the most specific candidates.
    maximizer m;
    for (app_list::iterator x=l.begin(); x!=l.end(); ++x) {
      assert(*x);
      m.add(*x);
    }
    return m.result();
  }
  else
    return l;
}

enum testExactType {
  TEST_EXACT,
  DONT_TEST_EXACT,
};

// Sanity check for multimatch optimizations.
void sameApplications(app_list a, app_list b, testExactType te) {
  assert(a.size() == b.size());

  if (te == TEST_EXACT) {
    for (app_list::iterator i = a.begin(); i != a.end(); ++i) {
      if (!(*i)->exact()) {
        cout << *(*i)->getType() << endl;
      }
      assert((*i)->exact());
    }
    for (app_list::iterator i = b.begin(); i != b.end(); ++i)
      assert((*i)->exact());
  }

  if (a.size() == 1)
    assert(equivalent(a.front()->getType(), b.front()->getType()));
}

app_list multimatch(env &e,
                    types::overloaded *o,
                    types::signature *source,
                    arglist &al)
{
  app_list a = exactMultimatch(e, o, source, al);
  if (!a.empty()) {
#if DEBUG_CACHE
    // Make sure that exactMultimatch and the fallback return the same
    // application(s).
    sameApplications(a, inexactMultimatch(e, o, source, al), TEST_EXACT);
#endif

    return a;
  }

  a = halfExactMultimatch(e, o, source, al);
  if (!a.empty()) {
#if DEBUG_CACHE
    sameApplications(a, inexactMultimatch(e, o, source, al), DONT_TEST_EXACT);
#endif

    return a;
  }

  // Slow but most general method.
  return inexactMultimatch(e, o, source, al);
}

} // namespace trans
