/*****
 * record.h
 * Andy Hammerlindl 2003/07/09
 *
 * The type for records and modules in the language.
 *****/

#ifndef RECORD_H
#define RECORD_H

#include "types.h"
#include "env.h"
#include "frame.h"
#include "access.h"

namespace vm {
struct lambda;
}

using trans::frame;
using trans::protoenv;
using trans::varEntry;
using trans::tyEntry;
using trans::AutoUnravelRegistry;

namespace types {

class record : public ty {
  // The base name of this type.
  symbol name;

  // The frame.  Like a frame for a function, it allocates the accesses
  // for fields and specifies the size of the record.
  frame *level;

  // The runtime representation of the record used by the virtual machine.
  vm::lambda *init;

  ty *kType = nullptr;
  ty *vType = nullptr;

public:
  // The name bindings for fields of the record.
  protoenv e;

  // These are name bindings that should be added to the enclosing environment
  // after translation of the record is completed.  Constructors implicitly
  // defined by "operator init" are stored here.
  protoenv postdefenv;

  // Registry of autounravel entries for this record.  Populated during
  // translation; read by addNameOps when the record is used as a type.
  AutoUnravelRegistry autounravelRegistry;

  record(symbol name, frame *level);
  ~record();

  symbol getName()
  {
    return name;
  }

  symbol getTemplateIndex() {
    return getName(); // May change in the future.
  }

  bool isReference() override {
    return true;
  }

  size_t hash() const override{
    // Use the pointer, as two records are equivalent only if they are the
    // same object.
    return (size_t)this;
  }

  // Initialize to null by default.
  trans::access *initializer() override;

  frame *getLevel(bool statically = false)
  {
    if (statically) {
      frame *f=level->getParent();
      return f ? f : level;
    }
    else
      return level;
  }

  vm::lambda *getInit()
  {
    return init;
  }

  // Allocates a new dynamic field in the record.
  trans::access *allocField(bool statically)
  {
    frame *underlevel = getLevel(statically);
    assert(underlevel);
    return underlevel->allocLocal();
  }

  // Create a statically enclosed record from this record.
  record *newRecord(symbol id, bool statically);

  // Sets the keytype and valuetype based on operator[] and operator[=].
  void computeKVTypes(const position& pos);
  // Returns the key type of the record, or primError() if it cannot be
  // determined. May be called before computeKVTypes, in which case it will
  // compute the key type on demand but will not set the kType field, so that if
  // operator[] is defined later, the correct key type will be returned.
  ty* keyType() override;
  // Returns the value type of the record, or primError() if it cannot be
  // determined. May be called before computeKVTypes, in which case it will
  // compute the value type on demand but will not set the vType field, so that
  // if operator[] or operator[=] is defined later, the correct value type will
  // be returned.
  ty *valType();

  void print(ostream& out) const override
  {
    out << name;
  }

  void debug(ostream& out) const
  {
    out << "struct " << name << endl;
    out << "types:" << endl;
    out << "re-implement" << endl;
    //out << te;
    out << "fields: " << endl;
    out << "re-implement" << endl;
    //out << ve;
  }
};

// A record that is being used just for its fields and types, and has no real
// initializer.  This is for modules such as settings that are built into the
// language.
class dummyRecord : public record {
public:
  dummyRecord(symbol name);
  dummyRecord(string s);

  // Convenient functions for adding fields.
  void add(string name, ty *t, trans::access *a,
           trans::permission perm=trans::PUBLIC);
  void add(string name, function *t, vm::bltin f,
           trans::permission perm=trans::PUBLIC);
};

} //namespace types

#endif
