#define _LUNA_SYSTEM_ERROR_EXTENSIONS
#include <errno.h>
#include <limits.h>
#include <luna/Heap.h>
#include <luna/NumberParsing.h>
#include <luna/Utf8.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <unistd.h>

template <typename ArgT, typename ResultT> static inline ResultT __generic_div(ArgT a, ArgT b)
{
    ResultT result;
    result.quot = a / b;
    result.rem = a % b;

    if (a >= 0 && result.rem < 0)
    {
        result.quot++;
        result.rem -= b;
    }

    return result;
}

extern "C"
{
    int abs(int v)
    {
        return __builtin_abs(v);
    }

    long labs(long v)
    {
        return __builtin_labs(v);
    }

    long long llabs(long long v)
    {
        return __builtin_llabs(v);
    }

    div_t div(int num, int den)
    {
        return __generic_div<int, div_t>(num, den);
    }

    ldiv_t ldiv(long num, long den)
    {
        return __generic_div<long, ldiv_t>(num, den);
    }

    lldiv_t lldiv(long long num, long long den)
    {
        return __generic_div<long long, lldiv_t>(num, den);
    }

    int atoi(const char* s)
    {
        return (int)strtol(s, NULL, 10);
    }

    long atol(const char* s)
    {
        return strtol(s, NULL, 10);
    }

    // Assuming LP64, long long == long.
    long long atoll(const char* s)
    {
        return (long long)strtol(s, NULL, 10);
    }

    // These checks are only necessary on LLP64 platforms, where long won't match size_t/ssize_t. Probably redundant
    // then (since Luna follows the regular LP64 model), but oh well...
    long strtol(const char* str, char** endptr, int base)
    {
        isize rc = parse_signed_integer(str, const_cast<const char**>(endptr), base);

        if (rc > (isize)LONG_MAX) return LONG_MAX;
        if (rc < (isize)LONG_MIN) return LONG_MIN;

        return rc;
    }

    unsigned long strtoul(const char* str, char** endptr, int base)
    {
        usize rc = parse_unsigned_integer(str, const_cast<const char**>(endptr), base);

        if (rc > (usize)ULONG_MAX) return ULONG_MAX;

        return rc;
    }

    __noreturn void abort()
    {
        syscall(SYS_exit, 255);
        __builtin_unreachable();
    }

    __noreturn void _Exit(int status)
    {
        syscall(SYS_exit, status);
        __builtin_unreachable();
    }

    size_t mbstowcs(wchar_t* buf, const char* src, size_t max)
    {
        if (max == 0) return 0;

        Utf8StringDecoder decoder(src);

        if (!buf) { return decoder.code_points().value_or((size_t)-1); }

        return decoder.decode(buf, max).value_or((size_t)-1);
    }

    size_t wcstombs(char* buf, const wchar_t* src, size_t max)
    {
        if (max == 0) return 0;

        Utf8StringEncoder encoder(src);

        if (!buf) { return encoder.byte_length().value_or((size_t)-1); }

        return encoder.encode(buf, max).value_or((size_t)-1);
    }

    void* malloc(size_t size)
    {
        auto rc = malloc_impl(size);
        if (rc.has_error())
        {
            errno = rc.error();
            return nullptr;
        }
        return rc.value();
    }

    void* calloc(size_t nmemb, size_t size)
    {
        auto rc = calloc_impl(nmemb, size);
        if (rc.has_error())
        {
            errno = rc.error();
            return nullptr;
        }
        return rc.value();
    }

    void* realloc(void* ptr, size_t size)
    {
        auto rc = realloc_impl(ptr, size);
        if (rc.has_error())
        {
            errno = rc.error();
            return nullptr;
        }
        return rc.value();
    }

    void free(void* ptr)
    {
        free_impl(ptr);
    }
}