/*
 * Copyright (c) 2015-2026, The matio contributors
 * Copyright (c) 2012-2014, Christopher C. Hulbert
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "matio_private.h"
#include <stdlib.h>
#include <string.h>
#if defined(_MSC_VER) || defined(__MINGW32__)
#define strdup _strdup
#endif

static matvar_t *
VarCreateStruct(const char *name, int rank, const size_t *dims, const char *const *fields,
                unsigned nfields)
{
    size_t nelems = 1;
    int j;
    matvar_t *matvar;

    if ( NULL == dims )
        return NULL;

    matvar = Mat_VarCalloc();
    if ( NULL == matvar )
        return NULL;

    matvar->compression = MAT_COMPRESSION_NONE;
    if ( NULL != name )
        matvar->name = strdup(name);
    matvar->rank = rank;
    matvar->dims = (size_t *)malloc(matvar->rank * sizeof(*matvar->dims));
    for ( j = 0; j < matvar->rank; j++ ) {
        matvar->dims[j] = dims[j];
        nelems *= dims[j];
    }
    matvar->class_type = MAT_C_STRUCT;
    matvar->data_type = MAT_T_STRUCT;

    matvar->data_size = sizeof(matvar_t *);

    if ( nfields ) {
        matvar->internal->num_fields = nfields;
        matvar->internal->fieldnames =
            (char **)malloc(nfields * sizeof(*matvar->internal->fieldnames));
        if ( NULL == matvar->internal->fieldnames ) {
            Mat_VarFree(matvar);
            matvar = NULL;
        } else {
            size_t i;
            for ( i = 0; i < nfields; i++ ) {
                if ( NULL == fields[i] ) {
                    Mat_VarFree(matvar);
                    matvar = NULL;
                    break;
                } else {
                    matvar->internal->fieldnames[i] = strdup(fields[i]);
                }
            }
        }
        if ( NULL != matvar && nelems > 0 ) {
            size_t nelems_x_nfields;
            int err = Mul(&nelems_x_nfields, nelems, nfields);
            err |= Mul(&matvar->nbytes, nelems_x_nfields, matvar->data_size);
            if ( err ) {
                Mat_VarFree(matvar);
                return NULL;
            }
            matvar->data = calloc(nelems_x_nfields, matvar->data_size);
        }
    }

    return matvar;
}

/** @brief Creates a structure MATLAB variable with the given name and fields
 *
 * @ingroup MAT
 * @param name Name of the structure variable to create
 * @param rank Rank of the variable
 * @param dims array of dimensions of the variable of size rank
 * @param fields Array of @c nfields fieldnames
 * @param nfields Number of fields in the structure
 * @return Pointer to the new structure MATLAB variable on success, NULL on error
 */
matvar_t *
Mat_VarCreateStruct(const char *name, int rank, const size_t *dims, const char *const *fields,
                    unsigned nfields)
{
    return VarCreateStruct(name, rank, dims, fields, nfields);
}

/** @brief Creates a structure MATLAB variable with the given name and fields
 *
 * @ingroup MAT
 * @param name Name of the structure variable to create
 * @param rank Rank of the variable
 * @param dims array of dimensions of the variable of size rank
 * @param fields NULL-terminated array of fieldnames
 * @return Pointer to the new structure MATLAB variable on success, NULL on error
 */
matvar_t *
Mat_VarCreateStruct2(const char *name, int rank, const size_t *dims, const char *const *fields)
{
    unsigned count = 0;
    if ( NULL == fields )
        return VarCreateStruct(name, rank, dims, fields, count);

    while ( fields[count] ) {
        count++;
    }
    return VarCreateStruct(name, rank, dims, fields, count);
}

/** @brief Adds a field to a structure
 *
 * Adds the given field to the structure. fields should be an array of matvar_t
 * pointers of the same size as the structure (i.e. 1 field per structure
 * element).
 * @ingroup MAT
 * @param matvar Pointer to the Structure MAT variable
 * @param fieldname Name of field to be added
 * @retval 0 on success
 * @deprecated Use Mat_VarAddStructField2 instead.
 */
