/***************************************************************************
 *
 * 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: pyvars.c,v 1.18.2.1.2.3 2001/09/19 19:22:17 bazsi Exp $
 *
 * Author  : Bazsi
 * Auditor : kisza
 * Last version: 1.9
 * Notes:
 *   - comments
 *
 ***************************************************************************/

#include <zorp/pyvars.h>
#include <zorp/policy.h>
#include <zorp/log.h>
#include <zorp/pysockaddr.h>
#include <zorp/dimhash.h>

typedef struct _ZPyVarDesc
{
  gchar *name;
  guint flags;
  gpointer value;
  void (*free)(gpointer value);
  ZProxySetVarFunc setvar;
  ZProxyGetVarFunc getvar;
} ZPyVarDesc;

gint
z_pyvars_setattr(ZProxy *self, gchar *name, PyObject *new)
{
  ZPyVarDesc *p;

  if (!self->vars)
    {
      return 0;
    }  
  p = g_hash_table_lookup(self->vars, name);
  
  z_trace(self->session_id, "pyvar_setattr, name=%s", name);
  if (p)
    {

      if (((ZPROXY_GET_STATE(self) == PS_CONFIG) && (p->flags & Z_VAR_SET_CONFIG)) ||
	  ((ZPROXY_GET_STATE(self) != PS_CONFIG) && (p->flags & Z_VAR_SET)))
	{
	  return p->setvar(self, name, p->value, new);
	}
      else
	{
	  PyErr_SetString(PyExc_AttributeError, "SET access is not allowed for this attribute");
	  /* FIXME: correct error handling */
	  /*LOG
	    SET access for the given attribute is forbidden. You might be
	    trying to set a config-only attribute outside of the config event,
	    or set a read-only attribute.
	   */
	  z_log(self->session_id, CORE_POLICY, 3, "SET access is not allowed; attribute='%s'", name);
	}
    }
  return 0;
}

PyObject *
z_pyvars_getattr(ZProxy *self, gchar *name)
{
  ZPyVarDesc *p;
  
  if (!self->vars)
    {
      PyErr_SetString(PyExc_AttributeError, "Attributes of freed instances are not available.");
      return NULL;
    }
    
  p = g_hash_table_lookup(self->vars, name);

  if (p)
    {
      if (((ZPROXY_GET_STATE(self) == PS_CONFIG) && (p->flags & Z_VAR_GET_CONFIG)) ||
	  ((ZPROXY_GET_STATE(self) != PS_CONFIG) && (p->flags & Z_VAR_GET)))
	{
	  return p->getvar(self, name, p->value);
	}
      else
	{
	  PyErr_SetString(PyExc_AttributeError, "GET access is not allowed for this attribute");
	  /*LOG
	    GET access for the given attribute is not permitted at this time.
	   */
	  z_log(self->session_id, CORE_POLICY, 3, "GET access is not allowed; attribute='%s'", name);
	}
    }
  return NULL;
}

/* int attributes support */

static PyObject *
z_pyvar_int_get(ZProxy *self, gchar *name, gpointer value)
{
  return z_policy_var_build("i", *(gint *) value);
}

static gint
z_pyvar_int_set(ZProxy *self, gchar *name, gpointer value, PyObject *new)
{
  if (!z_policy_var_parse(new, "i", value))
    {
      return 0;
    }
  return 1;
}

/* string attributes support */

static PyObject *
z_pyvar_string_get(ZProxy *self, gchar *name, gpointer value)
{
  return PyString_FromString(((GString *) value)->str);
}

static gint 
z_pyvar_string_set(ZProxy *self, gchar *name, gpointer value, PyObject *new)
{
  gchar *str;
  
  if (!PyArg_Parse(new, "s", &str))
    return 0;
  g_string_assign((GString *) value, str);
  return 1;
}

static void
z_pyvar_string_free(gpointer value)
{
  g_string_free((GString *) value, TRUE);
}

/* sockaddr attributes support */

static PyObject *
z_pyvar_sockaddr_get(ZProxy *self, gchar *name, gpointer value)
{
  ZSockAddr **v = value;

  return z_py_zorp_sockaddr_new(*v);
}

