/*****
 * dec.h
 * Andy Hammerlindl 2002/8/29
 *
 * Represents the abstract syntax tree for declarations in the language.
 * Also included is abstract syntax for types as they are most often
 * used with declarations.
 *****/

#ifndef DEC_H
#define DEC_H

#include "symbol.h"
#include "absyn.h"
#include "name.h"
#include "varinit.h"
#include "modifier.h"

namespace trans {
class coenv;
class genv;
class protoenv;
class varEntry;
class access;
}

namespace types {
class ty;
struct formal;
struct signature;
struct function;
}

namespace vm {
struct lambda;
}
namespace absyntax {

using trans::genv;
using trans::coenv;
using trans::protoenv;
using trans::varEntry;
using trans::access;
using sym::symbol;

class vardec;

enum class AutounravelOption {
  Apply,
  DoNotApply,
};

class astType : public absyn {
public:
  astType(position pos)
    : absyn(pos) {}

  virtual void prettyprint(ostream &out, Int indent) = 0;

  // If we introduced a new type, automatically add corresponding functions for
  // that type.
  virtual void
  addOps(coenv&, record*, AutounravelOption opt= AutounravelOption::Apply)
  {}

  // Returns the internal representation of the type. Error reporting
  // depends on ambient error suppression state (set via
  // em.modeGuard(ErrorMode) at call sites).
  virtual types::ty *trans(coenv &e) = 0;

  virtual trans::tyEntry *transAsTyEntry(coenv &e, record *where);

  virtual operator string() const = 0;
#ifdef USEGC
  operator std::string() const {
    return mem::stdString(this->operator string());
  }
#endif
};

class nameTy : public astType {
  name *id;

public:
  nameTy(position pos, name *id)
    : astType(pos), id(id) {}

  nameTy(name *id)
    : astType(id->getPos()), id(id) {}

  void prettyprint(ostream &out, Int indent) override;

  void
  addOps(coenv& e, record* r,
         AutounravelOption opt= AutounravelOption::Apply) override;
  types::ty *trans(coenv &e) override;
  trans::tyEntry *transAsTyEntry(coenv &e, record *where) override;

  virtual operator string() const override;
};

class dimensions : public absyn {
  size_t depth;
public:
  dimensions(position pos)
    : absyn(pos), depth(1) {}

  void prettyprint(ostream &out, Int indent);

  void increase()
  { depth++; }

  size_t size() {
    return depth;
  }

  types::array *truetype(types::ty *base);
};

class arrayTy : public astType {
  astType *cell;
  dimensions *dims;

public:
  arrayTy(position pos, astType *cell, dimensions *dims)
    : astType(pos), cell(cell), dims(dims) {}

  arrayTy(name *id, dimensions *dims)
    : astType(dims->getPos()), cell(new nameTy(id)), dims(dims) {}

  void prettyprint(ostream &out, Int indent) override;

  void
  addOps(coenv& e, record* r,
         AutounravelOption opt= AutounravelOption::Apply) override;

  types::ty *trans(coenv &e) override;

  operator string() const override;
};

// Similar to varEntryExp, this helper class always translates to the same
// fixed type.
class tyEntryTy : public astType {
  trans::tyEntry *ent;
public:
  tyEntryTy(position pos, trans::tyEntry *ent)
    : astType(pos), ent(ent) {}

  tyEntryTy(position pos, types::ty *t);

  void prettyprint(ostream &out, Int indent) override;

  types::ty *trans(coenv &e) override;
  trans::tyEntry *transAsTyEntry(coenv &, record *) override {
    return ent;
  }
  operator string() const override;
};

// Runnable is anything that can be executed by the program, including
// any declaration or statement.
class runnable : public absyn {
public:
  runnable(position pos)
    : absyn(pos) {}

  virtual void prettyprint(ostream &out, Int indent) = 0;

  void markTrans(coenv &e)
  {
    markPos(e);
    trans(e);
  }

  /* Translates the stm or dec as if it were in a function definition. */
  virtual void trans(coenv &e) {
    transAsField(e, 0);
  }

  /* This can be overridden, to specify a special way of translating the code
   * when it is run at the top of the interactive prompt.
   */
  virtual void interactiveTrans(coenv &e) {
    trans(e);
  }

