/******************************************************************************
 *
 * Project:  ASI CEOS Translator
 * Purpose:  Core CEOS functions.
 * Author:   Paul Lahaie, pjlahaie@atlsci.com
 *
 ******************************************************************************
 * Copyright (c) 2000, Atlantis Scientific Inc
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "ceos.h"

#include "cpl_error.h"
#include <assert.h>

/* Function implementations of functions described in ceos.h */

void CeosUpdateHeaderFromBuffer(CeosRecord_t *record);

void InitEmptyCeosRecord(CeosRecord_t *record, int32 sequence,
                         CeosTypeCode_t typecode, int32 length)
{
    if (record)
    {
        if ((record->Buffer = HMalloc(length)) == NULL)
        {
            return;
        }
        /* First we zero fill the buffer */
        memset(record->Buffer, 0, length);

        /* Setup values inside the CeosRecord_t header */
        record->Sequence = sequence;
        record->Flavor = 0;
        record->FileId = 0;
        record->TypeCode = typecode;
        record->Subsequence = 0;
        record->Length = length;

        /* Now we fill in the buffer portion as well */
        NativeToCeos(record->Buffer + SEQUENCE_OFF, &(record->Sequence),
                     sizeof(record->Sequence), sizeof(record->Sequence));
        memcpy(record->Buffer + TYPE_OFF, &(record->TypeCode.Int32Code),
               sizeof(record->TypeCode.Int32Code));
        NativeToCeos(record->Buffer + LENGTH_OFF, &length, sizeof(length),
                     sizeof(length));
    }
}

void InitCeosRecord(CeosRecord_t *record, uchar *buffer)
{
    if (record && buffer)
    {
        InitCeosRecordWithHeader(record, buffer, buffer + CEOS_HEADER_LENGTH);
    }
}

void InitCeosRecordWithHeader(CeosRecord_t *record, uchar *header,
                              uchar *buffer)
{
    if (record && buffer && header)
    {
        if (record->Length != 0)
            record->Length = DetermineCeosRecordBodyLength(header);

        if (record->Length < CEOS_HEADER_LENGTH ||
            (record->Buffer = HMalloc(record->Length)) == NULL)
        {
            record->Length = 0;
            return;
        }

        /* First copy the header then the buffer */
        memcpy(record->Buffer, header, CEOS_HEADER_LENGTH);
        /* Now we copy the rest */
        if (record->Length > CEOS_HEADER_LENGTH)
            memcpy(record->Buffer + CEOS_HEADER_LENGTH, buffer,
                   record->Length - CEOS_HEADER_LENGTH);

        /* Now we fill in the rest of the structure! */
        memcpy(&(record->TypeCode.Int32Code), header + TYPE_OFF,
               sizeof(record->TypeCode.Int32Code));
        CeosToNative(&(record->Sequence), header + SEQUENCE_OFF,
                     sizeof(record->Sequence), sizeof(record->Sequence));
    }
}

int DetermineCeosRecordBodyLength(const uchar *header)
{
    int i;

    if (header)
    {
        CeosToNative(&i, header + LENGTH_OFF, sizeof(i), sizeof(i));
        return i;
    }

    return -1;
}

void DeleteCeosRecord(CeosRecord_t *record)
{
    if (record)
    {
        if (record->Buffer)
        {
            HFree(record->Buffer);
            record->Buffer = NULL;
        }
        HFree(record);
    }
}

void GetCeosRecordStruct(const CeosRecord_t *record, void *struct_ptr)
{
    if (record && struct_ptr && record->Buffer)
    {
        memcpy(record->Buffer, struct_ptr, record->Length);
    }
}

void PutCeosRecordStruct(CeosRecord_t *record, const void *struct_ptr)
{
    int Length;

    if (record && struct_ptr)
    {
        CeosToNative(&Length, struct_ptr, sizeof(Length), sizeof(Length));
        memcpy(record->Buffer, struct_ptr, Length);
        CeosUpdateHeaderFromBuffer(record);
    }
}

