/* Copyright (C) 2018       Marvin Scholz <epirat07@gmail.com>
 * Copyright (C) 2018-2020  Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 */

#ifndef _LIBIGLOO__RO_H_
#define _LIBIGLOO__RO_H_

#ifdef __cplusplus
extern "C" {
#endif

#include <pthread.h>
#include <stdbool.h>
#include <stdarg.h>

#include <igloo/config.h>
#include <igloo/types.h>
#include <igloo/feature.h>
#include <igloo/rwlock.h>

igloo_FEATURE_FORWARD(igloo_ro_feature_ownlock);

/* Type used for callback called then the object is actually freed
 * That is once all references to it are gone.
 *
 * This function must not try to deallocate or alter self.
 *
 * The callback is called with the object in write lock.
 */
typedef void (*igloo_ro_free_t)(igloo_ro_t self);

/* Type used for callback called then the object is created
 * using the generic igloo_ro_new().
 *
 * Additional parameters passed to igloo_ro_new() are passed
 * in the list ap. All limitations of <stdarg.h> apply.
 *
 * This function must return igloo_ERROR_NONE in case of success and
 * the corresponding error in case of error.
 * In case of error igloo_ro_unref() is called internally to clear the object.
 */
typedef igloo_error_t (*igloo_ro_new_t)(igloo_ro_t self, const igloo_ro_type_t *type, va_list ap);

/* Type used for callback called when error value is requested from an object.
 *
 * The callback is called with the object in read lock.
 */
typedef igloo_error_t (*igloo_ro_get_error_t)(igloo_ro_t self, igloo_error_t *result);

/* Type used to store flags for stringify operation. */
typedef uint_least32_t igloo_ro_sy_t;
/* No stringify flags set. Usefull for variable initialization. */
#define igloo_RO_SY_NONE            ((igloo_ro_sy_t)0x0000)
/* Stringify using defaults. */
#define igloo_RO_SY_DEFAULT         ((igloo_ro_sy_t)0x1000)
/* Allow to stringify the object itself, do not touch the content. */
#define igloo_RO_SY_OBJECT          ((igloo_ro_sy_t)0x0001)
/* Allow to stringify the object's content. */
#define igloo_RO_SY_CONTENT         ((igloo_ro_sy_t)0x0002)
/* Allow to stringify the object in a way useful for debugging. */
#define igloo_RO_SY_DEBUG           ((igloo_ro_sy_t)0x0004)

/* Type used for callback called when the object needs to be converted to a string.
 *
 * This is used mostly for debugging or preseting the object to the user.
 * The callback is not expected to return a string that can be used to reconstruct the object.
 *
 * The callback is called with the object in write lock for mutable objects and
 * in read lock for immutable objects.
 */
typedef igloo_error_t (*igloo_ro_stringify_t)(igloo_ro_t self, char **result, igloo_ro_sy_t flags);

/* Type used for callback called when the object is queried for it's features.
 *
 * The callback only needs to check for it's own layer. All parent types are also queried.
 *
 * The callback is called with the object in read lock.
 */
typedef igloo_error_t (*igloo_ro_can_t)(igloo_ro_t self, bool *result, igloo_feature_t *feature);

/* ---[ PRIVATE ]--- */
/*
 * Those declarations are defined here as they must be known to the compiler.
 * Nobody should ever try to access them directly.
 */
typedef void (*igloo_ro_raw_func_ptr_t)(void);
struct igloo_ro_type_tag {
    /* Control structure */
    igloo_control_t             control;

    /* Total length of the objects to be created */
    size_t                      type_length;
    /* Name of type */
    const char *                type_name;

    /* Parent type */
    const igloo_ro_type_t *     type_parent;

    /* Features supported by this type */
    const igloo_feature_t ***   type_features;
    size_t                      type_features_length; // in elements

    /* Type flags
     * WARNING: This is not really a pointer. It is only a pointer
     * to keep the data structure as-is before the next SONAME change.
     *
     * REWRITE BEFORE RELEASE 1.0.0: Change from pointer to integer type.
     */
    const void *                type_flags;

    /* STILL UNUSED: Type description */
    const char *                type_description;
    const char *                type_uuid;
    const char *                type_application; // report XML
    const void *                type_reserved_ptr_0;
    const void *                type_reserved_ptr_1;
    const void *                type_reserved_ptr_2;

    /* Callback to be called on final free() */
    igloo_ro_free_t             type_freecb;
    /* Callback to be called by igloo_ro_new() */
    igloo_ro_new_t              type_newcb;

    /* Callbacks */
    igloo_ro_get_error_t        type_get_errorcb;
    igloo_ro_stringify_t        type_stringifycb;
    igloo_ro_can_t              type_cancb;

    /* STILL UNUSED: for possible future use */
    igloo_ro_raw_func_ptr_t     type_clonecb;
    igloo_ro_raw_func_ptr_t     type_convertcb;
    igloo_ro_raw_func_ptr_t     type_get_interfacecb;
    igloo_ro_raw_func_ptr_t     type_comparecb;
    igloo_ro_raw_func_ptr_t     type_sendto;
    igloo_ro_raw_func_ptr_t     type_recvfrom;
    igloo_ro_raw_func_ptr_t     type_test;
    igloo_ro_raw_func_ptr_t     type_reserved_func_0;
    igloo_ro_raw_func_ptr_t     type_reserved_func_1;
    igloo_ro_raw_func_ptr_t     type_reserved_func_2;
    igloo_ro_raw_func_ptr_t     type_reserved_func_3;
} igloo_ATTR_T_ALIGNED(8);
struct igloo_ro_stub_tag {
    /* Type of the object */
    const igloo_ro_type_t * type;
    /* Reference counters */
    uint32_t refc;
    uint32_t wrefc;
    /* object group */
    igloo_ro_object_group_t * group;
} igloo_ATTR_T_ALIGNED(8);
struct igloo_ro_tiny_tag {
    igloo_ro_stub_t stub;
};
struct igloo_ro_full_tag {
    igloo_ro_stub_t stub;
    /* lock for igloo_ro_*() */
    igloo_rwlock_t lock;
};
igloo_error_t igloo_ro_new__return_zero(igloo_ro_t self, const igloo_ro_type_t *type, va_list ap);
igloo_ro_t igloo_RO_TO_TYPE_raw(igloo_ro_t object, const igloo_ro_type_t *type) igloo_ATTR_F_HOT;
bool igloo_RO_IS_VALID_raw(igloo_ro_t object, const igloo_ro_type_t *type) igloo_ATTR_F_HOT;
bool igloo_RO_HAS_TYPE_raw(igloo_ro_t object, const igloo_ro_type_t *type) igloo_ATTR_F_HOT igloo_ATTR_F_PURE;
igloo_error_t   igloo_ro_ref_raw(igloo_ro_t self, igloo_ro_t *out, const igloo_ro_type_t *type);
igloo_error_t   igloo_ro_unref_raw(igloo_ro_t *self);
igloo_error_t   igloo_ro_weak_ref_raw(igloo_ro_t self, igloo_ro_t *out, const igloo_ro_type_t *type);
igloo_error_t   igloo_ro_weak_unref_raw(igloo_ro_t *self);
igloo_error_t   igloo_ro_ref_replace_raw(igloo_ro_t self, igloo_ro_t *out, const igloo_ro_type_t *type);
igloo_error_t   igloo_ro_weak_ref_replace_raw(igloo_ro_t self, igloo_ro_t *out, const igloo_ro_type_t *type);
igloo_error_t   igloo_ro_new__raw(igloo_ro_t *out, const igloo_ro_type_t *type, igloo_ro_t group);
igloo_error_t   igloo_ro_new__simple(igloo_ro_t *out, const igloo_ro_type_t *type, igloo_ro_t group, ...);
void            igloo_ro_locked_start(igloo_ro_t self, const igloo_ro_type_t *type, bool writelock);
void            igloo_ro_locked_end(igloo_ro_t self, const igloo_ro_type_t *type);
igloo_error_t   igloo_ro_get_error_raw(igloo_ro_t self, igloo_error_t *result, const igloo_ro_type_t *type);
igloo_error_t   igloo_ro_stringify_raw(igloo_ro_t self, char **result, igloo_ro_sy_t flags, const igloo_ro_type_t *type);
bool            igloo_ro_can_raw(igloo_ro_t self, igloo_feature_t *feature, const igloo_ro_type_t *type);
/* ---[ END PRIVATE ]--- */


#ifdef igloo_ATTR_T_TRANSPARENT_UNION
#define igloo_RO__GETSTUB(x)        (((igloo_ro_t)(x)).subtype__igloo_ro_stub_t)
#define igloo_RO_NULL               ((igloo_ro_t)(igloo_ro_stub_t*)NULL)
#define igloo_ro_is_null(x)         (igloo_RO__GETSTUB((x)) == NULL)
#define igloo_ro_to_type(x,type)    (((igloo_ro_t)igloo_RO_TO_TYPE_raw((x), igloo_RO_GET_TYPE_BY_SYMBOL(type))).subtype__ ## type)
#else
#define igloo_RO__GETSTUB(x)        ((igloo_ro_stub_t*)(x))
#define igloo_RO_NULL               NULL
#define igloo_ro_is_null(x)         ((x) == NULL)
#define igloo_ro_to_type(x,type)    ((type*)igloo_RO_TO_TYPE_raw((x), igloo_RO_GET_TYPE_BY_SYMBOL(type)))
#endif

#define igloo_RO_GET_TYPE(x)        (igloo_RO__GETSTUB((x)) == NULL ? NULL : igloo_RO__GETSTUB((x))->type)
#define igloo_RO_GET_TYPENAME(x)    (igloo_RO_GET_TYPE((x)) == NULL ? NULL : igloo_RO_GET_TYPE((x))->type_name)
#define igloo_ro_is_valid(x,type)   igloo_RO_IS_VALID_raw((x), igloo_RO_GET_TYPE_BY_SYMBOL(type))
#define igloo_ro_has_type(x,type)   igloo_RO_HAS_TYPE_raw((x), (type))

#define igloo_ro_is_same(a,b)       (igloo_RO__GETSTUB((a)) == igloo_RO__GETSTUB((b)))

/* Create a new object.
 *
 * Parameters:
 *  out
 *      The a pointer to the address object to create.
 *  type
 *      The type of new object to create.
 *  group
 *      The object group to make this object part of.
 *      If the passed object is a instance the default group for the instance is used.
 *      If any other object is used the new object will join the same group as that object.
 */

#ifdef igloo_HAVE_STATEMENT_EXPRESSIONS
#define         igloo_ro_new_raw(out, type, group)  igloo_ro_new__raw(({type ** igloo_tmp_arg = (out); (void)igloo_tmp_arg; (igloo_ro_t*)(out);}), igloo_RO_GET_OWN_TYPE_BY_SYMBOL(type), (group))
#define         igloo_ro_new(out, type, group, ...) igloo_ro_new__simple(({type ** igloo_tmp_arg = (out); (void)igloo_tmp_arg; (igloo_ro_t*)(out);}), igloo_RO_GET_TYPE_BY_SYMBOL(type), (group), ## __VA_ARGS__)
#else
#define         igloo_ro_new_raw(out, type, group)  igloo_ro_new__raw((igloo_ro_t*)(type**)(out), igloo_RO_GET_OWN_TYPE_BY_SYMBOL(type), (group))
#define         igloo_ro_new(out, type, group, ...) igloo_ro_new__simple((igloo_ro_t*)(type**)(out), igloo_RO_GET_TYPE_BY_SYMBOL(type), (group), ## __VA_ARGS__)
#endif

/* This gets a new reference (via out) for the given object (self) */
#ifdef igloo_HAVE_STATEMENT_EXPRESSIONS
#define igloo_ro_ref(self, out, type) ({type ** igloo_tmp_arg = (out); const igloo_error_t igloo_tmp_error = igloo_ro_ref_raw((self), (igloo_ro_t*)igloo_tmp_arg, igloo_RO_GET_TYPE_BY_SYMBOL(type)); igloo_tmp_error;})
#define igloo_ro_ref_replace(self, out, type) ({type ** igloo_tmp_arg = (out); const igloo_error_t igloo_tmp_error = igloo_ro_ref_replace_raw((self), (igloo_ro_t*)igloo_tmp_arg, igloo_RO_GET_TYPE_BY_SYMBOL(type)); igloo_tmp_error;})
#else
#define igloo_ro_ref(self, out, type) igloo_ro_ref_raw((self), (igloo_ro_t*)(type**)(out), igloo_RO_GET_TYPE_BY_SYMBOL(type))
#define igloo_ro_ref_replace(self, out, type) igloo_ro_ref_replace_raw((self), (igloo_ro_t*)(type**)(out), igloo_RO_GET_TYPE_BY_SYMBOL(type))
#endif
#define igloo_ro_ref_transparent(self, out) igloo_ro_ref_raw((self), (out), igloo_RO_GET_TYPE_BY_SYMBOL(igloo_ro_t))
#define igloo_ro_ref_replace_transparent(self, out) igloo_ro_ref_replace_raw((self), (out), igloo_RO_GET_TYPE_BY_SYMBOL(igloo_ro_t))
/* This clears a reference to the object.
 * If the object's reference counter reaches zero the object is freed.
 */
#ifdef igloo_HAVE_STATEMENT_EXPRESSIONS
#define igloo_ro_unref(self) igloo_ro_unref_raw(({igloo_ro_t igloo_tmp_arg = (igloo_ro_t)*(self); (void)igloo_tmp_arg; (igloo_ro_t*)(self);}))
#else
#define igloo_ro_unref(self) igloo_ro_unref_raw((igloo_ro_t*)(self))
#endif

/* This is the same as igloo_ro_ref() and igloo_ro_unref() but increases/decreases the weak reference counter. */
#define igloo_ro_weak_ref_transparent(self, out) igloo_ro_weak_ref_raw((self), (out), igloo_RO_GET_TYPE_BY_SYMBOL(igloo_ro_t))
#define igloo_ro_weak_ref_replace_transparent(self, out) igloo_ro_weak_ref_replace_raw((self), (out), igloo_RO_GET_TYPE_BY_SYMBOL(igloo_ro_t))
#ifdef igloo_HAVE_STATEMENT_EXPRESSIONS
#define igloo_ro_weak_ref(self, out, type) ({type ** igloo_tmp_arg = (out); const igloo_error_t igloo_tmp_error = igloo_ro_weak_ref_raw((self), (igloo_ro_t*)igloo_tmp_arg, igloo_RO_GET_TYPE_BY_SYMBOL(type)); igloo_tmp_error;})
#define igloo_ro_weak_ref_replace(self, out, type) ({type ** igloo_tmp_arg = (out); const igloo_error_t igloo_tmp_error = igloo_ro_weak_ref_replace_raw((self), (igloo_ro_t*)igloo_tmp_arg, (igloo_RO_GET_TYPE_BY_SYMBOL(type)); igloo_tmp_error;})
#define igloo_ro_weak_unref(self) igloo_ro_weak_unref_raw(({igloo_ro_t igloo_tmp_arg = (igloo_ro_t)*(self); (void)igloo_tmp_arg; (igloo_ro_t*)(self);}))
#else
#define igloo_ro_weak_ref(self, out, type) igloo_ro_weak_ref_raw((self), (igloo_ro_t*)(type**)(out), igloo_RO_GET_TYPE_BY_SYMBOL(type))
#define igloo_ro_weak_ref_replace(self, out, type) igloo_ro_weak_ref_replace_raw((self), (igloo_ro_t*)(type**)(out), igloo_RO_GET_TYPE_BY_SYMBOL(type))
#define igloo_ro_weak_unref(self) igloo_ro_weak_unref_raw((igloo_ro_t*)(self))
#endif

/* Locked section */
#ifdef igloo_HAVE_STATEMENT_EXPRESSIONS
#define igloo_ro_locked_raw(self, type, code, writelock) ({type * igloo_tmp_arg = (self); (void)igloo_tmp_arg;}); igloo_ro_locked_start((self), igloo_RO_GET_OWN_TYPE_BY_SYMBOL(type), (writelock)); do { code; } while (false); igloo_ro_locked_end((self), igloo_RO_GET_OWN_TYPE_BY_SYMBOL(type))
#else
#define igloo_ro_locked_raw(self, type, code, writelock) igloo_ro_locked_start((self), igloo_RO_GET_OWN_TYPE_BY_SYMBOL(type), (writelock)); do { code; } while (false); igloo_ro_locked_end((self), igloo_RO_GET_OWN_TYPE_BY_SYMBOL(type))
#endif
#define igloo_ro_rlocked(self, type, code) igloo_ro_locked_raw((self), type, code, false)
#define igloo_ro_wlocked(self, type, code) igloo_ro_locked_raw((self), type, code, true)

/* This gets a reference to the object's instance object. */
igloo_error_t   igloo_ro_get_instance(igloo_ro_t self, igloo_ro_t *instance) igloo_ATTR_F_WARN_UNUSED_RESULT;

/* This gets a reference to the object's object group object. */
igloo_error_t   igloo_ro_get_object_group(igloo_ro_t self, igloo_ro_object_group_t **object_group) igloo_ATTR_F_WARN_UNUSED_RESULT;

/* Get error value from a object.
 *
 * Parameters:
 *  self
 *      The Object to request error value from.
 *  result
 *      Pointer to the location the error value should be stored.
 *      The value is only written if igloo_ERROR_NONE is returned.
 * Returns:
 *  The result of the query.
 */
#define igloo_ro_get_error(self, result) igloo_ro_get_error_raw(self, result, igloo_RO_GET_TYPE(self))
#define igloo_ro_get_error_parent(self, result, type) igloo_ro_get_error_raw(self, result, igloo_RO_GET_OWN_TYPE_BY_SYMBOL(type))

/* Convert a object to a string.
 * This is used for debugging and presenting to the user.
 *
 * The resulting string can not be used to recreate the object.
 *
 * Parameters:
 *  self
 *      The object to convert to a string.
 *  result
 *      A string as allocated using malloc(3). The caller must call free(3).
 *  flags
 *      Flags used to select options to the conversion.
 *      Should normally be igloo_RO_SY_DEFAULT.
 * Returns:
 *  Error code for this call.
 */
#define igloo_ro_stringify(self, result, flags) igloo_ro_stringify_raw((self), (result), (flags), igloo_RO_GET_TYPE((self)))
#define igloo_ro_stringify_parent(self, result, flags, type) igloo_ro_stringify_raw((self), (result), (flags), igloo_RO_GET_OWN_TYPE_BY_SYMBOL(type))

/* Checks if a object supports a specific feature.
 *
 * Parameters:
 *  self
 *      The object to check.
 *  feature
 *      The feature to test for.
 * Returns:
 *  Whether the feature is supported or false if self is igloo_RO_NULL, or feature is NULL.
 */
#define igloo_ro_can(self, feature) igloo_ro_can_raw((self), (feature), igloo_RO_GET_TYPE((self)))
#define igloo_ro_can_parent(self, feature, type) igloo_ro_can_raw((self), (feature), igloo_RO_GET_OWN_TYPE_BY_SYMBOL(type))

#ifdef __cplusplus
}
#endif

#endif /* ! _LIBIGLOO__RO_H_ */
