/*
 * Copyright (c) 2007, Novell Inc.
 *
 * This program is licensed under the BSD license, read LICENSE.BSD
 * for further information
 */

/*
 * poolid.c
 *
 * Id management
 */

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

#include "pool.h"
#include "poolid.h"
#include "poolid_private.h"
#include "util.h"


/* intern string into pool, return id */

Id
pool_str2id(Pool *pool, const char *str, int create)
{
  int oldnstrings = pool->ss.nstrings;
  Id id = stringpool_str2id(&pool->ss, str, create);
  if (create && pool->whatprovides && oldnstrings != pool->ss.nstrings && (id & WHATPROVIDES_BLOCK) == 0)
    {
      /* grow whatprovides array */
      pool->whatprovides = solv_realloc(pool->whatprovides, (id + (WHATPROVIDES_BLOCK + 1)) * sizeof(Offset));
      memset(pool->whatprovides + id, 0, (WHATPROVIDES_BLOCK + 1) * sizeof(Offset));
    }
  return id;
}

Id
pool_strn2id(Pool *pool, const char *str, unsigned int len, int create)
{
  int oldnstrings = pool->ss.nstrings;
  Id id = stringpool_strn2id(&pool->ss, str, len, create);
  if (create && pool->whatprovides && oldnstrings != pool->ss.nstrings && (id & WHATPROVIDES_BLOCK) == 0)
    {
      /* grow whatprovides array */
      pool->whatprovides = solv_realloc(pool->whatprovides, (id + (WHATPROVIDES_BLOCK + 1)) * sizeof(Offset));
      memset(pool->whatprovides + id, 0, (WHATPROVIDES_BLOCK + 1) * sizeof(Offset));
    }
  return id;
}

void
pool_resize_rels_hash(Pool *pool, int numnew)
{
  Hashval h, hh, hashmask;
  Hashtable hashtbl;
  int i;
  Reldep *rd;

  if (numnew <= 0)
    return;
  hashmask = mkmask(pool->nrels + numnew);
  if (hashmask <= pool->relhashmask)
    return;    /* same as before */

  /* realloc hash table */
  pool->relhashmask = hashmask;
  solv_free(pool->relhashtbl);
  pool->relhashtbl = hashtbl = solv_calloc(hashmask + 1, sizeof(Id));

  /* rehash all rels into new hashtable */
  for (i = 1, rd = pool->rels + i; i < pool->nrels; i++, rd++)
    {
      h = relhash(rd->name, rd->evr, rd->flags) & hashmask;
      hh = HASHCHAIN_START;
      while (hashtbl[h])
	h = HASHCHAIN_NEXT(h, hh, hashmask);
      hashtbl[h] = i;
    }
}

Id
pool_rel2id(Pool *pool, Id name, Id evr, int flags, int create)
{
  Hashval h, hh, hashmask;
  Id id;
  Hashtable hashtbl;
  Reldep *ran;


  /* extend hashtable if needed */
  hashmask = pool->relhashmask;
  if ((Hashval)pool->nrels * 2 > hashmask)
    {
      pool_resize_rels_hash(pool, REL_BLOCK);
      hashmask = pool->relhashmask;
    }
  hashtbl = pool->relhashtbl;

  /* compute hash and check for match */
  h = relhash(name, evr, flags) & hashmask;
  hh = HASHCHAIN_START;
  ran = pool->rels;
  while ((id = hashtbl[h]) != 0)
    {
      if (ran[id].name == name && ran[id].evr == evr && ran[id].flags == flags)
	break;
      h = HASHCHAIN_NEXT(h, hh, hashmask);
    }
  if (id)
    return MAKERELDEP(id);

  if (!create)
    return ID_NULL;

  id = pool->nrels++;
  /* extend rel space if needed */
  pool->rels = solv_extend(pool->rels, id, 1, sizeof(Reldep), REL_BLOCK);
  hashtbl[h] = id;
  ran = pool->rels + id;
  ran->name = name;
  ran->evr = evr;
  ran->flags = flags;

  /* extend whatprovides_rel if needed */
  if (pool->whatprovides_rel && (id & WHATPROVIDES_BLOCK) == 0)
    {
      pool->whatprovides_rel = solv_realloc2(pool->whatprovides_rel, id + (WHATPROVIDES_BLOCK + 1), sizeof(Offset));
      memset(pool->whatprovides_rel + id, 0, (WHATPROVIDES_BLOCK + 1) * sizeof(Offset));
    }
  return MAKERELDEP(id);
}


