/*****
* entry.h
* Andy Hammerlindl 2002/08/29
*
* All variables, built-in functions and user-defined functions reside
* within the same namespace.  To keep track of all these, a table of
* "entries" is used.
*****/

#ifndef ENTRY_H
#define ENTRY_H

#include <iostream>

#include "common.h"
#include "frame.h"
#include "table.h"
#include "types.h"
#include "modifier.h"

using sym::symbol;
using types::ty;
using types::signature;

// Forward declaration.
namespace types {
class record;
}
using types::record;

namespace trans {

// An entry is associated to a name in the (variable or type) environment, and
// has permission based on the enclosing records where it was defined or
// imported.
class entry : public gc {
 struct pr {
   permission perm;
   record *r;

   pr(permission perm, record *r)
     : perm(perm), r(r) {}

   // Returns true if the permission allows access in this context.
   bool check(action act, coder &c);

   // Reports an error if permission is not allowed.
   void report(action act, position pos, coder &c);
 };

 mem::list<pr> perms;

 void addPerm(permission perm, record *r) {
   // Only store restrictive permissions.
   if (perm != PUBLIC && r)
     perms.push_back(pr(perm,r));
 }

 // The record where the variable or type is defined, or 0 if the entry is
 // not a field.
 record *where;

 // The location (file and line number) where the entry was defined.
 position pos;

public:
 entry(record *where, position pos) : where(where), pos(pos) {}
 entry(permission perm, record *r, record *where, position pos)
   : where(where), pos(pos) {
   addPerm(perm, r);
 }

 // (Non-destructively) merges two entries, appending permission lists.
 // The 'where' member is taken from the second entry.
 entry(entry &e1, entry &e2);

 // Create an entry with one more permission in the list.
 entry(entry &base, permission perm, record *r);

 bool checkPerm(action act, coder &c);
 void reportPerm(action act, position pos, coder &c);

 record *whereDefined() {
   return where;
 }

 position getPos() {
   return pos;
 }
};

class varEntry : public entry {
 ty *t;
 access *location;

public:
 varEntry(ty *t, access *location, record *where, position pos)
   : entry(where, pos), t(t), location(location) {}

 varEntry(ty *t, access *location, permission perm, record *r,
          record *where, position pos)
   : entry(perm, r, where, pos), t(t), location(location) {}

 // (Non-destructively) merges two varEntries, creating a qualified varEntry.
 varEntry(varEntry &qv, varEntry &v);

 // Copies the original varEntry and adds a new permission constraint.
 varEntry(varEntry &base, permission perm, record *r)
   : entry(base, perm, r), t(base.t), location(base.location) {}

 ty *getType()
 { return t; }

 signature *getSignature()
 {
   return t->getSignature();
 }

 access *getLocation()
 { return location; }

 frame *getLevel();

 // Encodes the access, but also checks permissions.
 void encode(action act, position pos, coder &c);
 void encode(action act, position pos, coder &c, frame *top);
};

varEntry *qualifyVarEntry(varEntry *qv, varEntry *v);

// As looked-up types can be allocated in a new expression, we need to know
// what frame they should be allocated on.  Type entries store this extra
// information along with the type.
class tyEntry : public entry {
public:
 ty *t;
 varEntry *v;  // NOTE: Name isn't very descriptive.

 tyEntry(ty *t, varEntry *v, record *where, position pos)
   : entry(where, pos), t(t), v(v) {}

 tyEntry(tyEntry *base, permission perm, record *r)
   : entry(*base, perm, r), t(base->t), v(base->v) {}

 // Records need a varEntry that refers back to the qualifier qv; i.e. in
 // the last new of the code
 //   struct A {
 //     struct B {}
 //   }
 //   A a=new A;
 //   unravel a;
 //   new B;
 // we need to put a's frame on the stack before allocating an instance of B.
 // NOTE: A possible optimization could be to only qualify the varEntry if
 // the type is a record, as other types don't use the varEntry.
private:
 tyEntry(tyEntry *base, varEntry *qv)
   : entry(*base, *qv), t(base->t), v(qualifyVarEntry(qv, base->v)) {}

public:
 // Since the constructor can only be used when qv is non-null it is private
 // for safety reasons, and we provide this method instead.
 friend tyEntry *qualifyTyEntry(varEntry *qv, tyEntry *ent);
};

inline tyEntry *qualifyTyEntry(varEntry *qv, tyEntry *ent) {
 return qv ? new tyEntry(ent, qv) : ent;
}

// The type environment.
class tenv : public sym::table<tyEntry *> {
 tyEntry *add(symbol dest, names_t::value_type &x, varEntry *qualifier,
              coder &c);
public:
 // Add the entries in one environment to another, if qualifier is
 // non-null, it is a record and the source environment is its types.  The
 // coder is used to see which entries are accessible and should be added.
 void add(tenv& source, varEntry *qualifier, coder &c);

