// OpenSTA, Static Timing Analyzer
// Copyright (c) 2026, Parallax Software, Inc.
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// 
// The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software.
// 
// Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 
// This notice may not be removed or altered from any source distribution.

#pragma once

#include <vector>

#include "ContainerHelpers.hh"
#include "Error.hh"
#include "ObjectId.hh"

namespace sta {

template <class OBJECT>
class TableBlock;

// Object tables allocate objects in blocks and use 32 bit IDs to
// reference an object. Paging performance is improved by allocating
// blocks instead of individual objects, and object sizes are reduced
// by using 32 bit references instead of 64 bit pointers.
//
// Class TYPE must define member functions
//  ObjectIdx objectIdx() const
//  void setObjectIdx(ObjectIdx idx)
// to get/set the index of the object in a block, which can be a bit
// field ObjectTable::idx_bits (7 bits) wide.

template <class TYPE>
class ObjectTable
{
public:
  ~ObjectTable();
  TYPE *make();
  void destroy(TYPE *object);
  TYPE *pointer(ObjectId id) const;
  TYPE &ref(ObjectId id) const;
  ObjectId objectId(const TYPE *object);
  size_t size() const { return size_; }
  void clear();

  // Objects are allocated in blocks of 128.
  static constexpr int idx_bits = 7;
  static constexpr int block_object_count = (1 << idx_bits);
  static constexpr int block_id_max = 1 << (object_id_bits - idx_bits);

private:
  void makeBlock();
  void freePush(TYPE *object,
                ObjectId id);

  size_t size_{0};
  // Object ID of next free object.
  ObjectId free_{object_id_null};
  std::vector<TableBlock<TYPE>*> blocks_;
  static constexpr ObjectId idx_mask_ = block_object_count - 1;
};

template <class TYPE>
ObjectTable<TYPE>::~ObjectTable()
{
  deleteContents(blocks_);
}

template <class TYPE>
TYPE *
ObjectTable<TYPE>::make()
{
  if (free_ == object_id_null)
    makeBlock();
  TYPE *object = pointer(free_);
  ObjectIdx idx = free_ & idx_mask_;
  object->setObjectIdx(idx);
  ObjectId *free_next = reinterpret_cast<ObjectId*>(object);
  free_ = *free_next;
  size_++;
  return object;
}

template <class TYPE>
void
ObjectTable<TYPE>::freePush(TYPE *object,
                            ObjectId id)
{
  // Link free objects into a list linked by Object ID.
  ObjectId *free_next = reinterpret_cast<ObjectId*>(object);
  *free_next = free_;
  free_ = id;
}

template <class TYPE>
void
ObjectTable<TYPE>::makeBlock()
{
  BlockIdx block_index = blocks_.size();
  TableBlock<TYPE> *block = new TableBlock<TYPE>(block_index, this);
  blocks_.push_back(block);
  if (blocks_.size() >= block_id_max)
    criticalError(224, "max object table block count exceeded.");
  // ObjectId zero is reserved for object_id_null.
  int last = (block_index > 0) ? 0 : 1;
  for (int i = block_object_count - 1; i >= last; i--) {
    TYPE *obj = block->pointer(i);
    ObjectId id = (block_index << idx_bits) + i;
    freePush(obj, id);
  }
}

template <class TYPE>
TYPE *
ObjectTable<TYPE>::pointer(ObjectId id) const
{
  if (id == object_id_null)
    return nullptr;
  else {
    BlockIdx blk_idx = id >> idx_bits;
    ObjectIdx obj_idx = id & idx_mask_;
    return blocks_[blk_idx]->pointer(obj_idx);
  }
}

template <class TYPE>
TYPE &
ObjectTable<TYPE>::ref(ObjectId id) const
{
  if (id == object_id_null)
    criticalError(225, "null ObjectId reference is undefined.");
  else {
    BlockIdx blk_idx = id >> idx_bits;
    ObjectIdx obj_idx = id & idx_mask_;
    return blocks_[blk_idx]->ptr(obj_idx);
  }
}

template <class TYPE>
ObjectId
ObjectTable<TYPE>::objectId(const TYPE *object)
{
  ObjectIdx idx = object->objectIdx();
  const TableBlock<TYPE> *blk =
    reinterpret_cast<const TableBlock<TYPE>*>(object - idx);
  return (blk->index() << idx_bits) + idx;
}

template <class TYPE>
void
ObjectTable<TYPE>::destroy(TYPE *object)
{
  ObjectId object_id = objectId(object);
  size_--;
  freePush(object, object_id);
}

template <class TYPE>
void
ObjectTable<TYPE>::clear()
{
  deleteContents(blocks_);;
  size_ = 0;
}

////////////////////////////////////////////////////////////////

template <class TYPE>
class TableBlock
{
public:
  TableBlock(BlockIdx block_idx,
             ObjectTable<TYPE> *table);
  BlockIdx index() const { return block_idx_; }
  TYPE &ref(ObjectIdx idx) { return objects_[idx]; }
  TYPE *pointer(ObjectIdx idx) { return &objects_[idx]; }

private:
  TYPE objects_[ObjectTable<TYPE>::block_object_count];
  BlockIdx block_idx_;
  ObjectTable<TYPE> *table_;
};

template <class TYPE>
TableBlock<TYPE>::TableBlock(BlockIdx block_idx,
                             ObjectTable<TYPE> *table) :
  block_idx_(block_idx),
  table_(table)
{
}

} // namespace sta