  void markTransAsField(coenv &e, record *r)
  {
    markPos(e);
    transAsField(e,r);
  }

  /* Translate the runnable as in the lowest lexical scope of a record
   * definition.  If it is simply a statement, it will be added to the
   * record's initializer.  A declaration, however, will also have to
   * add a new type or field to the record.
   */
  virtual void transAsField(coenv &e, record *) = 0;

  virtual vm::lambda *transAsCodelet(coenv &e);

  // For functions that return a value, we must guarantee that they end
  // with a return statement.  This checks for that condition.
  virtual bool returns()
  { return false; }

  // Returns true if it is syntactically allowable to modify this
  // runnable by a PUBLIC or PRIVATE modifier.
  virtual bool allowPermissions()
  { return false; }
};

// Forward declaration.
class formals;

class namedTy : public gc {
public:
  symbol dest;
  types::ty *t;
  position pos;
  namedTy(position pos, symbol dest, types::ty *t)
    : dest(dest), t(t), pos(pos) {}
};

class receiveTypedefDec;

class block : public runnable {
public:
  mem::list<runnable *> stms;

  // If the runnables should be interpreted in their own scope.
  bool scope;

protected:
  void prettystms(ostream &out, Int indent);

private:
  // If the first runnable exists and is a receiveTypedefDec*, return it;
  // otherwise return nullptr.
  receiveTypedefDec* getTypedefDec();

public:
  block(position pos, bool scope=true)
    : runnable(pos), scope(scope) {}

  // To ensure list deallocates properly.
  virtual ~block() {}

  void add(runnable *r) {
    stms.push_back(r);
  }

  void prettyprint(ostream &out, Int indent) override;

  void trans(coenv &e) override;

  void transAsField(coenv &e, record *r) override;

  bool transAsTemplatedField(
    coenv &e, record *r, mem::vector<absyntax::namedTy*>* args
  );

  types::record *transAsFile(genv& ge, symbol id);

  types::record *transAsTemplatedFile(
      genv& ge,
      symbol id,
      mem::vector<absyntax::namedTy*> *args
  );

  // If the block can be interpreted as a single vardec, return that vardec
  // (otherwise 0).
  vardec *asVardec();

  // A block is guaranteed to return iff one of the runnables is guaranteed to
  // return.
  // This is conservative in that
  //
  // int f(int x)
  // {
  //   if (x==1) return 0;
  //   if (x!=1) return 1;
  // }
  //
  // is not guaranteed to return.
  bool returns() override;

  void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
};

class modifierList : public absyn {
  mem::list<trans::permission> perms;
  mem::list<trans::modifier> mods;

public:
  modifierList(position pos)
    : absyn(pos) {}

  virtual ~modifierList()
  {}

  void prettyprint(ostream &out, Int indent);

  void add(trans::permission p)
  {
    perms.push_back(p);
  }

  void add(trans::modifier m)
  {
    mods.push_back(m);
  }

  /* True if a static or dynamic modifier is present.
   */
  bool staticSet();

  /* Says if the modifiers indicate static or dynamic. Prints error if
   * there are duplicates.
   */
  trans::modifier getModifier();

  /* Says if it is declared public, private, or read-only (default).
   * Prints error if there are duplicates.
   */
  trans::permission getPermission();
};

// Modifiers of static or dynamic can change the way declarations and
// statements are encoded.
class modifiedRunnable : public runnable {
  modifierList *mods;
  runnable *body;

public:
  modifiedRunnable(position pos, modifierList *mods, runnable *body)
    : runnable(pos), mods(mods), body(body)  {}

  modifiedRunnable(position pos, trans::permission perm, runnable *body)
    : runnable(pos), mods(new modifierList(pos)), body(body) {
    mods->add(perm);
  }

  void prettyprint(ostream &out, Int indent);

  void transAsField(coenv &e, record *r);

  bool returns()
  { return body->returns(); }
};


class decidstart : public absyn {
protected:
  symbol id;
  dimensions *dims;

public:
  decidstart(position pos, symbol id, dimensions *dims = 0)
    : absyn(pos), id(id), dims(dims) {}

