//GVariant : GVariant — strongly typed value datatype
// https://developer.gnome.org/glib/2.26/glib-GVariant.html

package glib

// #include "gvariant.go.h"
// #include "glib.go.h"
import "C"

import (
	"errors"
	"fmt"
	"runtime"
	"unsafe"
)

/*
 * GVariant
 */

// IVariant is an interface type implemented by Variant and all types which embed
// an Variant.  It is meant to be used as a type for function arguments which
// require GVariants or any subclasses thereof.
type IVariant interface {
	ToGVariant() *C.GVariant
	ToVariant() *Variant
}

// A Variant is a representation of GLib's GVariant.
type Variant struct {
	GVariant *C.GVariant
}

// ToGVariant exposes the underlying *C.GVariant type for this Variant,
// necessary to implement IVariant.
func (v *Variant) ToGVariant() *C.GVariant {
	if v == nil {
		return nil
	}
	return v.native()
}

// ToVariant returns this Variant, necessary to implement IVariant.
func (v *Variant) ToVariant() *Variant {
	return v
}

// native returns a pointer to the underlying GVariant.
func (v *Variant) native() *C.GVariant {
	if v == nil || v.GVariant == nil {
		return nil
	}
	return v.GVariant
}

// Native returns a pointer to the underlying GVariant.
func (v *Variant) Native() uintptr {
	return uintptr(unsafe.Pointer(v.native()))
}

// newVariant wraps a native GVariant.
// Does NOT handle reference counting! Use takeVariant() to take ownership of values.
func newVariant(p *C.GVariant) *Variant {
	if p == nil {
		return nil
	}
	return &Variant{GVariant: p}
}

// TakeVariant wraps a unsafe.Pointer as a glib.Variant, taking ownership of it.
// This function is exported for visibility in other gotk3 packages and
// is not meant to be used by applications.
func TakeVariant(ptr unsafe.Pointer) *Variant {
	return takeVariant(C.toGVariant(ptr))
}

// takeVariant wraps a native GVariant,
// takes ownership and sets up a finalizer to free the instance during GC.
func takeVariant(p *C.GVariant) *Variant {
	if p == nil {
		return nil
	}
	obj := &Variant{GVariant: p}

	if obj.IsFloating() {
		obj.RefSink()
	} else {
		obj.Ref()
	}

	runtime.SetFinalizer(obj, func(v *Variant) { FinalizerStrategy(v.Unref) })
	return obj
}

// IsFloating returns true if the variant has a floating reference count.
// Reference counting is usually handled in the gotk layer,
// most applications should not call this.
func (v *Variant) IsFloating() bool {
	return gobool(C.g_variant_is_floating(v.native()))
}

// Ref is a wrapper around g_variant_ref.
// Reference counting is usually handled in the gotk layer,
// most applications should not need to call this.
func (v *Variant) Ref() {
	C.g_variant_ref(v.native())
}

// RefSink is a wrapper around g_variant_ref_sink.
// Reference counting is usually handled in the gotk layer,
// most applications should not need to call this.
func (v *Variant) RefSink() {
	C.g_variant_ref_sink(v.native())
}

// TakeRef is a wrapper around g_variant_take_ref.
// Reference counting is usually handled in the gotk layer,
// most applications should not need to call this.
func (v *Variant) TakeRef() {
	C.g_variant_take_ref(v.native())
}

// Unref is a wrapper around g_variant_unref.
// Reference counting is usually handled in the gotk layer,
// most applications should not need to call this.
func (v *Variant) Unref() {
	C.g_variant_unref(v.native())
}

// VariantFromInt16 is a wrapper around g_variant_new_int16
func VariantFromInt16(value int16) *Variant {
	return takeVariant(C.g_variant_new_int16(C.gint16(value)))
}

// VariantFromInt32 is a wrapper around g_variant_new_int32
func VariantFromInt32(value int32) *Variant {
	return takeVariant(C.g_variant_new_int32(C.gint32(value)))
}

// VariantFromInt64 is a wrapper around g_variant_new_int64
func VariantFromInt64(value int64) *Variant {
	return takeVariant(C.g_variant_new_int64(C.gint64(value)))
}

