#include <cstring>

#include "stack.h"
#include "env.h"
#include "exp.h"
#include "stm.h"
#include "refaccess.h"

using std::strlen;

using namespace absyntax;
using namespace trans;

using vm::item;
using vm::get;


#include "policy.h"

coenv &coenvInOngoingProcess();
void runInOngoingProcess(absyntax::runnable *r);

void runExp(absyntax::exp *e)
{
  absyntax::expStm s(nullPos, e);
  runInOngoingProcess(&s);
}

class ImpDatum;
class ImpArguments;

ImpDatum *datumError(const char *msg);

// Expression used for non-item datums.
class errorExp : public absyntax::exp {
public:
  errorExp() : exp(nullPos) {}

  void prettyprint(ostream &out, Int indent)
  {
    absyntax::prettyname(out, "errorExp", indent, getPos());
  }

  void complain() {
    em.error(nullPos); em << "cannot use datum as expression";
  }

  types::ty *getType(coenv &) { return types::primError(); }
  types::ty *trans(coenv &e) { complain(); return getType(e); }

  void transAsType(coenv &e, types::ty *target) { complain(); }
};

// Abstract base class for Datum types.
class ImpDatum {
public:
  virtual operator handle_typ() { return (handle_typ)(this); }

  virtual int_typ toInt() {
    datumError("cannot convert to integer");

    // Return a weird value that will hopefully be noticed.
    return -777777;
  }

  virtual bool toBool() {
    datumError("cannot convert to bool");
    return false;
  }

  virtual double toDouble() {
    datumError("cannot convert to double");
    return -777e77;
  }

  virtual string_typ toString() {
    datumError("cannot convert to string");

    string_typ s = { "XXXXX", 5 };
    return s;
  }

  virtual absyntax::exp *getExp() {
    datumError("invalid use of datum");
    return new errorExp;
  }

  // How to access a field of the datum.
  virtual absyntax::exp *getFieldExp(symbol id) {
    assert(id);
    return new fieldExp(nullPos, this->getExp(), id);
  }

  virtual ImpDatum *getField(const char *name);

  virtual ImpDatum *getCell(ImpDatum *index) {
    return datumError("cannot index datatype");
  }

  virtual void addField(const char *name, ImpDatum *init)
  {
    datumError("cannot set field of datatype");
  }

};

// An ever-growing list of handles, used to avoid garbage collecting the data.
// TODO: Implement effective releaseHandle.
mem::vector<handle_typ> handles;

handle_typ wrap(ImpDatum *d)
{
  handle_typ h = (handle_typ)(d);
  handles.push_back(h);
  return h;
}

ImpDatum *unwrap(handle_typ handle)
{
  assert(handle != 0);
  return (ImpDatum *)(handle);
}

class ErrorDatum : public ImpDatum {
};

error_callback_typ errorCallback = 0;

ImpDatum *datumError(const char *msg)
{
  static ErrorDatum ed;

  if (errorCallback) {
    string_typ s = { msg, strlen(msg) };
    errorCallback(s);
  }
  else {
    cerr << msg << '\n';
  }

  return &ed;
}

handle_typ imp_copyHandle(handle_typ handle)
{
  //cout << "+";
  // For now, don't do anything.
  return handle;
}

void imp_releaseHandle()
{
  //cout << "-";
  // Do nothing, for now.
}

// A datum representing a value in Asymptote.  Both the runtime representation
// of the value and its type are stored.
class ItemDatum : public ImpDatum {
  item i;
  types::ty *t;

public:
  // Every itemDatum has a fixed (non-overloaded) type, t
  ItemDatum(types::ty *t) : t(t) {
    assert(t);
    assert(t->isNotOverloaded());
    assert(t->isNotError());
  }

  // An expression that can be used to get and set the datum.
  // The value should only be set once, when the datum is created, and not
  // changed.
  absyntax::exp *getExp() {
    // It may be faster to create this once on start, but then the datum will
    // require more space.  For now, we create the access and expression on
    // demand.
    return new varEntryExp(nullPos, t, new itemRefAccess(&i));
  }

  int_typ toInt() {
    // TODO: Decide if we want to use casting.
    if (t->kind == types::ty_Int)
      return static_cast<int_typ>(get<Int>(i));
    else
      return ImpDatum::toInt();
  }

  bool toBool() {
    if (t->kind == types::ty_boolean)
      return get<bool>(i);
    else
      return ImpDatum::toBool();
  }

  double toDouble() {
    if (t->kind == types::ty_real)
      return get<double>(i);
    else
      return ImpDatum::toDouble();
  }

  string_typ toString() {
    if (t->kind == types::ty_string) {
      // TODO: Fix for strings containing NUL.
      string *s = get<string *>(i);
      string_typ st = { s->c_str(), s->length() };
      return st;
    }
    else
      return ImpDatum::toString();
  }
};