void GetCeosField(const CeosRecord_t *record, int32 start_byte,
                  const char *format, void *value)
{
    char *d_ptr;
    char *mod_buf = NULL;

    const int field_size = atoi(format + 1);
    assert(field_size >= 1);

    if (format[0] == 'a' || format[0] == 'A')
    {
        ((char *)value)[0] = 0;
        ((char *)value)[field_size] = 0;
    }

    /* Check for out of bounds */
    if (start_byte + field_size - 1 > record->Length)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Cannot read field. Would require record to be at least %d "
                 "bytes large, whereas it is only %d",
                 start_byte + field_size - 1, record->Length);
        return;
    }

    if ((mod_buf = (char *)HMalloc(field_size + 1)) == NULL)
    {
        return;
    }

    memcpy(mod_buf, record->Buffer + (start_byte - 1), field_size);
    mod_buf[field_size] = 0;

    /* Switch on format type */
    switch (format[0])
    {
        case 'b':
        case 'B':
            /* Binary data type */
            if (field_size > 1)
            {
                CeosToNative(value, mod_buf, field_size, field_size);
            }
            else
            {
                memcpy(value, mod_buf, field_size);
            }
            break;

        case 'i':
        case 'I':
            /* Integer type */
            *((int *)value) = atoi(mod_buf);
            break;

        case 'f':
        case 'F':
        case 'e':
        case 'E':
            /* Double precision float data type */

            /* Change the 'D' exponent separators to 'e' */
            if ((d_ptr = strchr(mod_buf, 'd')) != NULL)
            {
                *d_ptr = 'e';
            }
            if ((d_ptr = strchr(mod_buf, 'D')) != NULL)
            {
                *d_ptr = 'e';
            }

            *((double *)value) = strtod(mod_buf, NULL);
            break;
        case 'a':
        case 'A':
            /* ASCII..  We just easily extract it */
            ((char *)value)[field_size] = '\0';
            memcpy(value, mod_buf, field_size);
            break;

        default:
            /* Unknown format.  Do nothing. */
            break;
    }

    HFree(mod_buf);
}

void SetCeosField(CeosRecord_t *record, int32 start_byte, const char *format,
                  int intValue, double dblValue)
{
    int field_size;
    char *temp_buf = NULL;
    char szPrintfFormat[20];

    field_size = 0;
    sscanf(&format[1], "%d", &field_size);
    if (field_size < 1)
    {
        return;
    }

    /* Check for bounds */
    if (start_byte + field_size - 1 > record->Length)
    {
        return;
    }

    /* Make a local buffer to print into */
    if ((temp_buf = (char *)HMalloc(field_size + 1)) == NULL)
    {
        return;
    }
    switch (format[0])
    {
        case 'b':
        case 'B':
#if 0
        /* Binary data type */
        if(field_size > 1)
        {
            NativeToCeos( value, temp_buf, field_size, field_size );
        } else {
            memcpy(value,temp_buf,field_size);
        }
        break;
#endif
            fprintf(stderr, "SetCeosField with format=%c not implemented",
                    format[0]);
            HFree(temp_buf);
            return;

        case 'i':
        case 'I':
            /* Integer data type */
            snprintf(szPrintfFormat, sizeof(szPrintfFormat), "%%%s%c",
                     format + 1, 'd');
            snprintf(temp_buf, field_size + 1, szPrintfFormat, intValue);
            break;

        case 'f':
        case 'F':
            /* Double precision floating point data type */
            snprintf(szPrintfFormat, sizeof(szPrintfFormat), "%%%s%c",
                     format + 1, 'g');
            snprintf(temp_buf, field_size + 1, szPrintfFormat, dblValue);
            break;

        case 'e':
        case 'E':
            /* Double precision floating point data type (forced exponent) */
            snprintf(szPrintfFormat, sizeof(szPrintfFormat), "%%%s%c",
                     format + 1, 'e');
            snprintf(temp_buf, field_size + 1, szPrintfFormat, dblValue);
            break;

        case 'a':
        case 'A':
#if 0
        strncpy(temp_buf,value,field_size+1);
        temp_buf[field_size] = '0';
        break;
#endif
            fprintf(stderr, "SetCeosField with format=%c not implemented",
                    format[0]);
            HFree(temp_buf);
            return;

        default:
            /* Unknown format */
            HFree(temp_buf);
            return;
    }

    memcpy(record->Buffer + start_byte - 1, temp_buf, field_size);

    HFree(temp_buf);
}

