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

#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif

#include <string.h>

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

// grep '^#define igloo_ERROR_' include/igloo/error.h | sed 's/^#define igloo_ERROR_\([^ ]*\) \+\([^ ]\+ *-\?[0-9][^ ]\+\) */\1::/; s/::\/\* \+\([^:]\+\): \+\(.\+\) \+\*\//:\1:\2/; s/::\/\* \+\([^:]\+\) \+\*\//:\1:/' | while IFS=: read name message description; do uuid=$(uuid -v 5 c4ce7102-4f5b-4421-9fb7-adbb299e336e "$name"); p='    '; printf "$p{\n$p${p}ERROR_BASE(%s),\n$p$p.flags = 0,\n" "$name"; [ -n "$message" ] && printf "$p$p.message = \"%s\",\n" "$message"; [ -n "$description" ] && printf "$p$p.description = \"%s\",\n" "$description"; printf "$p$p.uuid = \"%s\",\n$p$p.akindof = NULL\n$p},\n" "$uuid"; done | sed '$s/,$//'

#define ERROR_BASE(x) .control = igloo_CONTROL_INIT3(igloo_CONTROL_VERSION_BUILD(0), sizeof(igloo_error_desc_t), igloo_CONTROL_TYPE__ERROR_DESC), \
                      .error = igloo_ERROR_ ## x, \
                      .name = # x