ItemDatum *ItemDatumFromExp(types::ty *t, absyntax::exp *e)
{
  ItemDatum *d = new ItemDatum(t);
  assignExp ae(nullPos, d->getExp(), e);
  runExp(&ae);

  return d;
}

ItemDatum *ItemDatumFromInt(int_typ x)
{
  intExp ie(nullPos, static_cast<Int>(x));
  return ItemDatumFromExp(types::primInt(), &ie);
}

ItemDatum *ItemDatumFromBool(bool x)
{
  booleanExp be(nullPos, x);
  return ItemDatumFromExp(types::primBoolean(), &be);
}

ItemDatum *ItemDatumFromDouble(double x)
{
  realExp re(nullPos, x);
  return ItemDatumFromExp(types::primReal(), &re);
}

ItemDatum *ItemDatumFromString(string_typ x)
{
  mem::string s(x.buf, (size_t)x.length);
  stringExp se(nullPos, s);
  return ItemDatumFromExp(types::primString(), &se);
}


// If the interface is asked to return a field which is overloaded, a handle
// to and OverloadedDatum is returned.  No evaluation actually occurs.  The
// datum simply consists of the containing datum and the name of the field
// requested.  Subsequent use of the datum will resolve the overloading (or
// report an error).
class OverloadedDatum : public ImpDatum {
  ImpDatum *parent;
  symbol id;

public:
  OverloadedDatum(ImpDatum *parent, symbol id) : parent(parent), id(id)
  {
    assert(parent);
    assert(id);
  }

  absyntax::exp *getExp() {
    return parent->getFieldExp(id);
    return new fieldExp(nullPos, parent->getExp(), id);
  }
};

ImpDatum *ImpDatum::getField(const char *name)
{
  coenv &e = coenvInOngoingProcess();
  symbol id = symbol::trans(name);

  absyntax::exp *ex = getFieldExp(id);

  types::ty *t = ex->getType(e);
  if (t->isError())
    return datumError("no field of that name");

  if (t->isOverloaded())
    return new OverloadedDatum(this, id);

  // Create a new datum and assign the variable to it.
  ItemDatum *d = new ItemDatum(t);
  assignExp ae(nullPos, d->getExp(), ex);
  runExp(&ae);

  return d;
}

handle_typ imp_handleFromInt(int_typ x)
{
  return wrap(ItemDatumFromInt(x));
}

handle_typ imp_handleFromBool(int_typ x)
{
  if (x != 0 && x != 1)
    return wrap(datumError("invalid boolean value"));

  return wrap(ItemDatumFromBool(x == 1));
}

handle_typ imp_handleFromDouble(double x)
{
  return wrap(ItemDatumFromDouble(x));
}

int_typ imp_IntFromHandle(handle_typ handle)
{
  return unwrap(handle)->toInt();
}

int_typ imp_boolFromHandle(handle_typ handle)
{
  return unwrap(handle)->toBool() ? 1 : 0;
}

double imp_doubleFromHandle(handle_typ handle)
{
  return unwrap(handle)->toDouble();
}


handle_typ imp_handleFromString(string_typ x)
{
  return wrap(ItemDatumFromString(x));
}

string_typ imp_stringFromHandle(handle_typ handle)
{
  return unwrap(handle)->toString();
}

handle_typ imp_getField(handle_typ handle, const char *name)
{
  return wrap(unwrap(handle)->getField(name));
}

handle_typ imp_getCell(handle_typ handle, handle_typ index)
{
  return wrap(unwrap(handle)->getCell(unwrap(index)));
}

void imp_addField(handle_typ handle, const char *name, handle_typ init)
{
  unwrap(handle)->addField(name, unwrap(init));
}

class ImpArguments /* TODO: gc visible but not collected */ {
  arglist args;
public:

  ImpArguments() {}

  void add(const char *name, ImpDatum *arg, arg_rest_option isRest)
  {
    assert(isRest == NORMAL_ARG); // TODO: Implement rest.
    symbol id = (name && name[0]) ? symbol::trans(name) : symbol::nullsym;

    args.add(arg->getExp(), id);
  }

  arglist *getArgs() { return &args; }
};

arguments_typ wrapArgs(ImpArguments *args)
{
  return (arguments_typ)(args);
}
ImpArguments *unwrapArgs(arguments_typ args)
{
  return (ImpArguments *)(args);
}

arguments_typ imp_newArguments()
{
  return wrapArgs(new ImpArguments);
}

void imp_releaseArguments(arguments_typ args)
{
  // For now, do nothing.
}

void imp_addArgument(arguments_typ args, const char *name, handle_typ handle,
                     arg_rest_option isRest)
{
  unwrapArgs(args)->add(name, unwrap(handle), isRest);
}

