/* -*- c-file-style: "ruby"; indent-tabs-mode: nil -*- */
/*
 *  Copyright (C) 2011-2021  Ruby-GNOME Project Team
 *  Copyright (C) 2004  Masao Mutoh
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston,
 *  MA  02110-1301  USA
 */

#include "rbgprivate.h"
#include <ctype.h>

static ID id_code;
static ID id_CODE;
static const char *name_CODE = "CODE";
static ID id_domain;
static ID id_DOMAIN;
static const char *name_DOMAIN = "DOMAIN";
static ID id_CODE_CLASSES;
static const char *name_CODE_CLASSES = "CODE_CLASSES";
static VALUE gerror_table;
static VALUE generic_error;
static VALUE error_info;

G_DEFINE_QUARK(ruby-error-quark, rbgerr_ruby_error)

VALUE
rbgerr_gerror2exception(GError *error)
{
    VALUE exc = Qnil;
    VALUE exception_klass = Qnil;

    if (!error) {
        return rb_exc_new2(rb_eRuntimeError, "GError parameter doesn't have a value.");
    }

    exception_klass = rb_hash_aref(gerror_table, UINT2NUM(error->domain));
    if (NIL_P(exception_klass)) {
        exception_klass = generic_error;
    } else if (rb_const_defined_at(exception_klass, id_CODE_CLASSES)) {
        VALUE code_classes = rb_const_get(exception_klass, id_CODE_CLASSES);
        VALUE code_class = rb_hash_aref(code_classes, INT2NUM(error->code));
        if (!NIL_P(code_class)) {
            exception_klass = code_class;
        }
    }
    exc = rb_exc_new_str(exception_klass, CSTR2RVAL(error->message));
    rb_ivar_set(exc, id_domain, CSTR2RVAL(g_quark_to_string(error->domain)));
    rb_ivar_set(exc, id_code, INT2NUM(error->code));
    g_error_free(error);
    return exc;
}

static gchar *
nick_to_constant_name(const gchar *nick)
{
    GString *constant_name;
    const gchar *current;

    constant_name = g_string_new(NULL);

    for (current = nick; *current; current++) {
        if (*current == '-') {
            g_string_append_c(constant_name, '_');
        } else {
            g_string_append_c(constant_name, g_ascii_toupper(*current));
        }
    }

    return g_string_free(constant_name, FALSE);
}

static gchar *
nick_to_class_name(const gchar *nick)
{
    GString *class_name;
    const gchar *current;
    gboolean to_upper = TRUE;

    class_name = g_string_new(NULL);

    for (current = nick; *current; current++) {
        if (to_upper) {
            g_string_append_c(class_name, g_ascii_toupper(*current));
            to_upper = FALSE;
        } else if (*current == '-') {
            to_upper = TRUE;
        } else {
            g_string_append_c(class_name, *current);
        }
    }

    return g_string_free(class_name, FALSE);
}

VALUE
rbgerr_define_gerror(GQuark domain, const gchar *name, VALUE module, VALUE parent, GType gtype)
{
    VALUE error_class;
    VALUE code_classes;
    VALUE rb_domain = rb_str_new_cstr(g_quark_to_string(domain));
    rbgutil_string_set_utf8_encoding(rb_domain);
    rb_obj_freeze(rb_domain);

    error_class = rb_define_class_under(module, name, parent);
    rb_define_const(error_class, name_CODE, Qnil);
    rb_define_const(error_class, name_DOMAIN, rb_domain);
    rb_prepend_module(error_class, error_info);

    rb_hash_aset(gerror_table, UINT2NUM(domain), error_class);

    code_classes = rb_hash_new();
    rb_define_const(error_class, name_CODE_CLASSES, code_classes);

    if (gtype != G_TYPE_INVALID) {
        GEnumClass* gclass = g_type_class_ref(gtype);
        guint i;

        for (i = 0; i < gclass->n_values; i++) {
            VALUE code_class;
            GEnumValue* entry = &(gclass->values[i]);
            gchar *code_constant_name;
            gchar *code_class_name;

            code_constant_name = nick_to_constant_name(entry->value_nick);
            code_class_name = nick_to_class_name(entry->value_nick);

            if (strcmp(code_constant_name, code_class_name) != 0) {
                rbgobj_define_const(error_class,
                                    code_constant_name,
                                    INT2NUM(entry->value));
            }
            g_free(code_constant_name);

            code_class = rb_define_class_under(error_class,
                                               code_class_name,
                                               error_class);
            g_free(code_class_name);
            rb_define_const(code_class, name_CODE, INT2NUM(entry->value));
            rb_define_const(code_class, name_DOMAIN, rb_domain);
            rb_hash_aset(code_classes, INT2NUM(entry->value), code_class);
        }

        g_type_class_unref(gclass);
    }

    rb_obj_freeze(code_classes);

    return error_class;
}

static VALUE
rg_initialize(int argc, VALUE *argv, VALUE self)
{
    VALUE klass = rb_obj_class(self);
    rb_ivar_set(self, id_code, rb_const_get(klass, id_CODE));
    rb_ivar_set(self, id_domain, rb_const_get(klass, id_DOMAIN));
    return rb_call_super(argc, argv);
}

static VALUE
rbg_error_to_ruby(const GValue *from)
{
    GError *error;
    error = g_value_get_boxed(from);
    return GERROR2RVAL(error);
}

void
Init_glib_error(void)
{
    id_code = rb_intern("@code");
    id_CODE = rb_intern(name_CODE);
    id_domain = rb_intern("@domain");
    id_DOMAIN = rb_intern(name_DOMAIN);
    id_CODE_CLASSES = rb_intern(name_CODE_CLASSES);
    gerror_table = rb_hash_new();
    rb_global_variable(&gerror_table);

#define RG_TARGET_NAMESPACE error_info
    error_info = rb_define_module_under(rbg_mGLib(), "ErrorInfo");
    rb_define_attr(error_info, "code", TRUE, FALSE);
    rb_define_attr(error_info, "domain", TRUE, FALSE);
    RG_DEF_METHOD(initialize, -1);

    generic_error = rb_define_class_under(rbg_mGLib(), "Error", rb_eRuntimeError);
    rb_define_const(generic_error, name_CODE, Qnil);
    rb_define_const(generic_error, name_DOMAIN, Qnil);
    rb_include_module(generic_error, error_info);
}

void
Init_glib_error_conversions(void)
{
    rbgobj_register_g2r_func(G_TYPE_ERROR, rbg_error_to_ruby);
}
