/* -*- Mode: C; c-basic-offset: 4 -*-
 * pygtk- Python bindings for the GTK toolkit.
 * Copyright (C) 2006  Johannes Hölzl
 *
 *   pygoptiongroup.c: GOptionContext and GOptionGroup wrapper
 *
 * 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.1 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
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include "pygobject-private.h"

typedef struct {
    GOptionArg arg;
    union {
        gboolean none;
        gchar *string;
        gint int_;
        gchar **string_array;
    } value;
} ArgDescription;

static void arg_description_free(ArgDescription *desc)
{
    switch(desc->arg)
    {
        case G_OPTION_ARG_NONE: break;

        case G_OPTION_ARG_STRING:
        case G_OPTION_ARG_FILENAME:
            g_free(desc->value.string);
            break;

        case G_OPTION_ARG_INT: break;

        case G_OPTION_ARG_CALLBACK: break;

        case G_OPTION_ARG_STRING_ARRAY:
        case G_OPTION_ARG_FILENAME_ARRAY:
            g_free(desc->value.string_array);
            break;
    }
    g_free(desc);
}

static gboolean 
append_hash_table_cb(gchar *name,
                     ArgDescription *desc,
                     GHashTable *other)
{
    g_hash_table_insert(other, name, desc);
    return TRUE;
}

static gboolean 
arg_func(const gchar *option_name,
         const gchar *value,
         PyGOptionGroup *self,
         GError *error)
{
   PyObject *obj, *cb;
   cb = g_hash_table_lookup(self->arg_name_callback_map, option_name);
   obj = PyObject_CallFunction(cb, "ssO", option_name, value, self);

   if(obj == NULL)
       return FALSE;

   Py_DECREF(obj);
   return TRUE;      
}

static gboolean
parse_entries(PyObject *sequence, 
              PyGOptionGroup *group)
{
    gssize entry_count, pos;
    PyObject *tuple;
    PyObject *arg_name;
    ArgDescription *desc;
    GHashTable *new_args, *new_cbs;
    GOptionEntry *entries;
    gchar *long_name, *short_name;
    
    entry_count = PySequence_Size(sequence);
    if(entry_count == -1)
    {
        PyErr_SetString(PyExc_TypeError, "GOptionGroup.add_entries expected a list of entries");
        return FALSE;
    }
    
    new_args = g_hash_table_new_full(g_str_hash, g_str_equal,
                                     g_free, (GDestroyNotify) arg_description_free);
    new_cbs = g_hash_table_new_full(g_str_hash, g_str_equal,
                                    g_free, (GDestroyNotify) Py_DecRef);
    entries = g_new0(GOptionEntry, entry_count + 1);
    for(pos = 0; pos < entry_count; pos++)
    {
        tuple = PySequence_GetItem(sequence, pos);
        if(! PyArg_ParseTuple(tuple, "sciiOss",
            &(entries[pos].long_name),
            &(entries[pos].short_name),
            &(entries[pos].flags),
            &(entries[pos].arg),
            &(arg_name),
            &(entries[pos].description),
            &(entries[pos].arg_description)))
        {
            PyErr_SetString(PyExc_TypeError, "GOptionGroup.add_entries expected a list of entries");
            goto failed;
        }

        if(entries[pos].arg == G_OPTION_ARG_CALLBACK)
        {
            long_name = g_strdup_printf("--%s", entries[pos].long_name);
            if(g_hash_table_lookup(group->arg_name_callback_map, long_name) != NULL ||
               g_hash_table_lookup(new_cbs, long_name) != NULL)
            {
               g_free(long_name);
               PyErr_SetString(PyExc_ValueError, "GOptionGroup.add_entries got an long_name which is already assigned to an entry in this group.");
               goto failed;
            }
            g_hash_table_insert(new_cbs, long_name, arg_name);
            Py_INCREF(arg_name);
            
            if(entries[pos].short_name != '\0')
            {
                short_name = g_strdup_printf("-%c", entries[pos].short_name);
                if(g_hash_table_lookup(group->arg_name_callback_map, short_name) != NULL ||
                   g_hash_table_lookup(new_cbs, short_name) != NULL)
                {
                   g_free(short_name);
                   PyErr_SetString(PyExc_ValueError, "GOptionGroup.add_entries got an short_name which is already assigned to an entry in this group.");
                   goto failed;
                }
                g_hash_table_insert(new_cbs, short_name, arg_name);
                Py_INCREF(arg_name);
            }
            
            
            entries[pos].arg_data = arg_func;
        }
        else
        {
            desc = g_new0(ArgDescription, 1);
            desc->arg = entries[pos].arg;
            if(! PyString_Check(arg_name))
            {
               g_free(desc);
               PyErr_SetString(PyExc_TypeError, "GOptionGroup.add_entries expects a string as arg_name.");
               goto failed;
            }
            long_name = g_strdup(PyString_AsString(arg_name));
            if(g_hash_table_lookup(group->arg_name_value_map, long_name) != NULL ||
               g_hash_table_lookup(new_args, long_name)) {
               g_free(long_name);
               g_free(desc);
               PyErr_SetString(PyExc_ValueError, "GOptionGroup.add_entries got an arg_name which is already assigned to an entry in this group.");
               goto failed;
            }
         g_hash_table_insert(new_args, long_name, desc);
             entries[pos].arg_data = &(desc->value);
        }
    }
    
    g_hash_table_foreach_steal(new_args, (GHRFunc) append_hash_table_cb, group->arg_name_value_map);
    g_hash_table_destroy(new_args);

    g_hash_table_foreach_steal(new_cbs, (GHRFunc) append_hash_table_cb, group->arg_name_callback_map);
    g_hash_table_destroy(new_cbs);
    
    g_option_group_add_entries(group->group, entries);
    
    g_free(entries);
    
    return TRUE;
    
failed:
    g_free(entries);
    g_hash_table_destroy(new_args);
    g_hash_table_destroy(new_cbs);
    return FALSE;
}

static void destroy_g_group(PyGOptionGroup *self)
{
    GOptionGroup *group = self->group;
    self->group = NULL;
    if(group != NULL)
    {
        if(self->is_in_context)
        {
            Py_DECREF(self);
        }
           
        if(self->arg_name_callback_map != NULL)
        {
            g_hash_table_destroy(self->arg_name_callback_map);
            self->arg_name_callback_map = NULL;
        }
        
        Py_XDECREF(self->pre_hook);
        self->pre_hook = NULL;
        
        Py_XDECREF(self->post_hook);
        self->post_hook = NULL;
        
        Py_XDECREF(self->hooks_arg);
        self->hooks_arg = NULL;
        
        Py_XDECREF(self->error_hook);
        self->error_hook = NULL;
        
        Py_XDECREF(self->translate_func);
        self->translate_func = NULL;
        
        Py_XDECREF(self->translate_func_arg);
        self->translate_func_arg = NULL;
    }
}

static gboolean
call_parse_func(PyGOptionGroup *self,
                GOptionContext *context,
                PyObject *func,
                PyObject *arg,
                GError **error)
{
   PyObject *obj;
   if (func == NULL) return TRUE;
   
   if(arg != NULL)
       obj = PyObject_CallFunction(func, "OO", self, arg);
   else
       obj = PyObject_CallFunction(func, "O", self);

   if(obj == NULL)
       return FALSE;
      
   Py_DECREF(obj);
   return TRUE;
}

static gboolean
check_if_owned(PyGOptionGroup *self)
{
    if(self->other_owner)
    {
        PyErr_SetString(PyExc_ValueError, "The GOptionGroup was not created by gobject.OptionGroup(), so operation is not possible.");
        return TRUE;
    }
    return FALSE;
}

GOptionGroup *
pyg_option_group_transfer_group(PyGOptionGroup *self)
{
    if(self->is_in_context) return NULL;
    self->is_in_context = TRUE;
    
    /* Here we increase the reference count of the PyGOptionGroup, because now
     * the GOptionContext holds an reference to us (it is the userdata passed
     * to g_option_group_new().
     *
     * The GOptionGroup is freed with the GOptionContext.
     *
     * We set it here because if we would do this in the init method we would
     * hold two references and the PyGOptionGroup would never be freed.
     */
    Py_INCREF(self);
    
    return self->group;
}