  virtual void prettyprint(ostream &out, Int indent) override;

  virtual types::ty*
  getType(types::ty* base, coenv&);
  virtual trans::tyEntry *getTyEntry(trans::tyEntry *base, coenv &e,
                                     record *where);

  // If a new type is formed by adding dimensions (or a function signature)
  // after the id, this will add the standard functions for that new type.
  virtual void addOps(types::ty *base, coenv &e, record *r);

  virtual symbol getName()
  { return id; }

  void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
  void createSymMapWType(
      AsymptoteLsp::SymbolContext* symContext, absyntax::astType* base
  );
};

class fundecidstart : public decidstart {
  formals *params;

public:
  fundecidstart(position pos,
                symbol id,
                dimensions *dims = 0,
                formals *params = 0)
    : decidstart(pos, id, dims), params(params) {}

  void prettyprint(ostream &out, Int indent);

  types::ty*
  getType(types::ty* base, coenv& e);
  trans::tyEntry* getTyEntry(trans::tyEntry* base, coenv& e, record* where);
  void addOps(types::ty* base, coenv& e, record* r);
};

class decid : public absyn {
  decidstart *start;
  varinit *init;

  // Returns the default initializer for the type.
  access *defaultInit(coenv &e, types::ty *t);

public:
  decid(position pos, decidstart *start, varinit *init = 0)
    : absyn(pos), start(start), init(init) {}

  virtual void prettyprint(ostream &out, Int indent) override;

  virtual void transAsField(coenv &e, record *r, types::ty *base);

  // Translate, but add the names in as types rather than variables.
  virtual void transAsTypedefField(coenv &e, trans::tyEntry *base, record *r);

  decidstart *getStart() { return start; }

  void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
  void createSymMapWType(
      AsymptoteLsp::SymbolContext* symContext, absyntax::astType* base
  );
};

class decidlist : public absyn {
  mem::list<decid *> decs;

public:
  decidlist(position pos)
    : absyn(pos) {}

  virtual ~decidlist() {}

  void add(decid *p) {
    decs.push_back(p);
  }

  virtual void prettyprint(ostream &out, Int indent) override;

  virtual void transAsField(coenv &e, record *r, types::ty *base);

  // Translate, but add the names in as types rather than variables.
  virtual void transAsTypedefField(coenv &e, trans::tyEntry *base, record *r);

  // If the list consists of a single entry, return it.
  decid *singleEntry()
  {
    if (decs.size() == 1)
      return decs.front();
    else
      return 0;
  }

  void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
  void createSymMapWType(
      AsymptoteLsp::SymbolContext* symContext, absyntax::astType* base
  );
};

class dec : public runnable {
public:
  dec(position pos)
    : runnable(pos) {}

  void prettyprint(ostream &out, Int indent) override;

  // Declarations can be public or private.
  bool allowPermissions() override
  { return true; }
};

void createVar(position pos, coenv &e, record *r,
               symbol id, types::ty *t, varinit *init);

class vardec : public dec {
  astType *base;
  decidlist *decs;

public:
  vardec(position pos, astType *base, decidlist *decs)
    : dec(pos), base(base), decs(decs) {}

  vardec(position pos, astType *base, decid *di)
    : dec(pos), base(base), decs(new decidlist(pos))
  {
    decs->add(di);
  }
  void prettyprint(ostream &out, Int indent) override;

  void transAsField(coenv &e, record *r) override
  {
    base->addOps(e, r, AutounravelOption::DoNotApply);
    decs->transAsField(e, r, base->trans(e));
  }

  // Translate, but add the names in as types rather than variables.
  virtual void transAsTypedefField(coenv &e, record *r);

  // If the vardec encodes a single declaration, return the name of that
  // declaration (otherwise nullsym).
  symbol singleName();

  // If the vardec encodes a single declaration, return the type of that
  // declaration (otherwise 0).
  types::ty *singleGetType(coenv& e);

  void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
};

struct idpair : public absyn {
  symbol src; // The name of the module to access.
  symbol dest;  // What to call it in the local environment.
  bool valid; // If it parsed properly.

  idpair(position pos, symbol id)
    : absyn(pos), src(id), dest(id), valid(true) {}

