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

#include <string.h>

#include "libcork/core/error.h"
#include "libcork/core/types.h"
#include "libcork/ds/managed-buffer.h"
#include "libcork/ds/slice.h"
#include "libcork/helpers/errors.h"


/*-----------------------------------------------------------------------
 * Error handling
 */

static void
cork_slice_invalid_slice_set(size_t buf_size, size_t requested_offset,
                             size_t requested_length)
{
    cork_error_set
        (CORK_SLICE_ERROR, CORK_SLICE_INVALID_SLICE,
         "Cannot slice %zu-byte buffer at %zu:%zu",
         buf_size, requested_offset, requested_length);
}


/*-----------------------------------------------------------------------
 * Slices
 */

void
cork_slice_clear(struct cork_slice *slice)
{
    slice->buf = NULL;
    slice->size = 0;
    slice->iface = NULL;
    slice->user_data = NULL;
}


int
cork_slice_copy(struct cork_slice *dest, const struct cork_slice *slice,
                size_t offset, size_t length)
{
    if ((slice != NULL) &&
        (offset <= slice->size) &&
        ((offset + length) <= slice->size)) {
        /*
        DEBUG("Slicing <%p:%zu> at %zu:%zu, gives <%p:%zu>",
              slice->buf, slice->size,
              offset, length,
              slice->buf + offset, length);
        */
        return slice->iface->copy(dest, slice, offset, length);
    }

    else {
        /*
        DEBUG("Cannot slice <%p:%zu> at %zu:%zu",
              slice->buf, slice->size,
              offset, length);
        */
        cork_slice_clear(dest);
        cork_slice_invalid_slice_set
            ((slice == NULL)? 0: slice->size, offset, length);
        return -1;
    }
}


int
cork_slice_copy_offset(struct cork_slice *dest, const struct cork_slice *slice,
                       size_t offset)
{
    if (slice == NULL) {
        cork_slice_clear(dest);
        cork_slice_invalid_slice_set(0, offset, 0);
        return -1;
    } else {
        return cork_slice_copy
            (dest, slice, offset, slice->size - offset);
    }
}


int
cork_slice_light_copy(struct cork_slice *dest, const struct cork_slice *slice,
                      size_t offset, size_t length)
{
    if ((slice != NULL) &&
        (offset <= slice->size) &&
        ((offset + length) <= slice->size)) {
        /*
        DEBUG("Slicing <%p:%zu> at %zu:%zu, gives <%p:%zu>",
              slice->buf, slice->size,
              offset, length,
              slice->buf + offset, length);
        */
        return slice->iface->light_copy(dest, slice, offset, length);
    }

    else {
        /*
        DEBUG("Cannot slice <%p:%zu> at %zu:%zu",
              slice->buf, slice->size,
              offset, length);
        */
        cork_slice_clear(dest);
        cork_slice_invalid_slice_set
            ((slice == NULL)? 0: slice->size, offset, length);
        return -1;
    }
}


int
cork_slice_light_copy_offset(struct cork_slice *dest,
                             const struct cork_slice *slice, size_t offset)
{
    if (slice == NULL) {
        cork_slice_clear(dest);
        cork_slice_invalid_slice_set(0, offset, 0);
        return -1;
    } else {
        return cork_slice_light_copy
            (dest, slice, offset, slice->size - offset);
    }
}


int
cork_slice_slice(struct cork_slice *slice, size_t offset, size_t length)
{
    if ((slice != NULL) &&
        (offset <= slice->size) &&
        ((offset + length) <= slice->size)) {
        /*
        DEBUG("Slicing <%p:%zu> at %zu:%zu, gives <%p:%zu>",
              slice->buf, slice->size,
              offset, length,
              slice->buf + offset, length);
        */
        if (slice->iface->slice == NULL) {
            slice->buf += offset;
            slice->size = length;
            return 0;
        } else {
            return slice->iface->slice(slice, offset, length);
        }
    }

    else {
        /*
        DEBUG("Cannot slice <%p:%zu> at %zu:%zu",
              slice->buf, slice->size,
              offset, length);
        */
        cork_slice_invalid_slice_set(slice->size, offset, length);
        return -1;
    }
}


int
cork_slice_slice_offset(struct cork_slice *slice, size_t offset)
{
    if (slice == NULL) {
        cork_slice_invalid_slice_set(0, offset, 0);
        return -1;
    } else {
        return cork_slice_slice
            (slice, offset, slice->size - offset);
    }
}


void
cork_slice_finish(struct cork_slice *slice)
{
    /*
    DEBUG("Finalizing <%p:%zu>", dest->buf, dest->size);
    */

    if (slice->iface != NULL && slice->iface->free != NULL) {
        slice->iface->free(slice);
    }

    cork_slice_clear(slice);
}


bool
cork_slice_equal(const struct cork_slice *slice1,
                 const struct cork_slice *slice2)
{
    if (slice1 == slice2) {
        return true;
    }

    if (slice1->size != slice2->size) {
        return false;
    }

    return (memcmp(slice1->buf, slice2->buf, slice1->size) == 0);
}


/*-----------------------------------------------------------------------
 * Slices of static content
 */

static struct cork_slice_iface  cork_static_slice;

static int
cork_static_slice_copy(struct cork_slice *dest, const struct cork_slice *src,
                       size_t offset, size_t length)
{
    dest->buf = src->buf + offset;
    dest->size = length;
    dest->iface = &cork_static_slice;
    dest->user_data = NULL;
    return 0;
}

static struct cork_slice_iface  cork_static_slice = {
    NULL,
    cork_static_slice_copy,
    cork_static_slice_copy,
    NULL
};

void
cork_slice_init_static(struct cork_slice *dest, const void *buf, size_t size)
{
    dest->buf = buf;
    dest->size = size;
    dest->iface = &cork_static_slice;
    dest->user_data = NULL;
}


/*-----------------------------------------------------------------------
 * Copy-once slices
 */

static struct cork_slice_iface  cork_copy_once_slice;

static int
cork_copy_once_slice__copy(struct cork_slice *dest,
                           const struct cork_slice *src,
                           size_t offset, size_t length)
{
    struct cork_managed_buffer  *mbuf =
        cork_managed_buffer_new_copy(src->buf, src->size);
    rii_check(cork_managed_buffer_slice(dest, mbuf, offset, length));
    rii_check(cork_managed_buffer_slice
              ((struct cork_slice *) src, mbuf, 0, src->size));
    cork_managed_buffer_unref(mbuf);
    return 0;
}

static int
cork_copy_once_slice__light_copy(struct cork_slice *dest,
                                 const struct cork_slice *src,
                                 size_t offset, size_t length)
{
    dest->buf = src->buf + offset;
    dest->size = length;
    dest->iface = &cork_copy_once_slice;
    dest->user_data = NULL;
    return 0;
}

static struct cork_slice_iface  cork_copy_once_slice = {
    NULL,
    cork_copy_once_slice__copy,
    cork_copy_once_slice__light_copy,
    NULL
};

void
cork_slice_init_copy_once(struct cork_slice *dest, const void *buf, size_t size)
{
    dest->buf = buf;
    dest->size = size;
    dest->iface = &cork_copy_once_slice;
    dest->user_data = NULL;
}