/* Id -> String
 * for rels (returns name only) and strings
 */
const char *
pool_id2str(const Pool *pool, Id id)
{
  while (ISRELDEP(id))
    {
      Reldep *rd = GETRELDEP(pool, id);
      id = rd->name;
    }
  return pool->ss.stringspace + pool->ss.strings[id];
}

static const char *rels[] = {
  " ! ",
  " > ",
  " = ",
  " >= ",
  " < ",
  " <> ",
  " <= ",
  " <=> "
};


/* get operator for RelId */
const char *
pool_id2rel(const Pool *pool, Id id)
{
  Reldep *rd;
  if (!ISRELDEP(id))
    return "";
  rd = GETRELDEP(pool, id);

  switch (rd->flags)
    {
    /* debian special cases < and > */
    /* haiku special cases <> (maybe we should use != for the others as well */
    case 0: case REL_EQ: case REL_GT | REL_EQ:
    case REL_LT | REL_EQ: case REL_LT | REL_EQ | REL_GT:
#if !defined(DEBIAN) && !defined(MULTI_SEMANTICS)
    case REL_LT: case REL_GT:
#endif
#if !defined(HAIKU) && !defined(MULTI_SEMANTICS)
    case REL_LT | REL_GT:
#endif
      return rels[rd->flags];
#if defined(DEBIAN) || defined(MULTI_SEMANTICS)
    case REL_GT:
      return pool->disttype == DISTTYPE_DEB ? " >> " : rels[rd->flags];
    case REL_LT:
      return pool->disttype == DISTTYPE_DEB ? " << " : rels[rd->flags];
#endif
#if defined(HAIKU) || defined(MULTI_SEMANTICS)
    case REL_LT | REL_GT:
      return pool->disttype == DISTTYPE_HAIKU ? " != " : rels[rd->flags];
#endif
    case REL_AND:
      return pool->disttype == DISTTYPE_RPM ? " and " : " & ";
    case REL_OR:
      return pool->disttype == DISTTYPE_RPM ? " or " : " | ";
    case REL_WITH:
      return pool->disttype == DISTTYPE_RPM ? " with " : " + ";
    case REL_WITHOUT:
      return pool->disttype == DISTTYPE_RPM ? " without " : " - ";
    case REL_NAMESPACE:
      return " NAMESPACE ";	/* actually not used in dep2str */
    case REL_ARCH:
      return ".";
    case REL_MULTIARCH:
      return ":";
    case REL_FILECONFLICT:
      return " FILECONFLICT ";
    case REL_COND:
      return pool->disttype == DISTTYPE_RPM ? " if " : " IF ";
    case REL_UNLESS:
      return pool->disttype == DISTTYPE_RPM ? " unless " : " UNLESS ";
    case REL_COMPAT:
      return " compat >= ";
    case REL_KIND:
      return " KIND ";
    case REL_ELSE:
      return pool->disttype == DISTTYPE_RPM ? " else " : " ELSE ";
    case REL_CONDA:
      return " ";
    case REL_ERROR:
      return " ERROR ";
    default:
      break;
    }
  return " ??? ";
}


/* get e:v.r for Id */
const char *
pool_id2evr(const Pool *pool, Id id)
{
  Reldep *rd;
  if (!ISRELDEP(id))
    return "";
  rd = GETRELDEP(pool, id);
  if (ISRELDEP(rd->evr))
    return "(REL)";
  return pool->ss.stringspace + pool->ss.strings[rd->evr];
}