static gint 
z_pyvar_sockaddr_set(ZProxy *self, gchar *name, gpointer value, PyObject *new)
{
  if (z_py_zorp_sockaddr_check(new))
    {
      ZSockAddr **v = value;
    
      z_sockaddr_unref(*v);
      *v = z_sockaddr_ref(((ZorpSockAddr *) new)->sa);
      return 1;
    }
  else
    {
      PyErr_SetObject(PyExc_TypeError, new);
      return 0;
    }
}

static void
z_pyvar_sockaddr_free(gpointer value)
{
  z_sockaddr_unref(*((ZSockAddr **) value));
}

/* object attributes support */

static void
z_pyvar_object_free(gpointer value)
{
  Py_XDECREF(*((PyObject **) value));
}

static PyObject *
z_pyvar_object_get(ZProxy *self, gchar *name, gpointer value)
{
  PyObject *r = *((PyObject **) value);
  
  Py_XINCREF(r);
  return r;
}

static gint 
z_pyvar_object_set(ZProxy *self, gchar *name, gpointer value, PyObject *new)
{
  z_pyvar_object_free(value);
  *((PyObject **) value) = new;
  Py_XINCREF(new);
  
  return 1;
}


/* method attributes support */

typedef struct _ZorpMethod
{
  PyObject_HEAD
  ZProxy *proxy;
  ZProxyMethodFunc method;
} ZorpMethod;

extern PyTypeObject z_py_zorp_method_type;

static ZorpMethod *
z_py_zorp_method_new(ZProxy *proxy, ZProxyMethodFunc method)
{
  ZorpMethod *self;
  
  self = PyObject_NEW(ZorpMethod, &z_py_zorp_method_type);
  if (!self)
    return NULL;
  self->proxy = proxy;
  self->method = method;
  return self;
}

static PyObject *
z_py_zorp_method_call(ZorpMethod *self, PyObject *args, PyObject *kw)
{
  return self->method(self->proxy, args);
}

static void
z_py_zorp_method_free(ZorpMethod *self)
{
  PyMem_DEL(self);
}

PyTypeObject z_py_zorp_method_type = 
{
  PyObject_HEAD_INIT(&z_py_zorp_method_type)
  0,
  "Zorp method",
  sizeof(ZorpMethod),
  0,
  (destructor) z_py_zorp_method_free, 
  0,                                  /* tp_print */
  0,                                  /* tp_getattr */
  0,                                  /* tp_setattr */
  0,                                  /* tp_compare */
  0,                                  /* tp_repr */
  0,                                  /* tp_as_number */
  0,                                  /* tp_as_sequence */
  0,                                  /* tp_as_mapping */
  0,                                  /* tp_hash */
  (ternaryfunc) z_py_zorp_method_call,/* tp_call */
  0,                                  /* tp_str */
  0,                                  /* tp_getattro */
  0,                                  /* tp_setattro */
  0,                                  /* tp_as_buffer */
  0,                                  /* flags */
  "ZorpMethod class for Zorp",        /* docstring */
};

static PyObject *
z_pyvar_method_get(ZProxy *self, gchar *name, gpointer value)
{
  PyObject *res = (PyObject *) value;
  Py_XINCREF(res);
  return res;
}

static void
z_pyvar_method_free(gpointer value)
{
  /* z_policy_lock(z_python_global_state); */
  Py_XDECREF((PyObject *) value);
  /* z_policy_unlock(z_python_global_state); */
}

/* hash table support */

typedef struct _ZorpHash
{
  PyObject_HEAD
  GHashTable *hash;
} ZorpHash;

extern PyTypeObject z_py_zorp_hash_type;

static ZorpHash *
z_py_zorp_hash_new(GHashTable *hash)
{
  ZorpHash *self = PyObject_NEW(ZorpHash, &z_py_zorp_hash_type);
  
  self->hash = hash;
  return self;
}

static gboolean
z_py_zorp_hash_unref_items(gpointer key, gpointer value, gpointer user_data)
{
  Py_XDECREF((PyObject *) value);
  return TRUE;
}

