/***************************************************************************
 *
 * Copyright (c) 2000, 2001 BalaBit IT Ltd, Budapest, Hungary
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: dimhash.c,v 1.2.2.2 2001/09/04 09:42:55 bazsi Exp $
 *
 * Author  : SaSa
 * Auditor :
 * Last version: 
 * Notes:
 *   - comments
 *
 ***************************************************************************/

#include <zorp/dimhash.h>
#include <zorp/log.h>

#include <string.h>

void
z_dim_hash_key_free(int num, gchar **key)
{
  int i;
  
  for (i = 0; i < num; i++)
    {
      if (key[i])
        g_free(key[i]);
    }
  free(key);
}

static gboolean
z_dim_hash_nextstep(gchar *key, guint flags)
{
  gboolean ret;
  
  z_enter();
  if (!flags)
    {
      z_leave();
      return FALSE;
    }
  
  if (*key == 0)
    {
      z_leave();
      return FALSE;
    }
  
  switch (flags)
    {
    case DIMHASH_WILDCARD:
      *key = 0;
      ret = TRUE;
      break;
    case DIMHASH_CONSUME:
      key[strlen(key)-1] = 0;
      ret = TRUE;
      break;
    default:
      ret = FALSE;
    }
  
  z_leave();
  return ret;
}
    
gboolean
z_dim_hash_table_makekey(gchar *new_key, guint key_len, ZDimHashTable *self, guint num, gchar **key_parts)
{
  guint keylen;
  guint i;
  
  z_enter();
  keylen = 0;
  for (i = 0; i < num; i++)
    keylen += strlen(key_parts[i]);
  
  memset(new_key, 0, key_len);
  
  if (keylen > key_len)
    return FALSE;
  
  if (key_parts[0][0] != 0 && (key_parts[0][1] != 0 || key_parts[0][0] != '*'))
    strcpy(new_key, key_parts[0]);

  for (i = 1; i < num; i++)
    {
      strcat(new_key, "::");
      if (key_parts[i][0] != 0 && (key_parts[i][1] != 0 || key_parts[i][0] != '*'))
        strcat(new_key, key_parts[i]);
    }
  
#if ENABLE_TRACE
  z_log(NULL, CORE_DEBUG, 7, "Key created; Key=`%s'", new_key);
#endif
  
  z_leave();
  return TRUE;
}

static gpointer *
z_dim_hash_table_rec_search(ZDimHashTable *self, guint num, guint i, gchar **keys, gchar **save_keys)
{
  gpointer *ret;
  gchar key[DIMHASH_MAX_KEYNUM * (DIMHASH_MAX_KEYSIZE + 2) + 1];
  guint keylen = DIMHASH_MAX_KEYNUM * (DIMHASH_MAX_KEYSIZE + 2) + 1;
  
  z_enter();
  if (i < num)
    {
      strcpy(keys[i], save_keys[i]);

      ret = z_dim_hash_table_rec_search(self, num, i + 1, keys, save_keys);
      
      while (!ret && z_dim_hash_nextstep(keys[i], self->flags[i]))
        {
	  ret = z_dim_hash_table_rec_search(self, num, i + 1, keys, save_keys);
	}
      
      z_leave();
      return ret;
    }
  else
    {
      if (z_dim_hash_table_makekey(key, keylen, self, num, keys))
        {
          if (key != NULL)
            {
              ret = g_hash_table_lookup(self->hash, key);
              z_leave();
              return ret;
            }
          else
            {
              z_leave();
              return NULL;
            }
        }
      else
        {
          z_leave();
          return NULL;
        }
    }
}

ZDimHashTable *
z_dim_hash_table_new(guint minnum, guint num, ...)
{
  int i;
  va_list l;
  ZDimHashTable *self = g_new0(ZDimHashTable, 1);
  
  z_enter();
  self->keynum = num;
  self->minkeynum = minnum;
  self->flags = g_new0(guint, num);
  va_start(l, num);
  for(i = 0; i < num; i++)
    self->flags[i] = va_arg(l, guint);
  
  va_end(l);
  self->hash = g_hash_table_new(g_str_hash, g_str_equal);
  
  z_leave();
  return self;
}