int
Mat_VarAddStructField(matvar_t *matvar, const char *fieldname)
{
    int err;
    int cnt = 0;
    size_t i, nfields, nelems = 1;
    matvar_t **new_data, **old_data;
    char **fieldnames;

    if ( matvar == NULL || fieldname == NULL )
        return -1;

    err = Mat_MulDims(matvar, &nelems);
    if ( err )
        return -1;

    nfields = matvar->internal->num_fields + 1;
    fieldnames = (char **)realloc(matvar->internal->fieldnames,
                                  nfields * sizeof(*matvar->internal->fieldnames));
    if ( NULL == fieldnames )
        return -1;
    matvar->internal->num_fields = nfields;
    matvar->internal->fieldnames = fieldnames;
    matvar->internal->fieldnames[nfields - 1] = strdup(fieldname);

    {
        size_t nelems_x_nfields;
        err = Mul(&nelems_x_nfields, nelems, nfields);
        err |= Mul(&matvar->nbytes, nelems_x_nfields, sizeof(*new_data));
        if ( err ) {
            matvar->nbytes = 0;
            return -1;
        }
    }
    new_data = (matvar_t **)malloc(matvar->nbytes);
    if ( new_data == NULL ) {
        matvar->nbytes = 0;
        return -1;
    }

    old_data = (matvar_t **)matvar->data;
    for ( i = 0; i < nelems; i++ ) {
        size_t f;
        for ( f = 0; f < nfields - 1; f++ )
            new_data[cnt++] = old_data[i * (nfields - 1) + f];
        new_data[cnt++] = NULL;
    }

    free(matvar->data);
    matvar->data = new_data;

    return 0;
}

/** @brief Returns the number of fields in a structure variable
 *
 * Returns the number of fields in the given structure.
 * @ingroup MAT
 * @param matvar Structure matlab variable
 * @returns Number of fields
 */
unsigned
Mat_VarGetNumberOfFields(const matvar_t *matvar)
{
    int nfields;
    if ( matvar == NULL || matvar->class_type != MAT_C_STRUCT || NULL == matvar->internal ) {
        nfields = 0;
    } else {
        nfields = matvar->internal->num_fields;
    }
    return nfields;
}

/** @brief Returns the fieldnames of a structure variable
 *
 * Returns the fieldnames for the given structure. The returned pointers are
 * internal to the structure and should not be free'd.
 * @ingroup MAT
 * @param matvar Structure matlab variable
 * @returns Array of fieldnames
 */
char *const *
Mat_VarGetStructFieldnames(const matvar_t *matvar)
{
    if ( matvar == NULL || matvar->class_type != MAT_C_STRUCT || NULL == matvar->internal ) {
        return NULL;
    } else {
        return matvar->internal->fieldnames;
    }
}

/** @brief Finds a field of a structure by the field's index
 *
 * Returns a pointer to the structure field at the given 0-relative index.
 * @ingroup MAT
 * @param matvar Pointer to the Structure MAT variable
 * @param field_index 0-relative index of the field.
 * @param index linear index of the structure array
 * @return Pointer to the structure field on success, NULL on error
 */
matvar_t *
Mat_VarGetStructFieldByIndex(const matvar_t *matvar, size_t field_index, size_t index)
{
    int err;
    matvar_t *field = NULL;
    size_t nelems = 1, nfields;

    if ( matvar == NULL || matvar->data == NULL || matvar->class_type != MAT_C_STRUCT ||
         matvar->data_size == 0 )
        return NULL;

    err = Mat_MulDims(matvar, &nelems);
    if ( err )
        return NULL;

    nfields = matvar->internal->num_fields;

    if ( nelems > 0 && index >= nelems ) {
        Mat_Critical("Mat_VarGetStructField: structure index out of bounds");
    } else if ( nfields > 0 ) {
        if ( field_index > nfields ) {
            Mat_Critical("Mat_VarGetStructField: field index out of bounds");
        } else {
            field = *((matvar_t **)matvar->data + index * nfields + field_index);
        }
    }

    return field;
}