 // Adds entries of the name src in source as the name dest, returning true if
 // any were added.
 tyEntry *add(symbol src, symbol dest,
              tenv& source, varEntry *qualifier, coder &c);
};


// For speed reasons, many asserts are only tested when DEBUG_CACHE is set.
#ifndef DEBUG_CACHE_ASSERT
#  ifdef DEBUG_CACHE
#    define DEBUG_CACHE_ASSERT(x) assert(x)
#  else
#    define DEBUG_CACHE_ASSERT(x) (void)(x)
#  endif
#endif

// The hash table which is at the core of the variable environment venv.
class core_venv : public gc {

public:
 // The cells of the table
 struct cell {
   symbol name;
   varEntry *ent;

   bool empty() const {
     return name == 0;
   }

   bool isATomb() const {
     DEBUG_CACHE_ASSERT(!empty());
     return ent == 0;
   }

   bool filled() const {
     return !empty() and !isATomb();
   }

   bool matches(symbol name, const ty *t) {
     DEBUG_CACHE_ASSERT(name.special());
     DEBUG_CACHE_ASSERT(t);

     if (this->name != name)
       return false;
     if (!this->ent)
       return false;
     return equivalent(this->ent->getType(), t);
   }

   bool matches(symbol name, const signature *sig) {
     DEBUG_CACHE_ASSERT(!name.special());

     if (this->name != name)
       return false;
     if (!this->ent)
       return false;
     return equivalent(this->ent->getSignature(), sig);
   }

   void storeNew(symbol name, varEntry *ent) {
     DEBUG_CACHE_ASSERT(empty() || isATomb());

     this->name = name;
     this->ent = ent;
   }

   varEntry *replaceWith(symbol name, varEntry *ent) {
     this->name = name;

     varEntry *old = this->ent;
     this->ent = ent;
     return old;
   }

   void remove() {
     this->ent = 0;
   }
 };

private:
 size_t capacity;
 size_t size;
 size_t mask;
 cell *table;

 void initTable(size_t capacity);

 void resize();

 cell& cellByIndex(size_t i);

 const cell& cellByIndex(size_t i) const;

 varEntry *storeNew(cell& cell, symbol name, varEntry *ent);

 varEntry *storeNonSpecialAfterTomb(size_t tombIndex,
                                    symbol name, varEntry *ent);
 varEntry *storeSpecialAfterTomb(size_t tombIndex,
                                 symbol name, varEntry *ent);

public:
 core_venv(size_t capacity) {
   initTable(capacity);
 }

 bool empty() const { return size == 0; }
 void clear();

 void confirm_size();

 // Store an entry into the table.  If this shadows a previous entry, the old
 // entry is returned, otherwise 0 is returned.
 varEntry *storeNonSpecial(symbol name, varEntry *ent);
 varEntry *storeSpecial(symbol name, varEntry *ent);
 varEntry *store(symbol name, varEntry *ent);

 // Lookup an entry in the table.
 varEntry *lookupNonSpecial(symbol name, const signature *sig);
 varEntry *lookupSpecial(symbol name, const ty *t);
 varEntry *lookup(symbol name, const ty *t);

 // Remove an entry from the table.
 void removeNonSpecial(symbol name, const signature *sig);
 void removeSpecial(symbol name, const ty *t);
 void remove(symbol name, const ty *t);

 // Features for iterating over the entire table.
 class const_iterator {
   const core_venv& core;
   size_t index;

 public:
   const_iterator(const core_venv& core, size_t index)
     : core(core), index(index) {}

   const cell& operator * () const {
     return core.cellByIndex(index);
   }
   const cell* operator -> () const {
     return &core.cellByIndex(index);
   }

   const_iterator& operator ++ () {
     // Advance to the next filled cell, or stop at the end of the array.
     do {
       ++index;
     } while (!(*this)->filled() && index < core.capacity);

     DEBUG_CACHE_ASSERT((*this)->filled() || (*this) == core.end());

     return *this;
   }

   friend bool operator == (const const_iterator& a, const const_iterator& b)
   {
     // For speed, we don't compare the hashtables.
     return a.index == b.index;
   }
   friend bool operator != (const const_iterator& a, const const_iterator& b)
   {
     // For speed, we don't compare the hashtables.
     return a.index != b.index;
   }
 };

 const_iterator begin() const {
   size_t index = 0;
   while (index < capacity && !cellByIndex(index).filled())
     ++index;
   return const_iterator(*this, index);
 }

 const_iterator end() const {
   return const_iterator(*this, capacity);
 }
};

struct SigHash {
 size_t operator()(const mem::pair<symbol, ty *>& p) const;
};

struct SigEquiv {
 bool operator()(const mem::pair<symbol, ty *>& p1,
                 const mem::pair<symbol, ty *>& p2) const;
};

enum class AutounravelPriority {
 OFFER,
 FORCE,
};

// venv implemented with a hash table.
class venv {
 // A hash table used to quickly look up a variable once its name and type are
 // known.  Includes all scopes.
 core_venv core;