static gboolean
z_dim_hash_table_free_item(gpointer key, gpointer value, gpointer user_data)
{
  gboolean (*fn)(void *) = user_data;

  fn(value);
  g_free(key);
  return TRUE;
}

void
z_dim_hash_table_free(ZDimHashTable *self, ZDimHashFreeFunc func)
{
  z_enter();
  if (func)
    g_hash_table_foreach_remove(self->hash, z_dim_hash_table_free_item, func);
  
  g_hash_table_destroy(self->hash);
  g_free(self->flags);
  g_free(self);
  z_leave();
}

gpointer
z_dim_hash_table_lookup(ZDimHashTable *self, guint num, gchar **keys)
{
  gchar key[DIMHASH_MAX_KEYNUM * (DIMHASH_MAX_KEYSIZE + 2) + 1];
  guint keylen = DIMHASH_MAX_KEYNUM * (DIMHASH_MAX_KEYSIZE + 2) + 1;
  gpointer *ret = NULL;
  
  z_enter();
  if (self->minkeynum > num || self->keynum < num)
    {
      z_leave();
      return NULL;
    }
  
  if (z_dim_hash_table_makekey(key, keylen, self, num, keys))
    ret = g_hash_table_lookup(self->hash, key);

  z_leave();
  return ret;
}

void
z_dim_hash_table_delete(ZDimHashTable *self, guint num, gchar **keys, ZDimHashFreeFunc func)
{
  gchar key[DIMHASH_MAX_KEYNUM * (DIMHASH_MAX_KEYSIZE + 2) + 1];
  guint keylen = DIMHASH_MAX_KEYNUM * (DIMHASH_MAX_KEYSIZE + 2) + 1;
  gchar *orig_key;
  gpointer value;

  z_enter();
  if (self->keynum < num || self->minkeynum > num)
    {
      return;
    }
  
  if (z_dim_hash_table_makekey(key, keylen, self, num, keys))
    {
      if (g_hash_table_lookup_extended(self->hash, key, (gpointer *) &orig_key, &value))
        {
          g_hash_table_remove(self->hash, key);
          func(value);
          g_free(orig_key);
        }
    } 
  
  z_leave();
}

void
z_dim_hash_table_insert(ZDimHashTable *self, gpointer value, guint num, gchar **keys)
{
  gchar key[DIMHASH_MAX_KEYNUM * (DIMHASH_MAX_KEYSIZE + 2) + 1];
  guint keylen = DIMHASH_MAX_KEYNUM * (DIMHASH_MAX_KEYSIZE + 2) + 1;
  gchar *new_key;
  
  z_enter();
  if (self->keynum < num || self->minkeynum > num)
    {
      z_leave();
      return;
    }
  
  if (z_dim_hash_table_makekey(key, keylen, self, num, keys))
    {
      new_key = g_strdup(key);
      g_hash_table_insert(self->hash, new_key, value);
    }
  
  z_leave();
}

gpointer
z_dim_hash_table_search(ZDimHashTable *self, guint num, gchar **keys)
{
  gchar *save_keys[DIMHASH_MAX_KEYNUM];
  gpointer *ret;
  int i;

  z_enter();  
  if (self->keynum < num || self->minkeynum > num)
    {
      z_leave();
      return NULL;
    }
  
  for(i = 0; i < num; i++)
    {
      save_keys[i] = alloca(DIMHASH_MAX_KEYSIZE);
      strncpy(save_keys[i], keys[i], DIMHASH_MAX_KEYSIZE - 1);
      save_keys[i][DIMHASH_MAX_KEYSIZE-1] = 0;
    }

  ret = z_dim_hash_table_rec_search(self, num, 0, save_keys, keys);

  z_leave();
  return ret;
}
