/*
 * Copyright (c) 1994, 2021 Alex Leigh, All Rights Reserved
 *
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF ALEX LEIGH The copyright
 * notice above does not evidence any actual or intended publication of such
 * source code.
 *
 * Disclosure, copying, distribution, or transmission of this code without
 * express permission is prohibited.
 */

#include <stdlib.h>
#include <assert.h>
#include "mecha.h"
#include "da.h"

static short mFda_sz_ensure(struct mSda *array, mTda_sz len);

static short mFda_sz_set(struct mSda *array, mTda_sz new_len);

/**
 * Initialize the given dynamic array.
 *
 * If the initialization completes successfully, MECHA_OK is returned, otherwise, MECHA_ERR is returned and the
 * dynamic string is in an undefined state.
 *
 * Flags indicate the behavior of the dynamic array. Currently, the only supported flag is M_DA_FLAG_NULLTERMINATE.
 * If supplied, the dynamic array will maintain a null terminator one byte past the last entry in the array. This is useful for
 * maintaining null-terminate for dynamic arrays holding C strings.
 *
 * MTS: Unsafe.
 *
 * @param array The dynamic string to initialize.
 * @return MECHA_OK or MECHA_ERR.
 */
int mFda_init(struct mSda *array, mTda_sz elem_sz, uint8_t flags) {
    assert(array != NULL);
    assert(elem_sz >= 0);

    memset(array, 0, sizeof(struct mSda));
    array->elem_sz = elem_sz;
    array->flags = flags;

    return MECHA_OK;
}

/**
 * Release the resources within the given dynamic string. This function always succeeds.
 *
 * It is the responsibility of the caller to free the underlying resources for the string structure itself.
 *
 * MTS: Unsafe.
 *
 * @param array The dynamic string to free.
 */
// TODO: Add support for a free_func
void mFda_free(struct mSda *array, void (*free_func)(void *)) {
    if (free_func != NULL) {
        for (mTda_sz i = 0; i < array->len; i++) {
            void *p = array->buf + (i * array->elem_sz);
            free_func(p);
        }
    }
    if (array->buf != NULL) free(array->buf);
}

/**
 * Ensure that there is at least room for new_len more elements of elem_sz in the dynamic buffer.
 *
 * If null_term is set to 1, then an extra byte will be allowed to serve as a null terminator.
 *
 * If the list is smaller than the number of elements, the extra elements will be released.
 *
 *  MTS: Unsafe.
 *
 * @param array
 * @param new_len
 * @param elem_sz
 * @param null_term
 * @return
 */
static short mFda_sz_set(struct mSda *array, const mTda_sz new_len) {
    assert(array != NULL);

    if (array->sz == new_len) return MECHA_OK;

    size_t realloc_sz = new_len * array->elem_sz;
    if ((array->flags & M_DA_FLAG_NULLTERMINATE) > 0) realloc_sz++;
    char *np = realloc(array->buf, realloc_sz);
    if (np == NULL) return MECHA_ERR;

    array->buf = np;
    array->sz = new_len;
    if (array->len > array->sz) array->len = array->sz;

    return MECHA_OK;
}

/**
 * Ensure that there is space for at least elem_sz more elements in the given array. If sufficient space is not
 * available, more space will be allocated into the array before this function returns.
 *
 * MTS: Unsafe.
 *
 * @param array
 * @param len
 * @param elem_sz
 * @param null_term
 * @return
 */
static short mFda_sz_ensure(struct mSda *array, const mTda_sz len) {
    assert(array != NULL);
    assert(len >= 0);

    size_t free_elems = array->sz - array->len;
    if (free_elems >= len) return MECHA_OK;
    return mFda_sz_set(array, array->len + len);
}

/**
 * Concatenate src_len elements of elem_sz size in bytes into the array dst. If null_term is 1, then the dst
 * array will be null terminated.
 *
 * MTS: Unsafe.
 *
 * @param dst The destination dynamic buffer.
 * @param src The source buffer.
 * @param src_len The length of the source buffer in elements, not including any NULL terminator.
 * @param elem_sz The individual element size in bytes.
 * @param null_term Set to 1 if the buffer should be null terminated (as is the case with C-style strings)
 * @return  MECHA_OK or MECHA_ERR
 */
int mFda_cat(struct mSda *dst, const void *src, mTda_sz src_len) {
    assert(dst != NULL);
    assert(src != NULL);
    assert(src_len >= 0);

    if (mFda_sz_ensure(dst, src_len) != MECHA_OK)
        return MECHA_ERR;

    size_t bytes = dst->elem_sz * src_len;

    char *d = dst->buf + dst->len;
    memcpy(d, src, bytes);
    dst->len += src_len;

    if ((dst->flags & M_DA_FLAG_NULLTERMINATE) > 0) {
        dst->buf[dst->len * dst->elem_sz] = 0;
    }

    return MECHA_OK;
}

/**
 * Return a pointer to the nth element in the dynamic array, or, NULL if no such element exists.
 */
void *mFda_ptr(struct mSda *array, mTda_sz elem) {
    assert(array != NULL);
    assert(elem >= 0);

    if (elem > array->len - 1) return NULL;

    return array->buf + (elem * array->elem_sz);
}
