/*
 *  This file is part of the KDE libraries
 *  Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "collector.h"
#include "object.h"
#include "internal.h"

#include <stdio.h>
#include <string.h>
#include <assert.h>

namespace KJS {

  class CollectorBlock {
  public:
    CollectorBlock(int s);
    ~CollectorBlock();
    int size;
    int filled;
    void** mem;
    CollectorBlock *prev, *next;
  };

}; // namespace

using namespace KJS;

CollectorBlock::CollectorBlock(int s)
  : size(s),
    filled(0),
    prev(0L),
    next(0L)
{
  mem = new void*[size];
  memset(mem, 0, size * sizeof(void*));
}

CollectorBlock::~CollectorBlock()
{
  delete [] mem;
  mem = 0L;
}

CollectorBlock* Collector::root = 0L;
CollectorBlock* Collector::currentBlock = 0L;
int Collector::filled = 0;
#ifdef KJS_DEBUG_MEM
bool Collector::collecting = false;
#endif

void* Collector::allocate(size_t s)
{
  if (s == 0)
    return 0L;

  void *m = malloc(s);

  if (!root) {
    root = new CollectorBlock(BlockSize);
    currentBlock = root;
  }

  CollectorBlock *block = currentBlock;
  if (!block)
    block = root;

  // search for a block with space left
  while (block->next && block->filled == block->size)
    block = block->next;

  if (block->filled >= block->size) {
#ifdef KJS_DEBUG_MEM
    printf("allocating new block of size %d\n", block->size);
#endif
    CollectorBlock *tmp = new CollectorBlock(BlockSize);
    block->next = tmp;
    tmp->prev = block;
    block = tmp;
  }
  currentBlock = block;
  // look for a free spot in the block
  void **r = block->mem;
  while (*r)
    r++;
  *r = m;
  filled++;
  block->filled++;

#if KJS_MEM_LIMIT != -1
  if (filled >= KJS_MEM_LIMIT) {
      KJScriptImp::setException("Out of memory");
  }
#endif

  return m;
}

/**
 * Mark-sweep garbage collection.
 */
void Collector::collect()
{
#ifdef KJS_DEBUG_MEM
  printf("collecting %d objects total\n", Imp::count);
  collecting = true;
#endif

  // MARK: first set all ref counts to 0 ....
  CollectorBlock *block = root;
  while (block) {
#ifdef KJS_DEBUG_MEM
    printf("cleaning block filled %d out of %d\n", block->filled, block->size);
#endif
    Imp **r = (Imp**)block->mem;
    assert(r);
    for (int i = 0; i < block->size; i++, r++)
      if (*r)
	(*r)->refcount = 0;
    block = block->next;
  }
  // ... increase counter for all referenced objects recursively
  // starting out from the set of root objects
  if (KJScriptImp::hook) {
    KJScriptImp *scr = KJScriptImp::hook;
    do {
      scr->mark();
      scr = scr->next;
    } while (scr != KJScriptImp::hook);
  }

  // SWEEP: delete everything with a zero refcount (garbage)
  block = root;
  while (block) {
    Imp **r = (Imp**)block->mem;
    int del = 0;
    for (int i = 0; i < block->size; i++, r++) {
      if (*r && ((*r)->refcount == 0)) {
	// emulate 'operator delete()'
	(*r)->~Imp();
	free(*r);
	*r = 0L;
	del++;
      }
    }
    filled -= del;
    block->filled -= del;
    block = block->next;
  }

  // delete the emtpy containers
  block = root;
  while (block) {
    CollectorBlock *next = block->next;
    if (block->filled == 0) {
      if (block->prev)
	block->prev->next = next;
      if (block == root)
	root = next;
      if (next)
	next->prev = block->prev;
      if (block == currentBlock) // we don't want a dangling pointer
	currentBlock = 0L;
      assert(block != root);
      delete block;
    }
    block = next;
  }

#ifdef KJS_DEBUG_MEM
  collecting = false;
#endif
}
