#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;
 }

}