// VariantFromByte is a wrapper around g_variant_new_byte
func VariantFromByte(value uint8) *Variant {
	return takeVariant(C.g_variant_new_byte(C.guint8(value)))
}

// VariantFromUint16 is a wrapper around g_variant_new_uint16
func VariantFromUint16(value uint16) *Variant {
	return takeVariant(C.g_variant_new_uint16(C.guint16(value)))
}

// VariantFromUint32 is a wrapper around g_variant_new_uint32
func VariantFromUint32(value uint32) *Variant {
	return takeVariant(C.g_variant_new_uint32(C.guint32(value)))
}

// VariantFromUint64 is a wrapper around g_variant_new_uint64
func VariantFromUint64(value uint64) *Variant {
	return takeVariant(C.g_variant_new_uint64(C.guint64(value)))
}

// VariantFromBoolean is a wrapper around g_variant_new_boolean
func VariantFromBoolean(value bool) *Variant {
	return takeVariant(C.g_variant_new_boolean(gbool(value)))
}

// VariantFromFloat64 is a wrapper around g_variant_new_double().
// I chose to respect the Golang float64 nomenclature instead
// of 'double' 'C'. Corresponding VariantType is: 'VARIANT_TYPE_DOUBLE'
func VariantFromFloat64(value float64) *Variant {
	return takeVariant(C.g_variant_new_double(C.gdouble(value)))
}

// VariantFromString is a wrapper around g_variant_new_string/g_variant_new_take_string.
// Uses g_variant_new_take_string to reduce memory allocations if possible.
func VariantFromString(value string) *Variant {
	cstr := (*C.gchar)(C.CString(value))
	// g_variant_new_take_string takes owhership of the cstring and will call free() on it when done.
	// Do NOT free this string in this function!
	return takeVariant(C.g_variant_new_take_string(cstr))
}

// VariantFromVariant is a wrapper around g_variant_new_variant.
func VariantFromVariant(value *Variant) *Variant {
	return takeVariant(C.g_variant_new_variant(value.native()))
}

// TypeString returns the g variant type string for this variant.
func (v *Variant) TypeString() string {
	// the string returned from this belongs to GVariant and must not be freed.
	return C.GoString((*C.char)(C.g_variant_get_type_string(v.native())))
}

// IsContainer returns true if the variant is a container and false otherwise.
func (v *Variant) IsContainer() bool {
	return gobool(C.g_variant_is_container(v.native()))
}

// GetBoolean returns the bool value of this variant.
func (v *Variant) GetBoolean() bool {
	return gobool(C.g_variant_get_boolean(v.native()))
}

// GetDouble is a wrapper around g_variant_get_double()
func (v *Variant) GetDouble() float64 {
	return float64(C.g_variant_get_double(v.native()))
}

// GetString is a wrapper around g_variant_get_string.
// It returns the string value of the variant.
func (v *Variant) GetString() string {

	// The string value remains valid as long as the GVariant exists, do NOT free the cstring in this function.
	var len C.gsize
	gc := C.g_variant_get_string(v.native(), &len)

	// This is opposed to g_variant_dup_string, which copies the string.
	// g_variant_dup_string is not implemented,
	// as we copy the string value anyways when converting to a go string.

	return C.GoStringN((*C.char)(gc), (C.int)(len))
}

// GetVariant is a wrapper around g_variant_get_variant.
// It unboxes a nested GVariant.
func (v *Variant) GetVariant() *Variant {
	c := C.g_variant_get_variant(v.native())
	if c == nil {
		return nil
	}
	// The returned value is returned with full ownership transfer,
	// only Unref(), don't Ref().
	obj := newVariant(c)
	runtime.SetFinalizer(obj, func(v *Variant) { FinalizerStrategy(v.Unref) })
	return obj
}