static PyObject *
pyg_option_group_add_entries(PyGOptionGroup *self, PyObject *sequence)
{
    if(check_if_owned(self)) return NULL;
    
    if(! parse_entries(sequence, self))
        return NULL;

    Py_RETURN_NONE;
}

static gboolean
pre_hook(GOptionContext *context,
         GOptionGroup *group,
         PyGOptionGroup *pyg,
         GError **error)
{
    return call_parse_func(pyg, context, pyg->pre_hook, pyg->hooks_arg, error);
}

static gboolean
post_hook(GOptionContext *context,
          GOptionGroup *group,
          PyGOptionGroup *pyg,
          GError **error)
{
    return call_parse_func(pyg, context, pyg->post_hook, pyg->hooks_arg, error);
}

static void
error_hook(GOptionContext *context, 
           GOptionGroup *group,
           PyGOptionGroup *pyg,
           GError **error)
{
    call_parse_func(pyg, context, pyg->error_hook, pyg->error_hook_arg, error);
}

static int
pyg_option_group_init(PyGOptionGroup *self, PyObject *args)
{
    char *name, *description, *help_description;
    if (! PyArg_ParseTuple(args, "zzz:GOptionGroup.__init__", 
                           &name, &description, &help_description))
        return -1;
    
    self->group = g_option_group_new(name, description, help_description,
                                     self, (GDestroyNotify) destroy_g_group);
    g_option_group_set_parse_hooks(self->group, 
                                   (GOptionParseFunc) pre_hook,
                                   (GOptionParseFunc) post_hook);
    g_option_group_set_error_hook(self->group, (GOptionErrorFunc) error_hook);
    self->other_owner = FALSE;
    self->is_in_context = FALSE;
    self->arg_name_value_map = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, 
                                                     (GDestroyNotify) arg_description_free);
    self->arg_name_callback_map = g_hash_table_new_full(g_str_hash, g_str_equal,
                                                        g_free, (GDestroyNotify) Py_DecRef);
    return 0;
}

