/* 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.
 */

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <igloo/typedef.h>

typedef struct igloo_instance_tag igloo_instance_t;

#define igloo_RO_PRIVATETYPES igloo_RO_TYPE(igloo_instance_t)

#include <igloo/igloo.h>
#include <igloo/error.h>

#include "private.h"

struct igloo_instance_tag {
    igloo_ro_full_t __parent;
    igloo_error_t error;
    igloo_ro_t logger;
    igloo_instance_log_handler_t *logger_handler;
};

static igloo_error_t igloo_initialize__new(igloo_ro_t self, const igloo_ro_type_t *type, va_list ap);
static void igloo_initialize__free(igloo_ro_t self);
static igloo_error_t igloo_initialize__get_error(igloo_ro_t self, igloo_error_t *result);
static igloo_error_t igloo_initialize___stringify(igloo_ro_t self, char **result, igloo_ro_sy_t flags);

igloo_RO_PRIVATE_TYPE(igloo_instance_t, igloo_ro_full_t,
        igloo_RO_TYPEDECL_NEW(igloo_initialize__new),
        igloo_RO_TYPEDECL_FREE(igloo_initialize__free),
        igloo_RO_TYPEDECL_GET_ERROR(igloo_initialize__get_error),
        igloo_RO_TYPEDECL_STRINGIFY(igloo_initialize___stringify)
        );


igloo_error_t igloo_initialize__new(igloo_ro_t self, const igloo_ro_type_t *type, va_list ap)
{
    igloo_instance_t *instance = igloo_ro_to_type(self, igloo_instance_t);

    (void)type, (void)ap;

    instance->error = igloo_ERROR_NONE;

    return igloo_ERROR_NONE;
}

static void igloo_initialize__free(igloo_ro_t self)
{
    igloo_instance_t *instance = igloo_ro_to_type(self, igloo_instance_t);

    igloo_ro_weak_unref(&(instance->logger));
}

static igloo_error_t igloo_initialize__get_error(igloo_ro_t self, igloo_error_t *result)
{
    igloo_instance_t *instance = igloo_ro_to_type(self, igloo_instance_t);

    *result = instance->error;

    return igloo_ERROR_NONE;
}

static igloo_error_t render_error(igloo_error_t error, char *buffer, size_t len)
{
    const igloo_error_desc_t *desc = igloo_error_get_description(error);
    int ret;

    if (desc) {
        ret = snprintf(buffer, len, "%s (%s)", desc->name, desc->message);
    } else {
        ret = snprintf(buffer, len, "%" igloo_PRIerror, error);
    }

    if (ret < 1 || (size_t)ret >= len)
        return igloo_ERROR_FAULT;
    return igloo_ERROR_NONE;
}

static igloo_error_t igloo_initialize___stringify(igloo_ro_t self, char **result, igloo_ro_sy_t flags)
{
#define STRINGIFY_FORMAT "%s, error=%s}", from_parent, errorbuffer
    igloo_instance_t *instance = igloo_ro_to_type(self, igloo_instance_t);
    igloo_error_t error;
    char *from_parent;
    size_t from_parent_len;
    int ret, res;
    char errorbuffer[80];

    if (!(flags & igloo_RO_SY_DEBUG))
        return igloo_ERROR_NOSYS;

    error = render_error(instance->error, errorbuffer, sizeof(errorbuffer));
    if (error != igloo_ERROR_NONE)
        return error;

    error = igloo_ro_stringify_parent(self, &from_parent, flags, igloo_ro_full_t);
    if (error != igloo_ERROR_NONE)
        return error;

    from_parent_len = strlen(from_parent);
    if (from_parent_len < 2 || from_parent[from_parent_len - 1] != '}') {
        free(from_parent);
        return igloo_ERROR_FAULT;
    }

    from_parent[from_parent_len - 1] = 0;

    ret = snprintf(NULL, 0, STRINGIFY_FORMAT);
    if (ret < 1) {
        free(from_parent);
        return igloo_ERROR_FAULT;
    }

    *result = calloc(1, ret + 2);
    if (!*result) {
        free(from_parent);
        return igloo_ERROR_NOMEM;
    }

    res = snprintf(*result, ret + 1, STRINGIFY_FORMAT);
    if (ret < 1 || res > (ret + 1)) {
        free(from_parent);
        return igloo_ERROR_FAULT;
    }

    free(from_parent);

    return igloo_ERROR_NONE;
}