 // Record of added variables in the order they were added.
 struct addition {
   symbol name;
   ty *t;
   varEntry *shadowed;

   addition(symbol name, ty *t, varEntry *shadowed)
     : name(name), t(t), shadowed(shadowed) {}
 };
 typedef mem::stack<addition> addstack;
 addstack additions;

 // A list of variables that need to be unraveled whenever the containing
 // record (if any) becomes available.
 mem::list<mem::pair<symbol, varEntry *>> autoUnravels;
 mem::unordered_map<mem::pair<symbol, ty*>,
                    std::nullptr_t,
                    SigHash,
                    SigEquiv> nonShadowableAutoUnravels;

 // A scope can be recorded by the size of the addition stack at the time the
 // scope began.
 typedef mem::stack<size_t> scopestack;
 scopestack scopesizes;


 struct namehash {
   size_t operator()(const symbol name) const {
     return name.hash();
   }
 };
 struct nameeq {
   bool operator()(const symbol s, const symbol t) const {
     return s==t;
   }
 };

 struct namevalue {
   size_t maxFormals;
   ty *t;

   namevalue() : maxFormals(0), t(0) {}

   void addType(ty *s);

   void replaceType(ty *new_t, ty *old_t);

#if DEBUG_CACHE
   void popType(astType *tnew);
#else
   void popType();
#endif
 };

 // A dictionary indexed solely on the name, storing for each name the
 // current (possibly overloaded) type of the name.
 // The hash table implementation is slightly faster than the std::map binary
 // tree implementation, so we use it if we can.
 typedef mem::unordered_map<symbol, namevalue, namehash, nameeq> namemap;
 namemap names;


 // A sanity check.  For a given name, it checks that the type stored in the
 // names hash table exactly matches with all of the entries of that name
 // stored in the full hash table.
 void checkName(symbol name);

 void listValues(symbol name, record *module);


 // Helper function for endScope.
 void remove(const addition& a);

 // These are roughly the size the hashtables will be after loading the
 // builtin functions and plain module.
 static const size_t fileCoreSize=1 << 13;
 static const size_t fileNamesSize=1000;

 // The number of scopes begun (but not yet ended) when the venv was empty.
 size_t empty_scopes;
public:
 venv() :
   core(1 << 2), empty_scopes(0) {}

 // Most file level modules automatically import plain, so allocate hashtables
 // big enough to hold it in advance.
 struct file_env_tag {};
 venv(file_env_tag)
   : core(fileCoreSize),
     names(fileNamesSize),
     empty_scopes(0) {}

 // Add a new variable definition.
 void enter(symbol name, varEntry *v);

 // Add the entries in one environment to another, if qualifier is
 // non-null, it is a record and entries of the source environment are its
 // fields.  The coder is necessary to check which variables are accessible and
 // should be added.
 void add(venv& source, varEntry *qualifier, coder &c);

 // Add all unshadowed variables from source of the name src as variables
 // named dest.  Returns true if at least one was added.
 bool add(symbol src, symbol dest,
          venv& source, varEntry *qualifier, coder &c,
          mem::vector<varEntry *> *addedVec=nullptr);

 // Look for a function that exactly matches the type given.
 varEntry *lookByType(symbol name, ty *t) {
   return core.lookup(name, t);
 }

 // An optimization heuristic.  Try to guess the signature of a variable and
 // look it up.  This is allowed to return 0 even if the appropriate variable
 // exists.  If it returns a varEntry from an overloaded number of choices,
 // the returned function must be the one which would be called with
 // arguments given by sig, and its signature must be equivalent to sig.
 // For instance, in
 //   int f(int a, int b);
 //   int f(int a, int b, int c = 1);
 //   f(a,b);
 // looking up the signature of 'f' with arguments (int, int) must return 0
 // as there is an ambiguity.  The maxFormals field is used to ensure we
 // avoid such ambiguities.
 varEntry *lookBySignature(symbol name, signature *sig);

 // Get the (possibly overloaded) type of all variables associated to a
 // particular name.
 ty *getType(symbol name);

 void beginScope();
 void endScope();

 // Merges the top-level scope with the level immediately underneath it.
 void collapseScope();

 // Prints a list of the variables to the standard output.
 void list(record *module=0);

 // Adds to l, all names prefixed by start.
 void completions(mem::list<symbol>& l, string start);

 void registerAutoUnravel(
   symbol name,
   varEntry *v,
   AutounravelPriority priority=AutounravelPriority::FORCE
 );

 const mem::list<mem::pair<symbol, varEntry *>>& getAutoUnravels() {
   return autoUnravels;
 }
};

} // namespace trans

#endif //ENTRY_H