static void
pyg_option_group_dealloc(PyGOptionGroup *self)
{
    if(! self->other_owner)
    {
        if(self->group != NULL)
            g_option_group_free(self->group);
    }

    if(self->arg_name_value_map != NULL)    
        g_hash_table_destroy(self->arg_name_value_map);
    
    PyObject_Del(self);
}

static PyObject *
pyg_option_group_set_parse_hooks(PyGOptionGroup *self, PyObject *args)
{
    PyObject *pre, *post, *arg = NULL;
    if(check_if_owned(self)) return NULL;
    
    if(! PyArg_ParseTuple(args, "OO|O:GOptionGroup.set_parse_hooks", &pre, &post, &arg))
        return NULL;
    
    if(! (PyCallable_Check(pre) || pre == Py_None) || 
       ! (PyCallable_Check(post) || post == Py_None))
    {
        PyErr_SetString(PyExc_TypeError, "gobject.GOptionGroup.set_parse_hooks expects two callables.");
        return NULL;
    }
    
    Py_XDECREF(self->hooks_arg);
    self->hooks_arg = arg;
    Py_XINCREF(self->hooks_arg);

    Py_XDECREF(self->pre_hook);
    if(pre != Py_None)
    {
        self->pre_hook = pre;
        Py_INCREF(self->pre_hook);
    }
    else
        self->pre_hook = NULL;

    Py_XDECREF(self->post_hook);
    if(post != Py_None)
    {
        self->post_hook = post;
        Py_INCREF(self->post_hook);
    }
    else
        self->post_hook = NULL;
    
    Py_RETURN_NONE;    
}