static void
z_py_zorp_hash_free(ZorpHash *self)
{
  g_hash_table_foreach_remove(self->hash, z_py_zorp_hash_unref_items, NULL);
  g_hash_table_destroy(self->hash);
  PyMem_DEL(self);
}

static PyObject *
z_py_zorp_hash_subscript(ZorpHash *self, PyObject *k)
{
  gchar *key;
  PyObject *res;
  
  if (!PyArg_Parse(k, "s", &key))
    return NULL;
  res = g_hash_table_lookup(self->hash, key);
  if (res)
    {
      Py_INCREF(res);
      return res;
    }
  else
    {
      PyErr_SetObject(PyExc_KeyError, k);
      return NULL;
    }
}

static gint 
z_py_zorp_hash_ass_subscript(ZorpHash *self, PyObject *u, PyObject *v)
{
  gchar *key;
  PyObject *res;
  
  if (!PyArg_Parse(u, "s", &key))
    return -1;
    
  res = g_hash_table_lookup(self->hash, key);
  if (v == NULL)
    {
      /* delete item */
      if (!res)
        {
          PyErr_SetObject(PyExc_KeyError, u);
          return -1;
        }
      g_hash_table_remove(self->hash, key);
      Py_XDECREF(res);
      return 0;
    }
  else
    {
      Py_XINCREF(v);
      g_hash_table_insert(self->hash, key, v);
      Py_XDECREF(res);
      return 0;
    }
}

PyMappingMethods z_py_zorp_hash_mapping = 
{
  NULL,
  (binaryfunc) z_py_zorp_hash_subscript,
  (objobjargproc) z_py_zorp_hash_ass_subscript
};

PyTypeObject z_py_zorp_hash_type = 
{
  PyObject_HEAD_INIT(&z_py_zorp_hash_type)
  0,
  "Zorp hash",
  sizeof(ZorpHash),
  0,
  (destructor) z_py_zorp_hash_free, 
  0,                                  /* tp_print */
  0,                                  /* tp_getattr */
  0,                                  /* tp_setattr */
  0,                                  /* tp_compare */
  0,                                  /* tp_repr */
  0,                                  /* tp_as_number */
  0,                                  /* tp_as_sequence */
  &z_py_zorp_hash_mapping,            /* tp_as_mapping */
  0,                                  /* tp_hash */
  0,  				      /* tp_call */
  0,                                  /* tp_str */
  0,                                  /* tp_getattro */
  0,                                  /* tp_setattro */
  0,                                  /* tp_as_buffer */
  0,                                  /* flags */
  "ZorpHash class for Zorp",          /* docstring */
};

static PyObject *
z_pyvar_hash_get(ZProxy *self, gchar *name, gpointer value)
{
  PyObject *res = (PyObject *) value;
  
  Py_XINCREF(res);
  return res;
}

static void
z_pyvar_hash_free(gpointer value)
{
  /* z_policy_lock(z_python_global_state); */
  Py_XDECREF((PyObject *) value);
  /* z_policy_unlock(z_python_global_state); */
}

/* Multidimensional hash table support */

typedef struct _ZorpDimHash
{
  PyObject_HEAD
  ZDimHashTable *hash;
} ZorpDimHash;

extern PyTypeObject z_py_zorp_dimhash_type;

static ZorpDimHash *
z_py_zorp_dimhash_new(ZDimHashTable *hash)
{
  ZorpDimHash *self = PyObject_NEW(ZorpDimHash, &z_py_zorp_dimhash_type);
  
  self->hash = hash;
  return self;
}

static gboolean
z_py_zorp_dimhash_unref_items(gpointer value)
{
  Py_XDECREF((PyObject *) value);
  return TRUE;
}

static void
z_py_zorp_dimhash_free(ZorpDimHash *self)
{
  z_dim_hash_table_free(self->hash, z_py_zorp_dimhash_unref_items);
  PyMem_DEL(self);
}