static const igloo_error_desc_t errors[] = {
    {
        ERROR_BASE(GENERIC),
        .flags = 0,
        .message = "Generic error",
        .description = "A generic error occurred.",
        .uuid = "4f4dfebc-0b86-51e7-8233-73ff1c4707cf",
        .akindof = NULL
    },
    {
        ERROR_BASE(NONE),
        .flags = 0,
        .message = "No error",
        .description = "The operation succeeded.",
        .uuid = "3f9fa7d9-3d35-5358-bfa4-0c708bc2d7c9",
        .akindof = NULL
    },
    {
        ERROR_BASE(PERM),
        .flags = 0,
        .message = "Operation not permitted",
        .uuid = "bd371bfb-1e55-5819-aeb5-e8a085e287cf",
        .akindof = NULL
    },
    {
        ERROR_BASE(NOENT),
        .flags = 0,
        .message = "No such file, directory, or object",
        .uuid = "0bd73191-c0a8-5a60-8986-590aad6f937c",
        .akindof = NULL
    },
    {
        ERROR_BASE(BUSY),
        .flags = 0,
        .message = "Device or resource busy",
        .uuid = "5d867873-72ba-569c-bf62-f9c784b9ef3f",
        .akindof = NULL
    },
    {
        ERROR_BASE(CONNREFUSED),
        .flags = 0,
        .message = "Connection refused",
        .uuid = "9213226e-3e88-5475-8da7-584861b9aa15",
        .akindof = NULL
    },
    {
        ERROR_BASE(NOSYS),
        .flags = 0,
        .message = "Function not implemented",
        .uuid = "5a6344ec-1c3e-5fb4-99ae-12443f7a9179",
        .akindof = NULL
    },
    {
        ERROR_BASE(RANGE),
        .flags = 0,
        .message = "Result out of range",
        .uuid = "945b9377-4829-51cc-9f70-60f5333ec08b",
        .akindof = NULL
    },
    {
        ERROR_BASE(NOMEM),
        .flags = 0,
        .message = "Not enough space",
        .uuid = "3c236243-fb2e-5f63-a8ff-255a496ff998",
        .akindof = NULL
    },
    {
        ERROR_BASE(INVAL),
        .flags = 0,
        .message = "Invalid argument",
        .uuid = "1146a208-c6aa-59be-93c5-e8c431e48892",
        .akindof = NULL
    },
    {
        ERROR_BASE(BADRQC),
        .flags = 0,
        .message = "Invalid request code",
        .uuid = "9f2794e7-861d-53d3-8667-f7cc92d9f3be",
        .akindof = NULL
    },
    {
        ERROR_BASE(DOM),
        .flags = 0,
        .message = "Mathematics argument out of domain of function",
        .uuid = "900a300c-47b3-5a32-ad0d-f8149ae2d975",
        .akindof = NULL
    },
    {
        ERROR_BASE(FAULT),
        .flags = 0,
        .message = "Invalid address",
        .uuid = "92ef9c60-654f-550d-a256-dc80a85ba0a0",
        .akindof = NULL
    },
    {
        ERROR_BASE(IO),
        .flags = 0,
        .message = "I/O-Error",
        .uuid = "25541978-3f69-5d63-bd06-c86e299d8f10",
        .akindof = NULL
    },
    {
        ERROR_BASE(LOOP),
        .flags = 0,
        .message = "Too many recursions",
        .uuid = "5a10d13d-987f-5caa-bfbd-b555d94afd7a",
        .akindof = NULL
    },
    {
        ERROR_BASE(TYPEMM),
        .flags = 0,
        .message = "Type mismatch",
        .description = "Object of different type required",
        .uuid = "b3546924-99b9-5382-8121-095432d5381e",
        .akindof = NULL
    },
    {
        ERROR_BASE(NOTCONN),
        .flags = 0,
        .message = "Socket or object not connected",
        .uuid = "31706d95-1edd-54e8-b92f-3eb3c2b10624",
        .akindof = NULL
    },
    {
        ERROR_BASE(TIMEDOUT),
        .flags = 0,
        .message = "Connection timed out",
        .uuid = "0faef6a6-d0a4-5c80-9bb8-e3e2d839b907",
        .akindof = NULL
    },
    {
        ERROR_BASE(AGAIN),
        .flags = 0,
        .message = "Resource temporarily unavailable",
        .uuid = "8b8d4d4b-ff8f-53e0-ad82-aa4d15a4c64b",
        .akindof = NULL
    },
    {
        ERROR_BASE(LINKDOWN),
        .flags = 0,
        .message = "Physical or logical link down",
        .uuid = "74e8d6a7-4504-51d8-b86f-bcede84ed640",
        .akindof = NULL
    },
    {
        ERROR_BASE(ILLSEQ),
        .flags = 0,
        .message = "Illegal byte sequence",
        .uuid = "b5e35670-1abf-53aa-9766-7904ad4951e6",
        .akindof = NULL
    },
    {
        ERROR_BASE(ADDRINUSE),
        .flags = 0,
        .message = "Address in use",
        .uuid = "1740486d-1631-50c3-8e39-5dbd77d4ff4c",
        .akindof = NULL
    },
    {
        ERROR_BASE(NSVERSION),
        .flags = 0,
        .message = "Not supported version",
        .uuid = "8e71a2e4-e00f-5ae0-bdb8-4ed2cd97c841",
        .akindof = NULL
    },
    {
        ERROR_BASE(INPROGRESS),
        .flags = 0,
        .message = "Operation in progress",
        .uuid = "f624c70c-a549-5a3e-aab4-931016b5d7f2",
        .akindof = NULL
    },
    {
        ERROR_BASE(NETUNREACH),
        .flags = 0,
        .message = "Network unreachable",
        .uuid = "3ca95721-d122-598a-8410-07c505493520",
        .akindof = NULL
    },
    {
        ERROR_BASE(ISCONN),
        .flags = 0,
        .message = "Socket/Object is connected",
        .uuid = "01570eac-8669-5060-96e4-9f645ea0fc40",
        .akindof = NULL
    },
    {
        ERROR_BASE(CONNRST),
        .flags = 0,
        .message = "Connection reset",
        .uuid = "be9205a5-d88a-5813-9e44-f41d49b8fb49",
        .akindof = NULL
    },
    {
        ERROR_BASE(DESTADDRREQ),
        .flags = 0,
        .message = "Destination address required",
        .uuid = "2a39890c-5afb-510a-911d-4225c500d402",
        .akindof = NULL
    },
    {
        ERROR_BASE(AFNOTSUP),
        .flags = 0,
        .message = "Address family not supported",
        .uuid = "08dac630-6c99-579a-a6a2-b03e7c405775",
        .akindof = NULL
    },
    {
        ERROR_BASE(SWITCHPROTO),
        .flags = 0,
        .message = "Switch protocol",
        .uuid = "3a141c3d-3f91-5c38-bde8-eee0a6df8324",
        .akindof = NULL
    },
    {
        ERROR_BASE(GONE),
        .flags = 0,
        .message = "Resource gone",
        .uuid = "40894986-e34a-5597-81d9-5a172934ead0",
        .akindof = NULL
    },
    {
        ERROR_BASE(BADSTATE),
        .flags = 0,
        .message = "Object is in bad/wrong state",
        .uuid = "7eced33a-68e2-5dc3-afa3-237fb85c2302",
        .akindof = NULL
    }
};