static int
dep2strlen(const Pool *pool, Id id)
{
  int l = 0;

  while (ISRELDEP(id))
    {
      Reldep *rd = GETRELDEP(pool, id);
      /* add 2 for parens */
      l += 2 + dep2strlen(pool, rd->name) + strlen(pool_id2rel(pool, id));
      id = rd->evr;
    }
  return l + strlen(pool->ss.stringspace + pool->ss.strings[id]);
}

static void
dep2strcpy(const Pool *pool, char *p, Id id, int oldrel)
{
  while (ISRELDEP(id))
    {
      Reldep *rd = GETRELDEP(pool, id);
      int rel = rd->flags;
      if (oldrel == REL_AND || oldrel == REL_OR || oldrel == REL_WITH || oldrel == REL_WITHOUT || oldrel == REL_COND || oldrel == REL_UNLESS || oldrel == REL_ELSE || oldrel == -1)
	if (rel == REL_AND || rel == REL_OR || rel == REL_WITH || rel == REL_WITHOUT || rel == REL_COND || rel == REL_UNLESS || rel == REL_ELSE)
	  if ((oldrel != rel || rel == REL_COND || rel == REL_UNLESS || rel == REL_ELSE) && !((oldrel == REL_COND || oldrel == REL_UNLESS) && rel == REL_ELSE))
	    {
	      *p++ = '(';
	      dep2strcpy(pool, p, rd->name, rd->flags);
	      p += strlen(p);
	      strcpy(p, pool_id2rel(pool, id));
	      p += strlen(p);
	      dep2strcpy(pool, p, rd->evr, rd->flags);
	      strcat(p, ")");
	      return;
	    }
      if (rd->flags == REL_KIND)
	{
	  dep2strcpy(pool, p, rd->evr, rd->flags);
	  p += strlen(p);
	  *p++ = ':';
	  id = rd->name;
	  oldrel = rd->flags;
	  continue;
	}
      dep2strcpy(pool, p, rd->name, rd->flags);
      p += strlen(p);
      if (rd->flags == REL_NAMESPACE)
	{
	  *p++ = '(';
	  dep2strcpy(pool, p, rd->evr, rd->flags);
	  strcat(p, ")");
	  return;
	}
      if (rd->flags == REL_FILECONFLICT)
	{
	  *p = 0;
	  return;
	}
      strcpy(p, pool_id2rel(pool, id));
      p += strlen(p);
      id = rd->evr;
      oldrel = rd->flags;
    }
  strcpy(p, pool->ss.stringspace + pool->ss.strings[id]);
}

const char *
pool_dep2str(Pool *pool, Id id)
{
  char *p;
  if (!ISRELDEP(id))
    return pool->ss.stringspace + pool->ss.strings[id];
  p = pool_alloctmpspace(pool, dep2strlen(pool, id) + 1);
  dep2strcpy(pool, p, id, pool->disttype == DISTTYPE_RPM ? -1 : 0);
  return p;
}

static void
pool_free_rels_hash(Pool *pool)
{
  pool->relhashtbl = solv_free(pool->relhashtbl);
  pool->relhashmask = 0;
}

void
pool_shrink_strings(Pool *pool)
{
  /* free excessive big hashes */
  if (pool->ss.stringhashmask && pool->ss.stringhashmask > mkmask(pool->ss.nstrings + 8192))
    stringpool_freehash(&pool->ss);
  stringpool_shrink(&pool->ss);
}

void
pool_shrink_rels(Pool *pool)
{
  /* free excessive big hashes */
  if (pool->relhashmask && pool->relhashmask > mkmask(pool->nrels + 4096))
    pool_free_rels_hash(pool);
  pool->rels = solv_extend_resize(pool->rels, pool->nrels, sizeof(Reldep), REL_BLOCK);
}

/* free all hash tables */
void
pool_freeidhashes(Pool *pool)
{
  stringpool_freehash(&pool->ss);
  pool_free_rels_hash(pool);
}

/* EOF */