void SetIntCeosField(CeosRecord_t *record, int32 start_byte, int32 length,
                     int32 value)
{
    int integer_value = value;
    char total_len[12]; /* 12 because 2^32 -> 4294967296 + I + null */

    snprintf(total_len, sizeof(total_len), "I%d", length);
    SetCeosField(record, start_byte, total_len, integer_value, 0.0);
}

CeosRecord_t *FindCeosRecord(Link_t *record_list, CeosTypeCode_t typecode,
                             int32 fileid, int32 flavor, int32 subsequence)
{
    Link_t *Link;
    CeosRecord_t *record;

    for (Link = record_list; Link != NULL; Link = Link->next)
    {
        record = (CeosRecord_t *)Link->object;

        if ((record->TypeCode.Int32Code == typecode.Int32Code) &&
            ((fileid == -1) || (record->FileId == fileid)) &&
            ((flavor == -1) || (record->Flavor == flavor)) &&
            ((subsequence == -1) || (record->Subsequence == subsequence)))
            return record;
    }

    return NULL;
}

CPL_INLINE static void CPL_IGNORE_RET_VAL_SIZET(CPL_UNUSED size_t unused)
{
}

void SerializeCeosRecordsToFile(Link_t *record_list, VSILFILE *fp)
{
    Link_t *list;
    CeosRecord_t crec;
    unsigned char *Buffer;

    list = record_list;

    while (list != NULL)
    {
        memcpy(&crec, list->object, sizeof(CeosRecord_t));
        Buffer = crec.Buffer;
        crec.Buffer = NULL;
        CPL_IGNORE_RET_VAL_SIZET(
            VSIFWriteL(&crec, sizeof(CeosRecord_t), 1, fp));
        CPL_IGNORE_RET_VAL_SIZET(VSIFWriteL(Buffer, crec.Length, 1, fp));
    }
}

void SerializeCeosRecordsFromFile(Link_t *record_list, VSILFILE *fp)
{
    CeosRecord_t *crec;
    Link_t *Link;

    while (!VSIFEofL(fp))
    {
        crec = HMalloc(sizeof(CeosRecord_t));
        CPL_IGNORE_RET_VAL_SIZET(VSIFReadL(crec, sizeof(CeosRecord_t), 1, fp));
        crec->Buffer = HMalloc(crec->Length * sizeof(char));
        CPL_IGNORE_RET_VAL_SIZET(
            VSIFReadL(crec->Buffer, sizeof(char), crec->Length, fp));
        Link = ceos2CreateLink(crec);
        AddLink(record_list, Link);
    }
}

void CeosUpdateHeaderFromBuffer(CeosRecord_t *record)
{
    if (record && record->Buffer)
    {
        CeosToNative(&(record->Length), record->Buffer + LENGTH_OFF,
                     sizeof(record->Length), sizeof(record->Length));
        memcpy(&(record->TypeCode.Int32Code), record->Buffer + TYPE_OFF,
               sizeof(record->TypeCode.Int32Code));
        CeosToNative(&(record->Sequence), record->Buffer + SEQUENCE_OFF,
                     sizeof(record->Sequence), sizeof(record->Sequence));
    }
    if (record)
        record->Subsequence = 0;
}

#ifdef CPL_LSB

static void swapbyte(void *dst, void *src, size_t toswap)
{
    size_t i, e;
    unsigned char *in = (unsigned char *)src;
    unsigned char *out = (unsigned char *)dst;

    for (i = 0, e = toswap; i < toswap; i++, e--)
    {
        out[i] = in[e - 1];
    }
}

void NativeToCeos(void *dst, const void *src, const size_t len,
                  const size_t swapunit)
{
    size_t i;
    size_t l_remainder;
    size_t units;

    l_remainder = len % swapunit;

    units = len - l_remainder;

    for (i = 0; i < units; i += swapunit)
    {
        swapbyte((unsigned char *)dst + i, (unsigned char *)src + i, swapunit);
    }

    if (l_remainder)
    {
        memcpy((unsigned char *)dst + i, (unsigned char *)src + i, l_remainder);
    }
}

#endif