/** @brief Finds a field of a structure by the field's name
 *
 * Returns a pointer to the structure field at the given 0-relative index.
 * @ingroup MAT
 * @param matvar Pointer to the Structure MAT variable
 * @param field_name Name of the structure field
 * @param index linear index of the structure array
 * @return Pointer to the structure field on success, NULL on error
 */
matvar_t *
Mat_VarGetStructFieldByName(const matvar_t *matvar, const char *field_name, size_t index)
{
    int i, nfields, field_index, err;
    matvar_t *field = NULL;
    size_t nelems = 1;

    if ( matvar == NULL || matvar->data == NULL || matvar->class_type != MAT_C_STRUCT ||
         matvar->data_size == 0 )
        return NULL;

    err = Mat_MulDims(matvar, &nelems);
    if ( err )
        return NULL;

    nfields = matvar->internal->num_fields;
    field_index = -1;
    for ( i = 0; i < nfields; i++ ) {
        if ( !strcmp(matvar->internal->fieldnames[i], field_name) ) {
            field_index = i;
            break;
        }
    }

    if ( index >= nelems ) {
        Mat_Critical("Mat_VarGetStructField: structure index out of bounds");
    } else if ( field_index >= 0 ) {
        field = *((matvar_t **)matvar->data + index * nfields + field_index);
    }

    return field;
}

/** @brief Finds a field of a structure
 *
 * Returns a pointer to the structure field at the given 0-relative index.
 * @ingroup MAT
 * @param matvar Pointer to the Structure MAT variable
 * @param name_or_index Name of the field, or the 1-relative index of the field
 * If the index is used, it should be the address of an integer variable whose
 * value is the index number.
 * @param opt MAT_BY_NAME if the name_or_index is the name or MAT_BY_INDEX if
 *            the index was passed.
 * @param index linear index of the structure to find the field of
 * @return Pointer to the Structure Field on success, NULL on error
 */
matvar_t *
Mat_VarGetStructField(const matvar_t *matvar, void *name_or_index, int opt, int index)
{
    int err, nfields;
    matvar_t *field = NULL;
    size_t nelems = 1;

    err = Mat_MulDims(matvar, &nelems);
    nfields = matvar->internal->num_fields;
    if ( index < 0 || (nelems > 0 && (size_t)index >= nelems) )
        err = 1;
    else if ( nfields < 1 )
        err = 1;

    if ( !err && (opt == MAT_BY_INDEX) ) {
        size_t field_index = *(int *)name_or_index;
        if ( field_index > 0 )
            field = Mat_VarGetStructFieldByIndex(matvar, field_index - 1, index);
    } else if ( !err && (opt == MAT_BY_NAME) ) {
        field = Mat_VarGetStructFieldByName(matvar, (const char *)name_or_index, index);
    }

    return field;
}

/** @brief Indexes a structure
 *
 * Finds structures of a structure array given a start, stride, and edge for
 * each dimension.  The structures are placed in a new structure array.  If
 * copy_fields is non-zero, the indexed structures are copied and should be
 * freed, but if copy_fields is zero, the indexed structures are pointers to
 * the original, but should still be freed. The structures have a flag set
 * so that the structure fields are not freed.
 *
 * Note that this function is limited to structure arrays with a rank less than
 * 10.
 *
 * @ingroup MAT
 * @param matvar Structure matlab variable
 * @param start vector of length rank with 0-relative starting coordinates for
 *              each dimension.
 * @param stride vector of length rank with strides for each dimension.
 * @param edge vector of length rank with the number of elements to read in
 *              each dimension.
 * @param copy_fields 1 to copy the fields, 0 to just set pointers to them.
 * @returns A new structure array with fields indexed from @c matvar.
 */