  idpair(position pos, symbol src, symbol as, symbol dest)
    : absyn(pos), src(src), dest(dest), valid(as==symbol::trans("as")) {}

  idpair(position pos, string src, symbol as, symbol dest)
    : absyn(pos), src(symbol::trans(src)), dest(dest),
      valid(as==symbol::trans("as")) {}

  void checkValidity() {
    if (!valid) {
      em.error(getPos());
      em << "expected 'as'";
    }
  }

  void prettyprint(ostream &out, Int indent) override;

  // Translates as: access src as dest;
  void transAsAccess(coenv &e, record *r);

  // Translates as: from _ unravel src as dest;
  // where _ is the qualifier record with source as its fields and types.
  trans::tyEntry *transAsUnravel(coenv &e, record *r,
                                 protoenv &source, varEntry *qualifier);

  void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
};

struct idpairlist : public gc {
  mem::list<idpair *> base;

  void add(idpair *x) {
    base.push_back(x);
  }

  void prettyprint(ostream &out, Int indent);

  void transAsAccess(coenv &e, record *r);

  mem::vector<trans::tyEntry*> transAsUnravel(
    coenv &e, record *r, protoenv &source, varEntry *qualifier
  );

  void createSymMap(AsymptoteLsp::SymbolContext* symContext);

  template<typename TFn>
  void processListFn(TFn const& fn)
  {
    for (auto* idp : base)
    {
      fn(idp->src, idp->dest);
    }
  }
};

extern idpairlist * const WILDCARD;


class accessdec : public dec {
  idpairlist *base;

public:
  accessdec(position pos, idpairlist *base)
    : dec(pos), base(base) {}

  void prettyprint(ostream &out, Int indent) override;

  void transAsField(coenv &e, record *r) override {
    base->transAsAccess(e,r);
  }

  void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
};

class badDec : public dec {
  position errorPos;
  string errorMessage;

public:
  badDec(position pos, position errorPos, string errorMessage)
      : dec(pos), errorPos(errorPos), errorMessage(errorMessage) {}

  void transAsField(coenv&, record*) override {
    em.error(errorPos);
    em << errorMessage;
  }
};

// Accesses the file with specified types added to the type environment.
class templateAccessDec : public dec {
  symbol src; // The name of the module to access.
  formals *args;
  symbol dest;  // What to call it in the local environment.
  bool valid;
  position expectedAsPos;

public:
  templateAccessDec(position pos, symbol src, formals* args, symbol as,
                    symbol dest, position asPos)
      : dec(pos), src(src), args(args), dest(dest),
        valid(as == symbol::trans("as")), expectedAsPos(asPos) {}

  bool checkValidity() {
    if (!valid) {
      em.error(expectedAsPos);
      em << "expected 'as'";
      return false;
    }
    return true;
  }

  void transAsField(coenv& e, record* r) override;
};

class typeParam : public absyn {
  const symbol paramSym;
public:
  typeParam(position pos, symbol paramSym)
    : absyn(pos), paramSym(paramSym) {}

  bool transAsParamMatcher(coenv &e, record *r, namedTy *arg);

  void prettyprint(ostream &out, Int indent);
};

class typeParamList : public absyn {
  mem::vector<typeParam*> params;

public:
  typeParamList(position pos) : absyn(pos) {}

  void add(typeParam *tp);

  bool transAsParamMatcher(coenv &e, record *r,
                           mem::vector<namedTy*> *args);

  void prettyprint(ostream &out, Int indent);
};


class receiveTypedefDec : public dec {
  typeParamList* params;

public:
  receiveTypedefDec(position pos, typeParamList* params)
    : dec(pos), params(params) {}

  void transAsField(coenv& e, record *r) override;
  bool transAsParamMatcher(
    coenv& e, record *r, mem::vector<namedTy*> *args
  );
};


// Abstract base class for
//   from _ access _;  (fromaccessdec)
// and
//   from _ unravel _;  (unraveldec)
class fromdec : public dec {
protected:
  struct qualifier {
    // The varEntry holds the location and the type of the highest framed
    // structure that can be put on the stack.  The record holds the actual
    // type of the qualifier.
    // For example:
    //   struct A {
    //     struct B {
    //       static int x;
    //     }
    //   }
    //   A a=new A;
    //   from a.B unravel x;
    //
    // Here, v->getType() will yield A and v->getLocation() will yield the
    // location of the variable a, but the record type t will be B.
    record *t;
    varEntry *v;

