// Probabilistic reachability set storage -*- c++ -*-

#ifdef __GNUC__
# pragma implementation
#endif // __GNUC__
#include "HashGraph.h"
#include "ByteBuffer.h"
#include <stdlib.h>
#include <assert.h>
#include <time.h>

/** @file HashGraph.C
 * Transient, probabilistic reachability set storage
 */

/* Copyright © 2001-2002 Marko Mäkelä (msmakela@tcs.hut.fi).

   This file is part of MARIA, a reachability analyzer and model checker
   for high-level Petri nets.

   MARIA 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 2, or (at your option)
   any later version.

   MARIA 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.

   The GNU General Public License is often shipped with GNU software, and
   is generally kept in a file called COPYING or LICENSE.  If you do not
   have a copy of the license, write to the Free Software Foundation,
   59 Temple Place, Suite 330, Boston, MA 02111 USA. */

/** Number of bits size_t holds */
#define SIZE_T_BIT (sizeof (size_t) * CHAR_BIT)

/** primes[x] == prime closest to 32 << x */
static unsigned
primes[] = {
  0x1f,
  0x3d,
  0x7f,
  0x101,
  0x1fd,
  0x3fd,
  0x805,
  0xffd,
  0x1fff,
  0x3ffd,
  0x8003,
  0xfff1,
  0x1ffff,
  0x3fffb,
  0x7ffff,
  0xffffd,
  0x1ffff7,
  0x3ffffd,
  0x7ffff1,
  0xfffffd,
  0x2000023,
  0x3fffffb,
  0x800001d,
  0x10000003,
  0x1ffffffd,
  0x40000003,
  0x7fffffff,
  0xfffffffb
};

HashGraph::~HashGraph ()
{
  for (unsigned s = myNumTables; s--; ) {
    free (myHashes[s]);
    free (myHashers[s]);
  }
  free (myHashes);
  free (myHashers);
}

bool
HashGraph::init (unsigned numTables,
		 unsigned& funcSize,
		 unsigned& hashSize)
{
  assert (!myHashes && !myHashers && !myNumTables && !getNumStates ());
  if (!openFile ())
    return false;
  unsigned s;
  /* round the hash function size to the next power of 2 */
  for (s = 2; s < funcSize && sizeof (size_t) * s << 1; s <<= 1);
  funcSize = s;
  /* round the hash table size to the closest prime in our table */
  for (s = 0; s < ((sizeof primes) / sizeof *primes) - 1; s++)
    if (primes[s] >= hashSize)
      break;
  hashSize = primes[s];
  s = 1 + (hashSize - 1) / SIZE_T_BIT;

  /* allocate the tables */
  if (!(myHashers =
	static_cast<size_t**>(calloc (numTables, sizeof *myHashers))) ||
      !(myHashes =
	static_cast<size_t**>(calloc (numTables, sizeof *myHashes)))) {
  failure:
    if (myHashers) free (myHashers), myHashers = 0;
    if (myHashes) free (myHashes), myHashes = 0;
    return false;
  }

  unsigned t;
  for (t = numTables; t--; ) {
    if (!(myHashers[t] =
	  static_cast<size_t*>(calloc (funcSize, sizeof *myHashers[t]))) ||
	!(myHashes[t] =
	  static_cast<size_t*>(calloc (s, sizeof *myHashes[t])))) {
      for (; t < numTables; t++)
	free (myHashers[t]), free (myHashes[t]);
      goto failure;
    }
  }

  /* Initialize the tables for the hash functions */
  srand (time (0));
  for (t = numTables; t--; )
    for (s = funcSize; s--; )
      myHashers[t][s] =	size_t ((hashSize + double (1)) * rand () /
				(RAND_MAX + double (1)));
  myNumTables = numTables;
  myFuncSize = funcSize - 1;
  myHashSize = hashSize;
  return true;
}

bool
HashGraph::do_add (const void* buf,
		   size_t size)
{
  /** flag: was the state found? */
  bool found = true;
  /** the encoded byte stream */
  const char* state = reinterpret_cast<const char*>(buf);

  for (unsigned t = myNumTables; t--; ) {
    /* compute the hash value */
    size_t h = 0;
    const size_t* hasher = myHashers[t];
    for (size_t s = size; s--; )
      h += state[s] * hasher[s & myFuncSize];
    /* test and set the corresponding bit in the hash table */
    h %= myHashSize;
    size_t& ht = myHashes[t][h / SIZE_T_BIT];
    size_t mask = size_t (1) << (h % SIZE_T_BIT);
    if (!(ht & mask))
      ht |= mask, found = false;
  }

  if (found)
    return false;

  newState ();

  assert (myPathFileLength == ftell (myPathFile));
  assert (!myOffset || myOffset < myPathFileLength);

  mySearch.push (buf, size, myPathFileLength);
  class BytePacker p;
  p.append (myOffset), p.append (size);
  fwrite (p.getBuf (), 1, p.getLength (), myPathFile);
  fwrite (buf, 1, size, myPathFile);
  myPathFileLength += p.getLength () + size;
  return true;
}

word_t*
HashGraph::getState (long pos, size_t* size) const
{
  unsigned char rbuf[8];
  class ByteUnpacker u (rbuf);
  assert (pos < myPathFileLength);
  fseek (myPathFile, pos, SEEK_SET);
  fread (rbuf, sizeof rbuf, 1, myPathFile);
  unsigned offset = u.extract ();
  unsigned len = u.extract (); u.buf = rbuf;
  word_t* state = new word_t[len + (sizeof (word_t) - 1) / sizeof (word_t)];
  fseek (myPathFile,
	 pos + BytePacker::size (offset) + BytePacker::size (len), SEEK_SET);
  fread (state, 1, len, myPathFile);
  *size = len;
  return state;
}

word_t*
HashGraph::pop (bool tail, size_t& size)
{
  if (mySearch.empty ())
    return 0;
  return mySearch.pop (tail, myOffset, &size);
}