matvar_t *
Mat_VarGetStructs(const matvar_t *matvar, const int *start, const int *stride, const int *edge,
                  int copy_fields)
{
    size_t i, N, I, nfields, field,
        idx[10] =
            {
                0,
            },
        cnt[10] =
            {
                0,
            },
        dimp[10] = {
            0,
        };
    matvar_t **fields, *struct_slab;
    int j;

    if ( matvar == NULL || start == NULL || stride == NULL || edge == NULL ) {
        return NULL;
    } else if ( matvar->rank > 9 ) {
        return NULL;
    } else if ( matvar->class_type != MAT_C_STRUCT ) {
        return NULL;
    }

    struct_slab = Mat_VarDuplicate(matvar, 0);
    if ( !copy_fields )
        struct_slab->mem_conserve = 1;

    nfields = matvar->internal->num_fields;

    dimp[0] = matvar->dims[0];
    N = edge[0];
    I = start[0];
    struct_slab->dims[0] = edge[0];
    idx[0] = start[0];
    for ( j = 1; j < matvar->rank; j++ ) {
        idx[j] = start[j];
        dimp[j] = dimp[j - 1] * matvar->dims[j];
        N *= edge[j];
        I += start[j] * dimp[j - 1];
        struct_slab->dims[j] = edge[j];
    }
    I *= nfields;
    struct_slab->nbytes = N * nfields * sizeof(matvar_t *);
    struct_slab->data = malloc(struct_slab->nbytes);
    if ( struct_slab->data == NULL ) {
        Mat_VarFree(struct_slab);
        return NULL;
    }
    fields = (matvar_t **)struct_slab->data;
    for ( i = 0; i < N; i += edge[0] ) {
        for ( j = 0; j < edge[0]; j++ ) {
            for ( field = 0; field < nfields; field++ ) {
                if ( copy_fields )
                    fields[(i + j) * nfields + field] =
                        Mat_VarDuplicate(*((matvar_t **)matvar->data + I), 1);
                else
                    fields[(i + j) * nfields + field] = *((matvar_t **)matvar->data + I);
                I++;
            }
            I += (stride[0] - 1) * nfields;
        }
        idx[0] = start[0];
        I = idx[0];
        cnt[1]++;
        idx[1] += stride[1];
        for ( j = 1; j < matvar->rank; j++ ) {
            if ( cnt[j] == (size_t)edge[j] ) {
                cnt[j] = 0;
                idx[j] = start[j];
                if ( j < matvar->rank - 1 ) {
                    cnt[j + 1]++;
                    idx[j + 1] += stride[j + 1];
                }
            }
            I += idx[j] * dimp[j - 1];
        }
        I *= nfields;
    }
    return struct_slab;
}

/** @brief Indexes a structure
 *
 * Finds structures of a structure array given a single (linear)start, stride,
 * and edge.  The structures are placed in a new structure array.  If
 * copy_fields is non-zero, the indexed structures are copied and should be
 * freed, but if copy_fields is zero, the indexed structures are pointers to
 * the original, but should still be freed since the mem_conserve flag is set
 * so that the structures are not freed.
 * MAT file version must be 5.
 * @ingroup MAT
 * @param matvar Structure matlab variable
 * @param start starting index (0-relative)
 * @param stride stride (1 reads consecutive elements)
 * @param edge Number of elements to read
 * @param copy_fields 1 to copy the fields, 0 to just set pointers to them.
 * @returns A new structure with fields indexed from matvar
 */