static PyObject *
pyg_option_group_set_error_hook(PyGOptionGroup *self, PyObject *args)
{
    PyObject *error, *arg = NULL;
    if(check_if_owned(self)) return NULL;

    if(! PyArg_ParseTuple(args, "O|O:GOptionGroup.set_error_hook", &error, &arg))
        return NULL;

    if(! (PyCallable_Check(error) || error == Py_None))
    {
        PyErr_SetString(PyExc_TypeError, "gobject.GOptionGroup.set_error_hook expects a callable.");
        return NULL;
    }

    Py_XDECREF(self->error_hook_arg);
    self->error_hook_arg = arg;
    Py_XINCREF(self->error_hook_arg);

    Py_XDECREF(self->error_hook);
    if(error == Py_None)
    {
        self->error_hook = error;
        Py_INCREF(self->error_hook);
    }
    else
        self->error_hook = NULL;

    Py_RETURN_NONE;    
}

static gchar *
translate_func(const gchar *str,
               PyGOptionGroup *self)
{
   PyObject *obj;
   gchar *result;
   if(self->translate_func_arg != NULL)
       obj = PyObject_CallFunction(self->translate_func, "sOO", str, self, self->translate_func_arg);
   else
       obj = PyObject_CallFunction(self->translate_func, "sO", str, self);

   if(obj == NULL)
       return NULL;
   result = g_strdup(PyString_AsString(obj));
   Py_DECREF(obj);
   
   return result;
}

static PyObject *
pyg_option_group_set_translate_func(PyGOptionGroup *self, PyObject *args)
{
    PyObject *translate, *arg = NULL;
    if(check_if_owned(self)) return NULL;

    if(self->group == NULL)
    {
        PyErr_SetString(PyExc_RuntimeError, "The corresponding GOptionGroup was already freed, probably through the release of GOptionContext");
        return NULL;
    }
    if(! PyArg_ParseTuple(args, "O|O:GOptionGroup.set_translate_func", &translate, &arg))
        return NULL;

    if(! (PyCallable_Check(translate) || translate == Py_None))
    {
        PyErr_SetString(PyExc_TypeError, "gobject.GOptionGroup.set_translate_func expects a callable.");
        return NULL;
    }


    Py_XDECREF(self->translate_func_arg);
    self->translate_func_arg = arg;
    Py_XINCREF(self->translate_func_arg);

    Py_XDECREF(self->translate_func);
    if(translate == Py_None)
    {
        g_option_group_set_translate_func(self->group, NULL, NULL, NULL);
    }
    else
    {
        self->translate_func = translate;
        Py_INCREF(self->translate_func);
        g_option_group_set_translate_func(self->group, 
                                          (GTranslateFunc) translate_func, 
                                          self, NULL);
    }
    Py_RETURN_NONE;    
}

static PyObject *
pyg_option_group_set_translation_domain(PyGOptionGroup *self, PyObject *args)
{
    char *domain;
    if(check_if_owned(self)) return NULL;

    if(self->group == NULL)
    {
        PyErr_SetString(PyExc_RuntimeError, "The corresponding GOptionGroup was already freed, probably through the release of GOptionContext");
        return NULL;
    }
    if(! PyArg_ParseTuple(args, "s:GOptionGroup.set_translate_domain", &domain))
        return NULL;
    g_option_group_set_translation_domain(self->group, domain);
    Py_RETURN_NONE;    
}

