/*****
 * item.h
 * Tom Prince and John Bowman 2005/04/12
 *
 * Describes the items that are used by the virtual machine.
 *****/

#ifndef ITEM_H
#define ITEM_H

#include "common.h"
#include <cfloat>
#include <cmath>

#if COMPACT
#include <cassert>
#else
#include <typeinfo>
#endif

namespace vm {

class item;
class bad_item_value {};

template<typename T>
T get(const item&);

#if COMPACT
// Identify a default argument.
extern const Int DefaultValue;

// Identify an undefined item.
extern const Int Undefined;

// Values for true and false unlikely to match other values.
extern const Int BoolTruthValue;
extern const Int BoolFalseValue;

inline Int valueFromBool(bool b) {
  return b ? BoolTruthValue : BoolFalseValue;
}
#endif

extern const item Default;

class item : public gc {
private:

#if !COMPACT
  const std::type_info *kind;
#endif

  union {
    Int i;
    double x;
#if !COMPACT
    bool b;
#endif
    void *p;
  };

public:
#if COMPACT
  bool empty() const
  {return i >= Undefined;}

  item() : i(Undefined) {}

  item(Int i)
    : i(i) {}
  item(int i)
    : i(i) {}
  item(double x)
    : x(x) {}
  item(bool b)
    : i(valueFromBool(b)) {}

  item& operator= (int a)
  { i=a; return *this; }
  item& operator= (unsigned int a)
  { i=a; return *this; }
  item& operator= (Int a)
  { i=a; return *this; }
  item& operator= (double a)
  { x=a; return *this; }
  item& operator= (bool b)
  { i=valueFromBool(b); return *this; }

  template<class T>
  item(T *p)
    : p((void *) p) {
    assert(!empty());
  }

  template<class T>
  item(const T &p)
    : p(new(UseGC) T(p)) {
    assert(!empty());
  }

  template<class T>
  item& operator= (T *a)
  { p=(void *) a; return *this; }

  template<class T>
  item& operator= (const T &it)
  { p=new(UseGC) T(it); return *this; }
#else
  bool empty() const
  {return *kind == typeid(void);}

  item()
    : kind(&typeid(void)) {}

  item(Int i)
    : kind(&typeid(Int)), i(i) {}
#ifndef intIsInt
  item(int i)
    : kind(&typeid(Int)), i(i) {}
#endif
  item(double x)
    : kind(&typeid(double)), x(x) {}
  item(bool b)
    : kind(&typeid(bool)), b(b) {}

#ifndef intIsInt
  item& operator= (int a)
  { kind=&typeid(Int); i=a; return *this; }
#endif
  item& operator= (unsigned int a)
  { kind=&typeid(Int); i=a; return *this; }
  item& operator= (Int a)
  { kind=&typeid(Int); i=a; return *this; }
  item& operator= (double a)
  { kind=&typeid(double); x=a; return *this; }
  item& operator= (bool a)
  { kind=&typeid(bool); b=a; return *this; }

  template<class T>
  item(T *p)
    : kind(&typeid(T)), p((void *) p) {}

  template<class T>
  item(const T &p)
    : kind(&typeid(T)), p(new(UseGC) T(p)) {}

  template<class T>
  item& operator= (T *a)
  { kind=&typeid(T); p=(void *) a; return *this; }

  template<class T>
  item& operator= (const T &it)
  { kind=&typeid(T); p=new(UseGC) T(it); return *this; }

  const std::type_info &type() const
  { return *kind; }
#endif

  template<typename T>
  friend inline T get(const item&);

  friend inline bool isdefault(const item&);

  friend ostream& operator<< (ostream& out, const item& i);

private:
  template <typename T>
  struct help;

  template <typename T>
  struct help<T*> {
    static T* unwrap(const item& it)
    {
#if COMPACT
      if(!it.empty())
        return (T*) it.p;
#else
      if(*it.kind == typeid(T))
        return (T*) it.p;
#endif
      throw vm::bad_item_value();
    }
  };

  template <typename T>
  struct help {
    static T& unwrap(const item& it)
    {
#if COMPACT
      if(!it.empty())
        return *(T*) it.p;
#else
      if(*it.kind == typeid(T))
        return *(T*) it.p;
#endif
      throw vm::bad_item_value();
    }
  };
};

#ifdef SIMPLE_FRAME
// In the simple implementation, a frame is just an array of items.
typedef item vmFrame;
#else
class vmFrame : public gc {
#ifdef DEBUG_FRAME
  string name;
  size_t parentIndex;
#endif
  using internal_vars_t = mem::vector<item>;
  internal_vars_t vars;

  // Allow the stack direct access to vars.
  friend class stack;
public:
#ifdef DEBUG_FRAME
  vmFrame(string name, size_t parentIndex, size_t size)
    : name(name), parentIndex(parentIndex), vars(size)
  {}

  string getName() { return name; }

  size_t getParentIndex() { return parentIndex; }
#else
  vmFrame(size_t size)
    : vars(size)
  {}
#endif

  item& operator[] (size_t n)
  { return vars[n]; }
  item operator[] (size_t n) const
  { return vars[n]; }

  size_t size()
  { return vars.size(); }

  // Extends vars to ensure it has a place for any variable indexed up to n.
  void extend(size_t n) {
    if(vars.size() < n)
      vars.resize(n);
  }
};
#endif


template<typename T>
inline T get(const item& it)
{
  return item::help<T>::unwrap(it);
}

template <>
inline Int get<Int>(const item& it)
{
#if COMPACT
  if(!it.empty())
    return it.i;
#else
  if(*it.kind == typeid(Int))
    return it.i;
#endif
  throw vm::bad_item_value();
}

template <>
inline double get<double>(const item& it)
{
#if COMPACT
  if(!it.empty())
    return it.x;
#else
  if(*it.kind == typeid(double))
    return it.x;
#endif
  throw vm::bad_item_value();
}

template <>
inline bool get<bool>(const item& it)
{
#if COMPACT
  if(it.i == BoolTruthValue)
    return true;
  if(it.i == BoolFalseValue)
    return false;
#else
  if(*it.kind == typeid(bool))
    return it.b;
#endif
  throw vm::bad_item_value();
}

#if !COMPACT
// This serves as the object for representing a default argument.
struct default_t : public gc {};
#endif

inline bool isdefault(const item& it)
{
#if COMPACT
  return it.i == DefaultValue;
#else
  return *it.kind == typeid(default_t);
#endif
}

ostream& operator<< (ostream& out, const item& i);

} // namespace vm

#endif // ITEM_H