matvar_t *
Mat_VarGetStructsLinear(const matvar_t *matvar, int start, int stride, int edge, int copy_fields)
{
    matvar_t *struct_slab;

    if ( matvar == NULL || matvar->rank > 10 ) {
        struct_slab = NULL;
    } else {
        int i, I, field, nfields;
        matvar_t **fields;

        struct_slab = Mat_VarDuplicate(matvar, 0);
        if ( !copy_fields )
            struct_slab->mem_conserve = 1;

        nfields = matvar->internal->num_fields;

        struct_slab->nbytes = (size_t)edge * nfields * sizeof(matvar_t *);
        struct_slab->data = malloc(struct_slab->nbytes);
        if ( struct_slab->data == NULL ) {
            Mat_VarFree(struct_slab);
            return NULL;
        }
        struct_slab->dims[0] = edge;
        struct_slab->dims[1] = 1;
        fields = (matvar_t **)struct_slab->data;
        I = start * nfields;
        for ( i = 0; i < edge; i++ ) {
            if ( copy_fields ) {
                for ( field = 0; field < nfields; field++ ) {
                    fields[i * nfields + field] =
                        Mat_VarDuplicate(*((matvar_t **)matvar->data + I), 1);
                    I++;
                }
            } else {
                for ( field = 0; field < nfields; field++ ) {
                    fields[i * nfields + field] = *((matvar_t **)matvar->data + I);
                    I++;
                }
            }
            I += (stride - 1) * nfields;
        }
    }
    return struct_slab;
}

/** @brief Sets the structure field to the given variable
 *
 * Sets the structure field specified by the 0-relative field index
 * @c field_index for the given 0-relative structure index @c index to
 * @c field.
 * @ingroup MAT
 * @param matvar Pointer to the structure MAT variable
 * @param field_index 0-relative index of the field.
 * @param index linear index of the structure array
 * @param field New field variable
 * @return Pointer to the previous field (NULL if no previous field)
 */
matvar_t *
Mat_VarSetStructFieldByIndex(matvar_t *matvar, size_t field_index, size_t index, matvar_t *field)
{
    int err;
    matvar_t *old_field = NULL;
    size_t nelems = 1, nfields;

    if ( matvar == NULL || matvar->class_type != MAT_C_STRUCT || matvar->data == NULL )
        return NULL;

    err = Mat_MulDims(matvar, &nelems);
    if ( err )
        return NULL;

    nfields = matvar->internal->num_fields;

    if ( index < nelems && field_index < nfields ) {
        matvar_t **fields = (matvar_t **)matvar->data;
        old_field = fields[index * nfields + field_index];
        fields[index * nfields + field_index] = field;
        if ( NULL != field->name ) {
            free(field->name);
        }
        field->name = strdup(matvar->internal->fieldnames[field_index]);
    }

    return old_field;
}

/** @brief Sets the structure field to the given variable
 *
 * Sets the specified structure fieldname at the given 0-relative @c index to
 * @c field.
 * @ingroup MAT
 * @param matvar Pointer to the Structure MAT variable
 * @param field_name Name of the structure field
 * @param index linear index of the structure array
 * @param field New field variable
 * @return Pointer to the previous field (NULL if no previous field)
 */
matvar_t *
Mat_VarSetStructFieldByName(matvar_t *matvar, const char *field_name, size_t index, matvar_t *field)
{
    int err, i, nfields, field_index;
    matvar_t *old_field = NULL;
    size_t nelems = 1;

    if ( matvar == NULL || matvar->class_type != MAT_C_STRUCT || matvar->data == NULL )
        return NULL;

    err = Mat_MulDims(matvar, &nelems);
    if ( err )
        return NULL;

    nfields = matvar->internal->num_fields;
    field_index = -1;
    for ( i = 0; i < nfields; i++ ) {
        if ( !strcmp(matvar->internal->fieldnames[i], field_name) ) {
            field_index = i;
            break;
        }
    }

    if ( index < nelems && field_index >= 0 ) {
        matvar_t **fields = (matvar_t **)matvar->data;
        old_field = fields[index * nfields + field_index];
        fields[index * nfields + field_index] = field;
        if ( NULL != field->name ) {
            free(field->name);
        }
        field->name = strdup(matvar->internal->fieldnames[field_index]);
    }

    return old_field;
}