igloo_error_t   igloo_version_get(const char **str, int *major, int *minor, int *patch)
{
    if (str)
        *str = igloo_VERSION_STRING;

    if (major)
        *major = igloo_VERSION_MAJOR;
    if (minor)
        *minor = igloo_VERSION_MINOR;
    if (patch)
        *patch = igloo_VERSION_PATCH;

    return igloo_ERROR_NONE;
}

bool            igloo_version_check(int min_major, int min_minor, int min_patch, int max_major, int max_minor, int max_patch)
{
    if ((min_major < 0 && min_minor >= 0) || (min_minor < 0 && min_patch >= 0))
        return false;
    if ((max_major < 0 && max_minor >= 0) || (max_minor < 0 && max_patch >= 0))
        return false;

    if (min_major > igloo_VERSION_MAJOR)
        return false;

    if (min_major == igloo_VERSION_MAJOR && min_minor > igloo_VERSION_MINOR)
        return false;

    if (min_major == igloo_VERSION_MAJOR && min_minor == igloo_VERSION_MINOR && min_patch > igloo_VERSION_PATCH)
        return false;

    if (max_major >= 0) {
        if (max_major < igloo_VERSION_MAJOR)
            return false;

        if (max_minor >= 0) {
            if (max_major == igloo_VERSION_MAJOR && max_minor < igloo_VERSION_MINOR)
                return false;

            if (max_patch >= 0) {
                if (max_major == igloo_VERSION_MAJOR && max_minor == igloo_VERSION_MINOR && max_patch < igloo_VERSION_PATCH)
                    return false;
            }
        }
    }

    return true;
}

igloo_error_t   igloo_initialize(igloo_ro_t *instance)
{
    return igloo_ro_bootstrap(instance, igloo_RO_GET_TYPE_BY_SYMBOL(igloo_instance_t));
}

static inline igloo_instance_t * get_instance(igloo_ro_t obj)
{
    return igloo_ro_to_type(igloo_ro_get_instance_unsafe(obj, igloo_RO_GET_TYPE_BY_SYMBOL(igloo_instance_t)), igloo_instance_t);
}

bool            igloo_instance_can(igloo_ro_t instance, igloo_feature_t *feature)
{
    (void)instance, (void)feature;
    // No features supported yet.
    return false;
}

igloo_error_t igloo_instance_set_logger(igloo_ro_t self, igloo_ro_t logger, igloo_instance_log_handler_t *handler)
{
    igloo_instance_t *instance = get_instance(self);
    igloo_error_t error;

    if (!instance)
        return igloo_ERROR_FAULT;

    error = igloo_ro_weak_ref_replace_transparent(logger, &(instance->logger));
    if (error != igloo_ERROR_NONE)
        return error;

    if (igloo_ro_is_null(instance->logger)) {
        instance->logger_handler = NULL;
    } else {
        instance->logger_handler = handler;
    }

    return igloo_ERROR_NONE;
}

igloo_error_t igloo_instance_get_logger(igloo_ro_t self, igloo_ro_t *logger)
{
    igloo_instance_t *instance = get_instance(self);

    if (!instance)
        return igloo_ERROR_FAULT;

    return igloo_ro_ref_transparent(instance->logger, logger);
}
igloo_error_t igloo_instance_log(igloo_ro_t self, igloo_ro_t msg)
{
    igloo_instance_t *instance = get_instance(self);

    if (igloo_ro_is_null(msg))
        return igloo_ERROR_NONE;

    if (!instance)
        return igloo_ERROR_FAULT;

    if (!instance->logger_handler)
        return igloo_ERROR_BADSTATE;

    return instance->logger_handler(instance, instance->logger, msg);
}

igloo_error_t igloo_instance_validate(igloo_ro_t self)
{
    igloo_instance_t *instance = get_instance(self);
    if (!instance)
        return igloo_ERROR_FAULT;

    return igloo_ERROR_NONE;
}
