/* -*- coding: utf-8 -*-
 * ----------------------------------------------------------------------
 * Copyright © 2011-2013, RedJack, LLC.
 * All rights reserved.
 *
 * Please see the COPYING file in this distribution for license details.
 * ----------------------------------------------------------------------
 */

#include <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <string.h>

#include "libcork/config.h"
#include "libcork/core/allocator.h"
#include "libcork/core/error.h"
#include "libcork/ds/buffer.h"
#include "libcork/os/process.h"
#include "libcork/threads/basics.h"


/*-----------------------------------------------------------------------
 * Life cycle
 */

struct cork_error {
    cork_error  code;
    struct cork_buffer  *message;
    struct cork_buffer  *other;
    struct cork_buffer  buf1;
    struct cork_buffer  buf2;
    struct cork_error  *next;
};

static struct cork_error *
cork_error_new(void)
{
    struct cork_error  *error = cork_new(struct cork_error);
    error->code = CORK_ERROR_NONE;
    cork_buffer_init(&error->buf1);
    cork_buffer_init(&error->buf2);
    error->message = &error->buf1;
    error->other = &error->buf2;
    return error;
}

static void
cork_error_free(struct cork_error *error)
{
    cork_buffer_done(&error->buf1);
    cork_buffer_done(&error->buf2);
    free(error);
}


static struct cork_error * volatile  errors;

cork_once_barrier(cork_error_list);

static void
cork_error_list_done(void)
{
    struct cork_error  *curr;
    struct cork_error  *next;
    for (curr = errors; curr != NULL; curr = next) {
        next = curr->next;
        cork_error_free(curr);
    }
}

static void
cork_error_list_init(void)
{
    cork_cleanup_at_exit(0, cork_error_list_done);
}


cork_tls(struct cork_error *, cork_error_);

static struct cork_error *
cork_error_get(void)
{
    struct cork_error  **error_ptr = cork_error__get();
    if (CORK_UNLIKELY(*error_ptr == NULL)) {
        struct cork_error  *old_head;
        struct cork_error  *error = cork_error_new();
        cork_once(cork_error_list, cork_error_list_init());
        do {
            old_head = errors;
            error->next = old_head;
        } while (cork_ptr_cas(&errors, old_head, error) != old_head);
        *error_ptr = error;
        return error;
    } else {
        return *error_ptr;
    }
}


/*-----------------------------------------------------------------------
 * Public error API
 */

bool
cork_error_occurred(void)
{
    struct cork_error  *error = cork_error_get();
    return error->code != CORK_ERROR_NONE;
}

cork_error
cork_error_code(void)
{
    struct cork_error  *error = cork_error_get();
    return error->code;
}

const char *
cork_error_message(void)
{
    struct cork_error  *error = cork_error_get();
    return error->message->buf;
}

void
cork_error_clear(void)
{
    struct cork_error  *error = cork_error_get();
    error->code = CORK_ERROR_NONE;
    cork_buffer_clear(error->message);
}

void
cork_error_set_printf(cork_error code, const char *format, ...)
{
    va_list  args;
    struct cork_error  *error = cork_error_get();
    error->code = code;
    va_start(args, format);
    cork_buffer_vprintf(error->message, format, args);
    va_end(args);
}

void
cork_error_set_string(cork_error code, const char *str)
{
    struct cork_error  *error = cork_error_get();
    error->code = code;
    cork_buffer_set_string(error->message, str);
}

void
cork_error_set_vprintf(cork_error code, const char *format, va_list args)
{
    struct cork_error  *error = cork_error_get();
    error->code = code;
    cork_buffer_vprintf(error->message, format, args);
}

void
cork_error_prefix_printf(const char *format, ...)
{
    va_list  args;
    struct cork_error  *error = cork_error_get();
    struct cork_buffer  *temp;
    va_start(args, format);
    cork_buffer_vprintf(error->other, format, args);
    va_end(args);
    cork_buffer_append_copy(error->other, error->message);
    temp = error->other;
    error->other = error->message;
    error->message = temp;
}

void
cork_error_prefix_string(const char *str)
{
    struct cork_error  *error = cork_error_get();
    struct cork_buffer  *temp;
    cork_buffer_set_string(error->other, str);
    cork_buffer_append_copy(error->other, error->message);
    temp = error->other;
    error->other = error->message;
    error->message = temp;
}

void
cork_error_prefix_vprintf(const char *format, va_list args)
{
    struct cork_error  *error = cork_error_get();
    struct cork_buffer  *temp;
    cork_buffer_vprintf(error->other, format, args);
    cork_buffer_append_copy(error->other, error->message);
    temp = error->other;
    error->other = error->message;
    error->message = temp;
}


/*-----------------------------------------------------------------------
 * Deprecated
 */

void
cork_error_set(uint32_t error_class, unsigned int error_code,
               const char *format, ...)
{
    /* Create a fallback error code that's most likely not very useful. */
    va_list  args;
    va_start(args, format);
    cork_error_set_vprintf(error_class + error_code, format, args);
    va_end(args);
}

void
cork_error_prefix(const char *format, ...)
{
    va_list  args;
    va_start(args, format);
    cork_error_prefix_vprintf(format, args);
    va_end(args);
}


/*-----------------------------------------------------------------------
 * Built-in errors
 */

void
cork_system_error_set_explicit(int err)
{
    cork_error_set_string(err, strerror(err));
}

void
cork_system_error_set(void)
{
    cork_error_set_string(errno, strerror(errno));
}

void
cork_unknown_error_set_(const char *location)
{
    cork_error_set_printf(CORK_UNKNOWN_ERROR, "Unknown error in %s", location);
}