static PyObject *
z_py_zorp_dimhash_subscript(ZorpDimHash *self, PyObject *k)
{
  gchar **keys;
  gchar *key;
  guint keynum;
  PyObject *item;
  PyObject *stritem;
  PyObject *res;
  int i;
  
  
  if (PyArg_Parse(k, "s", &key))
    {
      keynum = 1;
      keys = g_new0(gchar *, keynum);
      keys[0] = g_new0(gchar, strlen(key)+1);
      strcpy(keys[0], key);
    }
  else
    {
      if (z_policy_seq_check(k))
        {
          keynum = z_policy_seq_length(k);
      
          keys = g_new0(gchar *, keynum);
      
          for (i = 0; i < keynum; i++)
            {
              item = z_policy_seq_getitem(k, i);
              stritem = z_policy_var_str(item);
              z_policy_var_unref(item);
          
              key = z_policy_str_as_string(stritem);
              keys[i] = g_new0(gchar, strlen(key)+1);
              strcpy(keys[i], key);
              z_policy_var_unref(stritem);
            }
        }
      else
        return NULL;
    }
  res = z_dim_hash_table_lookup(self->hash, keynum, keys);
  z_dim_hash_key_free(keynum, keys);

  if (res)
    {
      Py_INCREF(res);
      return res;
    }
  else
    {
      PyErr_SetObject(PyExc_KeyError, k);
      return NULL;
    }
}

static gint 
z_py_zorp_dimhash_ass_subscript(ZorpDimHash *self, PyObject *u, PyObject *v)
{
  gchar **keys;
  gchar *key;
  guint keynum;
  PyObject *res;
  PyObject *item;
  PyObject *stritem;
  int i;
  
  
  if (PyArg_Parse(u, "s", &key))
    {
      keynum = 1;
      keys = g_new0(gchar *, keynum);
      keys[0] = g_new0(gchar, strlen(key)+1);
      strcpy(keys[0], key);
      z_cp();
    }
  else
    {
      if (z_policy_seq_check(u))
        {
          keynum = z_policy_seq_length(u);
      
          keys = g_new0(gchar *, keynum);
      
          for (i = 0; i < keynum; i++)
            {
              item = z_policy_seq_getitem(u, i);
              stritem = z_policy_var_str(item);
              z_policy_var_unref(item);
          
              key = z_policy_str_as_string(stritem);
              keys[i] = g_new0(gchar, strlen(key)+1);
              strcpy(keys[i], key);
              z_policy_var_unref(stritem);
              z_cp();
            }
        }
      else
        return -1;
    }
  res = z_dim_hash_table_lookup(self->hash, keynum, keys);

  if (v == NULL)
    {
      /* delete item */
      if (!res)
        {
          PyErr_SetObject(PyExc_KeyError, u);
          z_dim_hash_key_free(keynum, keys);
          return -1;
        }
      z_dim_hash_table_delete(self->hash, keynum, keys, z_py_zorp_dimhash_unref_items);
      z_dim_hash_key_free(keynum, keys);
      Py_XDECREF(res);
      return 0;
    }
  else
    {
      Py_XINCREF(v);
      z_dim_hash_table_insert(self->hash, v, keynum, keys);
      z_dim_hash_key_free(keynum, keys);
      Py_XDECREF(res);
      return 0;
    }
}

PyMappingMethods z_py_zorp_dimhash_mapping = 
{
  NULL,
  (binaryfunc) z_py_zorp_dimhash_subscript,
  (objobjargproc) z_py_zorp_dimhash_ass_subscript
};

PyTypeObject z_py_zorp_dimhash_type = 
{
  PyObject_HEAD_INIT(&z_py_zorp_dimhash_type)
  0,
  "Zorp Multidimensional hash",
  sizeof(ZorpDimHash),
  0,
  (destructor) z_py_zorp_dimhash_free, 
  0,                                  /* tp_print */
  0,                                  /* tp_getattr */
  0,                                  /* tp_setattr */
  0,                                  /* tp_compare */
  0,                                  /* tp_repr */
  0,                                  /* tp_as_number */
  0,                                  /* tp_as_sequence */
  &z_py_zorp_dimhash_mapping,        /* tp_as_mapping */
  0,                                  /* tp_hash */
  0,  				      /* tp_call */
  0,                                  /* tp_str */
  0,                                  /* tp_getattro */
  0,                                  /* tp_setattro */
  0,                                  /* tp_as_buffer */
  0,                                  /* flags */
  "ZorpDimHash class for Zorp",       /* docstring */
};