static PyObject *
pyg_option_group_getitem(PyGOptionGroup *self, 
                         PyObject *name)
{
    char *name_str;
    ArgDescription *arg;
    int pos;
    PyObject *list, *string;
    
    name_str = PyString_AsString(name);
    if(name_str == NULL) return NULL;
    
    arg = g_hash_table_lookup(self->arg_name_value_map, name_str);
    if(arg == NULL)
    {
        PyErr_SetString(PyExc_KeyError, "arg_name not found in OptionGroup.");
        return NULL;
    }
    
    switch(arg->arg) {
        case G_OPTION_ARG_NONE:
            if(arg->value.none)
                Py_RETURN_TRUE;
            else
                Py_RETURN_FALSE;

        case G_OPTION_ARG_STRING:
        case G_OPTION_ARG_FILENAME:
            if (arg->value.string == NULL)
                Py_RETURN_NONE;
            else
                return PyString_FromString(arg->value.string);

        case G_OPTION_ARG_INT:
            return PyInt_FromLong(arg->value.int_);

        case G_OPTION_ARG_STRING_ARRAY:
        case G_OPTION_ARG_FILENAME_ARRAY:
            if (arg->value.string_array == NULL)
                Py_RETURN_NONE;

            list = PyList_New(0);
            
            for(pos = 0; arg->value.string_array[pos] != NULL; pos++)
            {
                string = PyString_FromString(arg->value.string_array[pos]);
                PyList_Append(list, string);
                Py_DECREF(string);
            }
            return list;
        case G_OPTION_ARG_CALLBACK:
        default:
            g_assert_not_reached();
    }   
}

static int
pyg_option_group_compare(PyGOptionGroup *self, PyGOptionGroup *v)
{
    if (self->group == v->group) return 0;
    if (self->group > v->group) return -1;
    return 1;
}

static PyMethodDef pyg_option_group_methods[] = {
    { "add_entries", (PyCFunction)pyg_option_group_add_entries, METH_O },
    { "set_parse_hooks", (PyCFunction)pyg_option_group_set_parse_hooks, METH_VARARGS },
    { "set_error_hook", (PyCFunction)pyg_option_group_set_error_hook, METH_VARARGS },
    { "set_translate_func", (PyCFunction)pyg_option_group_set_translate_func, METH_VARARGS },
    { "set_translation_domain", (PyCFunction)pyg_option_group_set_translation_domain, METH_VARARGS },
    { NULL, NULL, 0 },
};

static PyMappingMethods pyg_option_group_mapping_methods = {
    0,
    (binaryfunc) pyg_option_group_getitem,
    0,
};

PyTypeObject PyGOptionGroup_Type = {
    PyObject_HEAD_INIT(NULL)
    0,
    "gobject.OptionGroup",
    sizeof(PyGOptionGroup),
    0,
    /* methods */
    (destructor)pyg_option_group_dealloc,
    (printfunc)0,
    (getattrfunc)0,
    (setattrfunc)0,
    (cmpfunc)pyg_option_group_compare,
    (reprfunc)0,
    0,
    0,
    &pyg_option_group_mapping_methods,
    (hashfunc)0,
    (ternaryfunc)0,
    (reprfunc)0,
    (getattrofunc)0,
    (setattrofunc)0,
    0,
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    NULL,
    (traverseproc)0,
    (inquiry)0,
    (richcmpfunc)0,
    0,
    (getiterfunc)0,
    (iternextfunc)0,
    pyg_option_group_methods,
    0,
    0,
    NULL,
    NULL,
    (descrgetfunc)0,
    (descrsetfunc)0,
    0,
    (initproc)pyg_option_group_init,
};

PyObject * 
pyg_option_group_new (GOptionGroup *group)
{
    PyGOptionGroup *self;

    self = (PyGOptionGroup *)PyObject_NEW(PyGOptionGroup,
					  &PyGOptionGroup_Type);
    if (self == NULL)
	return NULL;

    self->group = group;
    self->other_owner = TRUE;
    self->is_in_context = FALSE;
        
    return (PyObject *)self;
}

