/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */

/*
 *  Main authors:
 *     Guido Tack <guido.tack@monash.edu>
 */

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#pragma once

#include <minizinc/config.hh>
#include <minizinc/timer.hh>

#include <cassert>
#include <cstdlib>
#include <new>
#include <unordered_map>

// #define MINIZINC_GC_STATS

#ifdef MINIZINC_GC_STATS
#include <map>

struct GCStat {
  int first;
  int second;
  int keepalive;
  int inmodel;
  size_t total;
  GCStat() : first(0), second(0), keepalive(0), inmodel(0), total(0) {}
};

#endif

namespace MiniZinc {

/**
 * \brief Base class for abstract syntax tree nodes
 */
class ASTNode {
  friend class GC;

protected:
  /// Mark for garbage collection
  mutable unsigned int _gcMark : 1;
  /// Mark for garbage collection of VarDecls
  mutable unsigned int _vdGcMark : 1;
  /// Id of the node
  unsigned int _id : 7;
  /// Secondary id
  unsigned int _secondaryId : 7;
  /// Flag
  unsigned int _flag1 : 1;
  /// Flag
  unsigned int _flag2 : 1;

  enum BaseNodes { NID_FL, NID_CHUNK, NID_VEC, NID_STR, NID_END = NID_STR };

  /// Constructor
  ASTNode(unsigned int id) : _gcMark(0), _vdGcMark(0), _id(id) {}

public:
  /// Allocate node
  void* operator new(size_t size);

  /// Placement-new
  void* operator new(size_t /*s*/, void* n) throw() { return n; }

  /// Delete node (no-op)
  void operator delete(void* /*n*/, size_t /*s*/) throw() {}
  /// Delete node (no-op)
  void operator delete(void* /*n*/, void* /*m*/) throw() {}

  /// Delete node (no-op)
  void operator delete(void* /*n*/) throw() {}
};

/**
 * \brief Base class for unstructured garbage collected data
 */
class ASTChunk : public ASTNode {
  friend class GC;

protected:
  /// Allocated size
  size_t _size;
  /// Storage
  char _data[4];
  /// Constructor
  ASTChunk(size_t size, unsigned int id = ASTNode::NID_CHUNK);
  /// Actual size of object in memory
  size_t memsize() const {
    size_t s = sizeof(ASTChunk) + (_size <= 4 ? 0 : _size - 4) * sizeof(char);
    s += ((8 - (s & 7)) & 7);
    return s;
  }
  /// Allocate raw memory
  static void* alloc(size_t size);
};

/**
 * \brief Base class for structured garbage collected data
 */
class ASTVec : public ASTNode {
  friend class GC;

protected:
  /// Allocated size
  size_t _size;
  /// Storage
  void* _data[2];
  /// Constructor
  ASTVec(size_t size);
  /// Actual size of object in memory
  size_t memsize() const {
    size_t s = sizeof(ASTVec) + (_size <= 2 ? 0 : _size - 2) * sizeof(void*);
    s += ((8 - (s & 7)) & 7);
    return s;
  }
  /// Allocate raw memory
  static void* alloc(size_t size);
};

class Expression;

class GCMarker;
class KeepAlive;
class WeakRef;

class ASTNodeWeakMap;
class ASTStringData;

/// Garbage collector
class GC {
  friend class ASTNode;
  friend class ASTVec;
  friend class ASTChunk;
  friend class ASTStringData;
  friend class KeepAlive;
  friend class WeakRef;
  friend class ASTNodeWeakMap;

private:
  class Heap;
  /// The memory controlled by the collector
  Heap* _heap;
  /// Count how many locks are currently active
  unsigned int _lockCount;
  /// Return thread-local GC object
  static GC*& gc();
  /// Constructor
  GC();

  /// Allocate garbage collected memory
  void* alloc(size_t size);