    qualifier(record *t, varEntry *v)
      : t(t), v(v) {}
  };

  // Return the qualifier from which the fields are taken.  If t==0, it is
  // assumed that an error occurred and was reported.
  virtual qualifier getQualifier(coenv &e, record *r) = 0;
  idpairlist *fields;

public:
  fromdec(position pos, idpairlist *fields)
    : dec(pos), fields(fields) {}

  void prettyprint(ostream &out, Int indent) override;

  void transAsField(coenv &e, record *r) override;
};

// An unravel declaration dumps fields and types of a record into the local
// scope.
class unraveldec : public fromdec {
  name *id;

  qualifier getQualifier(coenv &e, record *) override;
public:
  unraveldec(position pos, name *id, idpairlist *fields)
    : fromdec(pos, fields), id(id) {}

  void prettyprint(ostream &out, Int indent) override;

  void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
};

// A fromaccess declaration dumps fields and types of a module into the local
// scope.  It does not add the module as a variable in the local scope.
class fromaccessdec : public fromdec {
  symbol id;
  formals *templateArgs;

  qualifier getQualifier(coenv &e, record *r) override;
public:
  fromaccessdec(
      position pos, symbol id, idpairlist *fields, formals *templateArgs = 0
  ) : fromdec(pos, fields), id(id), templateArgs(templateArgs) {}

  void prettyprint(ostream &out, Int indent) override;

  void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
};

// An import declaration dumps fields and types of a module into the local
// scope.  It also adds the module as a variable in the local scope.
class importdec : public dec {
  block base;

public:
  importdec(position pos, idpair *id)
    : dec(pos), base(pos, false) {
    idpairlist *i=new idpairlist;
    i->add(id);
    base.add(new accessdec(pos, i));
    base.add(new unraveldec(pos, new simpleName(pos, id->dest), WILDCARD));
  }

  void trans(coenv &e) override {
    base.trans(e);
  }

  void transAsField(coenv &e, record *r) override {
    base.transAsField(e, r);
  }

  void prettyprint(ostream &out, Int indent) override;

  void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
};

// Parses the file given, and translates the resulting runnables as if they
// occurred at this place in the code.
class includedec : public dec {
  string filename;

public:
  includedec(position pos, string filename)
    : dec(pos), filename(filename) {}
  includedec(position pos, symbol id)
    : dec(pos), filename(id) {}

  void prettyprint(ostream &out, Int indent) override;
  void loadFailed(coenv &e);

  void transAsField(coenv &e, record *r) override;

  void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
};

// Types defined from others in `typedef` or `using` statement.
class typedec : public dec {
  vardec *body;

public:
  typedec(position pos, vardec *body)
    : dec(pos), body(body) {}

  typedec(position pos, decidstart *id_with_signature, astType *return_type)
    : dec(pos)
  {
    decid *di = new decid(id_with_signature->getPos(), id_with_signature);
    body = new vardec(return_type->getPos(), return_type, di);
  }

  void prettyprint(ostream &out, Int indent);

  void transAsField(coenv &e, record *r) {
    body->transAsTypedefField(e,r);
  }
};


// A struct declaration.
class recorddec : public dec {
  symbol id;
  block *body;

  void transRecordInitializer(coenv &e, record *parent);
  void addPostRecordEnvironment(coenv &e, record *r, record *parent);

public:
  recorddec(position pos, symbol id, block *body)
    : dec(pos), id(id), body(body) {}

  virtual ~recorddec()
  {}

  void prettyprint(ostream &out, Int indent) override;

  void transAsField(coenv &e, record *parent) override;

  void createSymMap(AsymptoteLsp::SymbolContext* symContext) override;
};

// Returns a runnable that facilitates the autoplain feature.
runnable *autoplainRunnable();

void addVar(coenv &e, record *r, varEntry *v, symbol id);

} // namespace absyntax

#endif