// GetStrv returns a slice of strings from this variant.  It wraps
// g_variant_get_strv, but returns copies of the strings instead.
func (v *Variant) GetStrv() []string {
	gstrv := C.g_variant_get_strv(v.native(), nil)
	// we do not own the memory for these strings, so we must not use strfreev
	// but we must free the actual pointer we receive (transfer container).
	// We don't implement g_variant_dup_strv which copies the strings,
	// as we need to copy anyways when converting to go strings.
	c := gstrv
	defer C.g_free(C.gpointer(gstrv))
	var strs []string

	for *c != nil {
		strs = append(strs, C.GoString((*C.char)(*c)))
		c = C.next_gcharptr(c)
	}
	return strs
}

// GetObjv returns a slice of object paths from this variant.  It wraps
// g_variant_get_objv, but returns copies of the strings instead.
func (v *Variant) GetObjv() []string {
	gstrv := C.g_variant_get_objv(v.native(), nil)
	// we do not own the memory for these strings, so we must not use strfreev
	// but we must free the actual pointer we receive (transfer container).
	// We don't implement g_variant_dup_objv which copies the strings,
	// as we need to copy anyways when converting to go strings.
	c := gstrv
	defer C.g_free(C.gpointer(gstrv))
	var strs []string

	for *c != nil {
		strs = append(strs, C.GoString((*C.char)(*c)))
		c = C.next_gcharptr(c)
	}
	return strs
}

// GetInt returns the int64 value of the variant if it is an integer type, and
// an error otherwise.  It wraps variouns `g_variant_get_*` functions dealing
// with integers of different sizes.
func (v *Variant) GetInt() (int64, error) {
	t := v.TypeString()
	var i int64
	switch t {
	case "n":
		i = int64(C.g_variant_get_int16(v.native()))
	case "i":
		i = int64(C.g_variant_get_int32(v.native()))
	case "x":
		i = int64(C.g_variant_get_int64(v.native()))
	default:
		return 0, fmt.Errorf("variant type %s not a signed integer type", t)
	}
	return i, nil
}

// GetUint returns the uint64 value of the variant if it is an integer type, and
// an error otherwise.  It wraps variouns `g_variant_get_*` functions dealing
// with integers of different sizes.
func (v *Variant) GetUint() (uint64, error) {
	t := v.TypeString()
	var i uint64
	switch t {
	case "y":
		i = uint64(C.g_variant_get_byte(v.native()))
	case "q":
		i = uint64(C.g_variant_get_uint16(v.native()))
	case "u":
		i = uint64(C.g_variant_get_uint32(v.native()))
	case "t":
		i = uint64(C.g_variant_get_uint64(v.native()))
	default:
		return 0, fmt.Errorf("variant type %s not an unsigned integer type", t)
	}
	return i, nil
}

// Type returns the VariantType for this variant.
func (v *Variant) Type() *VariantType {
	// The return value is valid for the lifetime of value and must not be freed.
	return newVariantType(C.g_variant_get_type(v.native()))
}

// IsType returns true if the variant's type matches t.
func (v *Variant) IsType(t *VariantType) bool {
	return gobool(C.g_variant_is_of_type(v.native(), t.native()))
}

// String wraps g_variant_print().  It returns a string understood
// by g_variant_parse().
func (v *Variant) String() string {
	gc := C.g_variant_print(v.native(), gbool(false))
	defer C.g_free(C.gpointer(gc))
	return C.GoString((*C.char)(gc))
}

// AnnotatedString wraps g_variant_print(), but returns a type-annotated
// string.
func (v *Variant) AnnotatedString() string {
	gc := C.g_variant_print(v.native(), gbool(true))
	defer C.g_free(C.gpointer(gc))
	return C.GoString((*C.char)(gc))
}