static PyObject *
z_pyvar_dimhash_get(ZProxy *self, gchar *name, gpointer value)
{
  PyObject *res = (PyObject *) value;
  
  Py_XINCREF(res);
  return res;
}

static void
z_pyvar_dimhash_free(gpointer value)
{
  /* z_policy_lock(z_python_global_state); */
  Py_XDECREF((PyObject *) value);
  /* z_policy_unlock(z_python_global_state); */
}

/* alias support */

static PyObject *
z_pyvar_alias_get(ZProxy *self, gchar *name, gpointer value)
{
  return PyObject_GetAttrString(self->handler, (char *) value);
}

static gint
z_pyvar_alias_set(ZProxy *self, gchar *name, gpointer value, PyObject *new)
{
  return PyObject_SetAttrString(self->handler, (char *) value, new);
}

/* attribute support */

void
z_proxy_var_new(ZProxy *self, gchar *name, guint flags, ...)
{
  ZPyVarDesc *e = g_new0(ZPyVarDesc, 1);
  va_list l;
  
  e->name = name;
  e->flags = flags;

  va_start(l, flags);
  switch (Z_VAR_TYPE(flags))
    {
    case Z_VAR_TYPE_INT:
      e->value = va_arg(l, gpointer);
      e->getvar = z_pyvar_int_get;
      e->setvar = z_pyvar_int_set;
      break;
    case Z_VAR_TYPE_STRING:
      e->value = va_arg(l, gpointer);
      e->getvar = z_pyvar_string_get;
      e->setvar = z_pyvar_string_set;
      e->free = z_pyvar_string_free;
      break;
    case Z_VAR_TYPE_SOCKADDR:
      e->value = va_arg(l, gpointer);
      e->getvar = z_pyvar_sockaddr_get;
      e->setvar = z_pyvar_sockaddr_set;
      e->free = z_pyvar_sockaddr_free;
      break;
    case Z_VAR_TYPE_OBJECT:
      e->value = va_arg(l, gpointer);
      e->getvar = z_pyvar_object_get;
      e->setvar = z_pyvar_object_set;
      e->free = z_pyvar_object_free;
      break;
    case Z_VAR_TYPE_METHOD:
      {
	ZorpMethod *zm;

	zm = z_py_zorp_method_new(self, va_arg(l, ZProxyMethodFunc));
	e->value = zm;
	e->free = z_pyvar_method_free;
	e->getvar = z_pyvar_method_get;
	break;
      }
    case Z_VAR_TYPE_HASH:

      e->value = z_py_zorp_hash_new((GHashTable *) va_arg(l, gpointer));
      e->free = z_pyvar_hash_free;
      e->getvar = z_pyvar_hash_get;
      break;
    case Z_VAR_TYPE_CUSTOM:

      e->value = va_arg(l, gpointer);
      e->getvar = va_arg(l, ZProxyGetVarFunc);
      e->setvar = va_arg(l, ZProxySetVarFunc);
      break;
    case Z_VAR_TYPE_DIMHASH:

      e->value = z_py_zorp_dimhash_new((ZDimHashTable *) va_arg(l, gpointer));
      e->free = z_pyvar_dimhash_free;
      e->getvar = z_pyvar_dimhash_get;
      break;
    case Z_VAR_TYPE_ALIAS:
      e->value = va_arg(l, gpointer);
      e->getvar = z_pyvar_alias_get;
      e->setvar = z_pyvar_alias_set;
      break;
    }
  va_end(l);
  g_hash_table_insert(self->vars, name, e);
}


static gboolean
z_proxy_var_destroy(gchar *key, ZPyVarDesc *value, gpointer user_data)
{
  if (value->free)
    (*value->free)(value->value);
  g_free(value);
  return TRUE;
}

GHashTable *
z_pyvars_new()
{
  return g_hash_table_new(g_str_hash, g_str_equal);
}

void
z_pyvars_destroy(GHashTable *vars)
{
  g_hash_table_foreach_remove(vars, (GHRFunc) z_proxy_var_destroy, NULL);
  g_hash_table_destroy(vars);
}
