/******************************************************************************
 *
 * Project:  NTF Translator
 * Purpose:  NTFRecord class implementation.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 1999, Frank Warmerdam
 * Copyright (c) 2013, Even Rouault <even dot rouault at spatialys.com>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "ntf.h"
#include "cpl_conv.h"

static int nFieldBufSize = 0;
static char *pszFieldBuf = nullptr;

constexpr int MAX_RECORD_LEN = 160;

/************************************************************************/
/*                             NTFRecord()                              */
/*                                                                      */
/*      The constructor is where the record is read.  This includes     */
/*      transparent merging of continuation lines.                      */
/************************************************************************/

NTFRecord::NTFRecord(VSILFILE *fp) : nType(99), nLength(0), pszData(nullptr)
{
    if (fp == nullptr)
        return;

    /* ==================================================================== */
    /*      Read lines until we get to one without a continuation mark.     */
    /* ==================================================================== */
    char szLine[MAX_RECORD_LEN + 3] = {};
    int nNewLength = 0;

    do
    {
        nNewLength = ReadPhysicalLine(fp, szLine);
        if (nNewLength == -1 || nNewLength == -2)
            break;

        while (nNewLength > 0 && szLine[nNewLength - 1] == ' ')
            szLine[--nNewLength] = '\0';

        if (nNewLength < 2 || szLine[nNewLength - 1] != '%')
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Corrupt NTF record, missing end '%%'.");
            CPLFree(pszData);
            pszData = nullptr;
            break;
        }

        if (pszData == nullptr)
        {
            nLength = nNewLength - 2;
            // coverity[overflow_sink]
            pszData = static_cast<char *>(VSI_MALLOC_VERBOSE(nLength + 1));
            if (pszData == nullptr)
            {
                return;
            }
            memcpy(pszData, szLine, nLength);
            pszData[nLength] = '\0';
        }
        else
        {
            if (!STARTS_WITH_CI(szLine, "00") || nNewLength < 4)
            {
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid line");
                VSIFree(pszData);
                pszData = nullptr;
                return;
            }

            char *pszNewData = static_cast<char *>(
                VSI_REALLOC_VERBOSE(pszData, nLength + (nNewLength - 4) + 1));
            if (pszNewData == nullptr)
            {
                VSIFree(pszData);
                pszData = nullptr;
                return;
            }

            pszData = pszNewData;
            memcpy(pszData + nLength, szLine + 2, nNewLength - 4);
            nLength += nNewLength - 4;
            pszData[nLength] = '\0';
        }
    } while (szLine[nNewLength - 2] == '1');

    /* -------------------------------------------------------------------- */
    /*      Figure out the record type.                                     */
    /* -------------------------------------------------------------------- */
    if (pszData != nullptr)
    {
        char szType[3];

        strncpy(szType, pszData, 2);
        szType[2] = '\0';

        nType = atoi(szType);
    }
}

/************************************************************************/
/*                             ~NTFRecord()                             */
/************************************************************************/

NTFRecord::~NTFRecord()

{
    CPLFree(pszData);

    if (pszFieldBuf != nullptr)
    {
        CPLFree(pszFieldBuf);
        pszFieldBuf = nullptr;
        nFieldBufSize = 0;
    }
}

/************************************************************************/
/*                          ReadPhysicalLine()                          */
/************************************************************************/

int NTFRecord::ReadPhysicalLine(VSILFILE *fp, char *pszLine)

{
    /* -------------------------------------------------------------------- */
    /*      Read enough data that we are sure we have a whole record.       */
    /* -------------------------------------------------------------------- */
    int nRecordStart = static_cast<int>(VSIFTellL(fp));
    const int nBytesRead =
        static_cast<int>(VSIFReadL(pszLine, 1, MAX_RECORD_LEN + 2, fp));

    if (nBytesRead == 0)
    {
        if (VSIFEofL(fp))
            return -1;
        else /* if (VSIFErrorL(fp)) */
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Low level read error occurred while reading NTF file.");
            return -2;
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Search for CR or LF.                                            */
    /* -------------------------------------------------------------------- */
    int i = 0;  // Used after for.
    for (; i < nBytesRead; i++)
    {
        if (pszLine[i] == 10 || pszLine[i] == 13)
            break;
    }

    /* -------------------------------------------------------------------- */
    /*      If we don't find EOL within 80 characters something has gone    */
    /*      badly wrong!                                                    */
    /* -------------------------------------------------------------------- */
    if (i == MAX_RECORD_LEN + 2)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "%d byte record too long for NTF format.  "
                 "No line may be longer than 80 characters though up "
                 "to %d tolerated.",
                 nBytesRead, MAX_RECORD_LEN);
        return -2;
    }

    /* -------------------------------------------------------------------- */
    /*      Trim CR/LF.                                                     */
    /* -------------------------------------------------------------------- */
    const int l_nLength = i;
    const int nRecordEnd =
        nRecordStart + i +
        (pszLine[i + 1] == 10 || pszLine[i + 1] == 13 ? 2 : 1);

    pszLine[l_nLength] = '\0';

    /* -------------------------------------------------------------------- */
    /*      Restore read pointer to beginning of next record.               */
    /* -------------------------------------------------------------------- */
    if (VSIFSeekL(fp, nRecordEnd, SEEK_SET) != 0)
        return -1;

    return l_nLength;
}

/************************************************************************/
/*                              GetField()                              */
/*                                                                      */
/*      Note that the start position is 1 based, to match the           */
/*      notation in the NTF document.  The returned pointer is to an    */
/*      internal buffer, but is zero terminated.                        */
/************************************************************************/

const char *NTFRecord::GetField(int nStart, int nEnd)

{
    const int nSize = nEnd - nStart + 1;

    if (pszData == nullptr)
        return "";

    /* -------------------------------------------------------------------- */
    /*      Reallocate working buffer larger if needed.                     */
    /* -------------------------------------------------------------------- */
    if (nFieldBufSize < nSize + 1)
    {
        CPLFree(pszFieldBuf);
        nFieldBufSize = nSize + 1;
        pszFieldBuf = static_cast<char *>(CPLMalloc(nFieldBufSize));
    }

    /* -------------------------------------------------------------------- */
    /*      Copy out desired data.                                          */
    /* -------------------------------------------------------------------- */
    if (nStart + nSize > nLength + 1)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Attempt to read %d to %d, beyond the end of %d byte long\n"
                 "type `%2.2s' record.\n",
                 nStart, nEnd, nLength, pszData);
        memset(pszFieldBuf, ' ', nSize);
        pszFieldBuf[nSize] = '\0';
    }
    else
    {
        strncpy(pszFieldBuf, pszData + nStart - 1, nSize);
        pszFieldBuf[nSize] = '\0';
    }

    return pszFieldBuf;
}