// TODO:
//gint	g_variant_compare ()
//GVariantClass	g_variant_classify ()
//gboolean	g_variant_check_format_string ()
//void	g_variant_get ()
//void	g_variant_get_va ()
//GVariant *	g_variant_new ()
//GVariant *	g_variant_new_va ()
//GVariant *	g_variant_new_handle ()
//GVariant *	g_variant_new_printf ()
//GVariant *	g_variant_new_object_path ()
//gboolean	g_variant_is_object_path ()
//GVariant *	g_variant_new_signature ()
//gboolean	g_variant_is_signature ()
//GVariant *	g_variant_new_strv ()
//GVariant *	g_variant_new_objv ()
//GVariant *	g_variant_new_bytestring ()
//GVariant *	g_variant_new_bytestring_array ()
//guchar	g_variant_get_byte ()
//gint16	g_variant_get_int16 ()
//guint16	g_variant_get_uint16 ()
//gint32	g_variant_get_int32 ()
//guint32	g_variant_get_uint32 ()
//gint64	g_variant_get_int64 ()
//guint64	g_variant_get_uint64 ()
//gint32	g_variant_get_handle ()
//gdouble	g_variant_get_double ()
//const gchar *	g_variant_get_bytestring ()
//gchar *	g_variant_dup_bytestring ()
//const gchar **	g_variant_get_bytestring_array ()
//gchar **	g_variant_dup_bytestring_array ()
//GVariant *	g_variant_new_maybe ()
//GVariant *	g_variant_new_array ()
//GVariant *	g_variant_new_tuple ()
//GVariant *	g_variant_new_dict_entry ()
//GVariant *	g_variant_new_fixed_array ()
//GVariant *	g_variant_get_maybe ()
//gsize	g_variant_n_children ()
//GVariant *	g_variant_get_child_value ()
//void	g_variant_get_child ()
//GVariant *	g_variant_lookup_value ()
//gboolean	g_variant_lookup ()
//gconstpointer	g_variant_get_fixed_array ()
//gsize	g_variant_get_size ()
//gconstpointer	g_variant_get_data ()
//GBytes *	g_variant_get_data_as_bytes ()
//void	g_variant_store ()
//GVariant *	g_variant_new_from_data ()
//GVariant *	g_variant_new_from_bytes ()
//GVariant *	g_variant_byteswap ()
//GVariant *	g_variant_get_normal_form ()
//gboolean	g_variant_is_normal_form ()
//guint	g_variant_hash ()
//gboolean	g_variant_equal ()
//gchar *	g_variant_print ()
//GString *	g_variant_print_string ()
//GVariantIter *	g_variant_iter_copy ()
//void	g_variant_iter_free ()
//gsize	g_variant_iter_init ()
//gsize	g_variant_iter_n_children ()
//GVariantIter *	g_variant_iter_new ()
//GVariant *	g_variant_iter_next_value ()
//gboolean	g_variant_iter_next ()
//gboolean	g_variant_iter_loop ()
//void	g_variant_builder_unref ()
//GVariantBuilder *	g_variant_builder_ref ()
//GVariantBuilder *	g_variant_builder_new ()
//void	g_variant_builder_init ()
//void	g_variant_builder_clear ()
//void	g_variant_builder_add_value ()
//void	g_variant_builder_add ()
//void	g_variant_builder_add_parsed ()
//GVariant *	g_variant_builder_end ()
//void	g_variant_builder_open ()
//void	g_variant_builder_close ()
//void	g_variant_dict_unref ()
//GVariantDict *	g_variant_dict_ref ()
//GVariantDict *	g_variant_dict_new ()
//void	g_variant_dict_init ()
//void	g_variant_dict_clear ()
//gboolean	g_variant_dict_contains ()
//gboolean	g_variant_dict_lookup ()
//GVariant *	g_variant_dict_lookup_value ()
//void	g_variant_dict_insert ()
//void	g_variant_dict_insert_value ()
//gboolean	g_variant_dict_remove ()
//GVariant *	g_variant_dict_end ()
//#define	G_VARIANT_PARSE_ERROR

// VariantParse is a wrapper around g_variant_parse()
func VariantParse(vType *VariantType, text string) (*Variant, error) {
	cstr := C.CString(text)
	defer C.free(unsafe.Pointer(cstr))
	var gerr *C.GError
	c := C.g_variant_parse(vType.native(), (*C.gchar)(cstr), nil, nil, &gerr)
	if c == nil {
		defer C.g_error_free(gerr)
		return nil, errors.New(goString(gerr.message))
	}
	// will be freed during GC
	return takeVariant(c), nil
}

//GVariant *	g_variant_new_parsed_va ()
//GVariant *	g_variant_new_parsed ()
//gchar *	g_variant_parse_error_print_context ()