ImpDatum *callDatum(ImpDatum *callee, ImpArguments *args)
{
  coenv &e = coenvInOngoingProcess();

  callExp callex(nullPos, callee->getExp(), args->getArgs());

  types::ty *t = callex.getType(e);
  if (t->isError()) {
    // Run for errors.
    runExp(&callex);
    em.sync(true);
    return datumError("invalid call");
  }

  assert(t->isNotOverloaded()); // Calls are never overloaded.

  if (t->kind == types::ty_void) {
    // Execute the call and return 0 to indicate void.
    runExp(&callex);
    return 0;
  }
  else
    return ItemDatumFromExp(t, &callex);
}

handle_typ imp_call(handle_typ callee, arguments_typ args)
{
  return wrap(callDatum(unwrap(callee), unwrapArgs(args)));
}

class GlobalsDatum : public ImpDatum {
  typedef std::map<const char*, ImpDatum *> gmap;
  gmap base;

  virtual absyntax::exp *getFieldExp(symbol id)
  {
    // Fields of the globals datum are global variables.  Use the unqualified
    // name.
    return new nameExp(nullPos, id);
  }

  virtual void addField(const char *name, ImpDatum *init) {
    datumError("addField not yet re-implemented");
  }
};

class ImpState {
  //ImpArguments *params;

  ImpDatum *retval;
public:
  ImpState() : retval(0) {}

  ImpDatum *globals() {
    return new GlobalsDatum();
  }

  int_typ numParams() {
    /*if (params)
      return params->val.size();
      else */ {
      datumError("parameters accessed outside of function");
      return 0;
    }
  }

  ImpDatum *getParam(int_typ index) {
    /*if (params) {
      if (index >= 0 && index < static_cast<int_typ>(params->val.size()))
      return params->val[index];
      else
      return datumError("invalid index for parameter");
      }
      else */ {
      return datumError("parameters accessed outside of function");
    }
  }

  void setReturnValue(ImpDatum *retval)
  {
    /*if (params) {
      if (this->retval)
      datumError("return value set more than once");
      else
      this->retval = retval;
      }
      else */ {
      datumError("return value set outside of function");
    }
  }

  ImpDatum *getReturnValue()
  {
    return retval;
  }
};

state_typ wrapState(ImpState *s)
{
  return (state_typ)(s);
}
ImpState *unwrapState(state_typ s)
{
  return (ImpState *)(s);
}


handle_typ imp_globals(state_typ state)
{
  return wrap(unwrapState(state)->globals());
}

int_typ imp_numParams(state_typ state)
{
  return unwrapState(state)->numParams();
}

handle_typ imp_getParam(state_typ state, int_typ index)
{
  return wrap(unwrapState(state)->getParam(index));
}

void imp_setReturnValue(state_typ state, handle_typ handle)
{
  unwrapState(state)->setReturnValue(unwrap(handle));
}

state_typ cheatState()
{
  return wrapState(new ImpState());
}

#if 0
class FunctionDatum : public ImpDatum {
  function_typ f;
  void *data;

public:
  FunctionDatum(function_typ f, void *data) : f(f), data(data) {}

  ImpDatum *call(ImpArguments *args) {
    ImpState state(args);

    // Call the function.
    f(wrapState(&state),data);

    if (state.getReturnValue())
      return state.getReturnValue();
    else
      // TODO: Decide on datum for void return.
      return 0;
  }
};
#endif

handle_typ imp_handleFromFunction(const char *signature,
                                  function_typ f, void *data)
{
  // TODO: Re-implement.
  return 0; //wrap(new FunctionDatum(f, data));
}

void imp_setErrorCallback(error_callback_typ callback)
{
  errorCallback = callback;
}

extern policy_typ imp_policy;
policy_typ imp_policy =
{
  /* version = */ 101,
  imp_copyHandle,
  imp_releaseHandle,
  imp_handleFromInt,
  imp_handleFromBool,
  imp_handleFromDouble,
  imp_handleFromString,
  imp_handleFromFunction,
  imp_IntFromHandle,
  imp_boolFromHandle,
  imp_doubleFromHandle,
  imp_stringFromHandle,
  imp_getField,
  imp_getCell,
  imp_addField,
  imp_newArguments,
  imp_releaseArguments,
  imp_addArgument,
  imp_call,
  imp_globals,
  imp_numParams,
  imp_getParam,
  imp_setReturnValue,
  imp_setErrorCallback,
};

// Defined in process.cc
void init(bool resetpath=true);

extern "C" {

  policy_typ *_asy_getPolicy()
  {
    return &imp_policy;
  }

  state_typ _asy_getState()
  {
    static state_typ state = cheatState();

    // TODO: Make sure this runs once.
    char buf[] = "asymptote.so";
    char *argv [] = { buf };
    settings::setOptions(1,argv);

    // Ensures uptodate is not used.
    init();

    return state;
  }

}