#ifdef HAVE_ERRNO_H
static const struct {
    const intmax_t value;
    const igloo_error_t error;
} errno_to_error[] = {
    {0,         igloo_ERROR_NONE},
#ifdef EPERM
	{EPERM,		igloo_ERROR_PERM},
#endif
#ifdef ENOENT
    {ENOENT,    igloo_ERROR_NOENT},
#endif
#ifdef EBUSY
	{EBUSY,		igloo_ERROR_BUSY},
#endif
#ifdef ECONNREFUSED
	{ECONNREFUSED,	igloo_ERROR_CONNREFUSED},
#endif
#ifdef ENOSYS
    {ENOSYS,    igloo_ERROR_NOSYS},
#endif
#ifdef ERANGE
    {ERANGE,    igloo_ERROR_RANGE},
#endif
#ifdef ENOMEM
    {ENOMEM,    igloo_ERROR_NOMEM},
#endif
#ifdef EINVAL
    {EINVAL,    igloo_ERROR_INVAL},
#endif
#ifdef EBADRQC
	{EBADRQC,	igloo_ERROR_BADRQC},
#endif
#ifdef EDOM
    {EDOM,      igloo_ERROR_DOM},
#endif
#ifdef EFAULT
    {EFAULT,    igloo_ERROR_FAULT},
#endif
#ifdef EIO
	{EIO,		igloo_ERROR_IO},
#endif
#ifdef ELOOP
    {ELOOP,     igloo_ERROR_LOOP},
#endif
#ifdef ENOTCONN
	{ENOTCONN,	igloo_ERROR_NOTCONN},
#endif
#ifdef ETIMEDOUT
	{ETIMEDOUT,	igloo_ERROR_TIMEDOUT},
#endif
#ifdef EAGAIN
	{EAGAIN,	igloo_ERROR_AGAIN},
#endif
#ifdef EILSEQ
    {EILSEQ,    igloo_ERROR_ILLSEQ},
#endif
#ifdef EADDRINUSE
	{EADDRINUSE,	igloo_ERROR_ADDRINUSE},
#endif
#ifdef EINPROGRESS
	{EINPROGRESS,	igloo_ERROR_INPROGRESS},
#endif
#ifdef ENETUNREACH
	{ENETUNREACH,	igloo_ERROR_NETUNREACH},
#endif
#ifdef EISCONN
	{EISCONN,	igloo_ERROR_ISCONN},
#endif
#ifdef ECONNRESET
	{ECONNRESET,	igloo_ERROR_CONNRST},
#endif
#ifdef EDESTADDRREQ
	{EDESTADDRREQ,	igloo_ERROR_DESTADDRREQ},
#endif
#ifdef EAFNOSUPPORT
	{EAFNOSUPPORT,	igloo_ERROR_AFNOTSUP}
#endif
};
#endif

const igloo_error_desc_t *      igloo_error_get_description(igloo_error_t error)
{
    size_t i;

    for (i = 0; i < (sizeof(errors)/sizeof(*errors)); i++) {
        if (errors[i].error == error)
            return &(errors[i]);
    }

    return NULL;
}

const igloo_error_desc_t *      igloo_error_getbyname(const char *name)
{
    size_t i;

    for (i = 0; i < (sizeof(errors)/sizeof(*errors)); i++) {
        if (strcmp(errors[i].name, name) == 0)
            return &(errors[i]);
    }

    return NULL;
}


static inline igloo_error_t     igloo_error_by_domain__errno(intmax_t value, igloo_error_t *error)
{
    size_t i;

    for (i = 0; i < (sizeof(errno_to_error)/sizeof(*errno_to_error)); i++) {
        if (errno_to_error[i].value == value) {
            *error = igloo_ERROR_NONE;
            return errno_to_error[i].error;
        }
    }

    *error = igloo_ERROR_NOENT;
    return igloo_ERROR_GENERIC;
}

static inline igloo_error_t     igloo_error_by_domain__ra_error(intmax_t value, igloo_error_t *error)
{

    if (value == (intmax_t)256 || value == (intmax_t)65535 ||
        value == (intmax_t)16777215 || value == (intmax_t)4294967295) {
        *error = igloo_ERROR_INVAL;
        return igloo_ERROR_GENERIC;
    }

    if (value < -1 || value > 65534) {
        *error = igloo_ERROR_RANGE;
        return igloo_ERROR_GENERIC;
    }

    *error = igloo_ERROR_NONE;
    return value;
}

igloo_error_t                   igloo_error_by_domain(igloo_error_domain_t domain, intmax_t value, igloo_error_t *error)
{
    igloo_error_t error_buffer;

    if (!error)
        error = &error_buffer;

    switch (domain) {
#ifdef HAVE_ERRNO_H
        case igloo_ERROR_DOMAIN_ERRNO:
            return igloo_error_by_domain__errno(value, error);
        break;
#endif
        case igloo_ERROR_DOMAIN_RA_ERROR:
            return igloo_error_by_domain__ra_error(value, error);
        break;
    }

    if (error)
        *error = igloo_ERROR_NOSYS;
    return igloo_ERROR_GENERIC;
}

igloo_error_t                   igloo_error_from_system(igloo_error_t *error)
{
#ifdef HAVE_ERRNO_H
    return igloo_error_by_domain(igloo_ERROR_DOMAIN_ERRNO, errno, error);
#else
    if (error)
        *error = igloo_ERROR_NOSYS;
    return igloo_ERROR_GENERIC;
#endif
}

igloo_error_t                   igloo_error_clear_system(void)
{
#ifdef HAVE_ERRNO_H
    errno = 0;
    return igloo_ERROR_NONE;
#else
    return igloo_ERROR_NOSYS;
#endif
}