  static void addKeepAlive(KeepAlive* e);
  static void removeKeepAlive(KeepAlive* e);
  static void addWeakRef(WeakRef* e);
  static void removeWeakRef(WeakRef* e);
  static void addNodeWeakMap(ASTNodeWeakMap* m);
  static void removeNodeWeakMap(ASTNodeWeakMap* m);

public:
  /// Acquire garbage collector lock for this thread
  static void lock();
  /// Acquire garbage collector lock for this thread and do not run GC
  static void lockNoGC();
  /// Release garbage collector lock for this thread
  static void unlock();
  /// Manually trigger garbage collector (must be unlocked)
  static void trigger();
  /// Test if garbage collector is locked
  static bool locked();
  /// Add model \a m to root set
  static void add(GCMarker* m);
  /// Remove model \a m from root set
  static void remove(GCMarker* m);

  /// Put a mark on the trail
  static void mark();
  /// Add a trail entry
  static void trail(Expression** l, Expression* v);
  /// Untrail to previous mark
  static void untrail();

  /// Set timeout of \a t milliseconds, 0 means disable
  static void setTimeout(unsigned long long int t);

  /// Return maximum allocated memory (high water mark)
  static size_t maxMem();

#ifdef MINIZINC_GC_STATS
  /// Return statistics object
  static std::map<int, GCStat>& stats();
#endif
};

/// Automatic garbage collection lock
class GCLock {
public:
  /// Acquire lock
  GCLock();
  /// Release lock upon destruction
  ~GCLock();
};

/// Expression wrapper that is a member of the root set
class KeepAlive {
  friend class GC;

private:
  Expression* _e;
  KeepAlive* _p;
  KeepAlive* _n;

public:
  KeepAlive(Expression* e = nullptr);
  ~KeepAlive();
  KeepAlive(const KeepAlive& e);
  KeepAlive& operator=(const KeepAlive& e);
  Expression* operator()() { return _e; }
  Expression* operator()() const { return _e; }
  KeepAlive* next() const { return _n; }
};

/// Expression wrapper that is a member of the root set
class WeakRef {
  friend class GC;

private:
  Expression* _e;
  WeakRef* _p;
  WeakRef* _n;

public:
  WeakRef(Expression* e = nullptr);
  ~WeakRef();
  WeakRef(const WeakRef& e);
  WeakRef& operator=(const WeakRef& e);
  Expression* operator()();
  Expression* operator()() const;
  WeakRef* next() const { return _n; }
};

class ASTNodeWeakMap {
  friend class GC;

private:
  ASTNodeWeakMap(const ASTNodeWeakMap& e) = delete;
  ASTNodeWeakMap& operator=(const ASTNodeWeakMap& e) = delete;

protected:
  typedef std::unordered_map<ASTNode*, ASTNode*> NodeMap;
  ASTNodeWeakMap* _p;
  ASTNodeWeakMap* _n;
  ASTNodeWeakMap* next() const { return _n; }
  NodeMap _m;

public:
  ASTNodeWeakMap();
  ~ASTNodeWeakMap();
  void insert(ASTNode* n0, ASTNode* n1);
  ASTNode* find(ASTNode* n);
  void clear() { _m.clear(); }
};

/**
 * \brief Abstract base class for object containing garbage collected data
 */
class GCMarker {
  friend class GC;

private:
  /// Previous object in root set list
  GCMarker* _rootsPrev = nullptr;
  /// Next object in root set list
  GCMarker* _rootsNext = nullptr;

protected:
  /// Mark garbage collected objects that
  virtual void mark() = 0;

  virtual void fixWeakRefs() {}

public:
  GCMarker() { GC::add(this); }
  GCMarker(const GCMarker& marker) { GC::add(this); }
  // NOLINTNEXTLINE(bugprone-unhandled-self-assignment)
  GCMarker& operator=(const GCMarker& marker) { return *this; }
  virtual ~GCMarker() { GC::remove(this); }
};

}  // namespace MiniZinc
