/**********************************************************************
 *
 * Name:     avc_e00write.c
 * Project:  Arc/Info vector coverage (AVC)  E00->BIN conversion library
 * Language: ANSI C
 * Purpose:  Functions to create a binary coverage from a stream of
 *           ASCII E00 lines.
 * Author:   Daniel Morissette, dmorissette@dmsolutions.ca
 *
 **********************************************************************
 * Copyright (c) 1999-2001, Daniel Morissette
 *
 * SPDX-License-Identifier: MIT
 **********************************************************************
 *
 * $Log: avc_e00write.c,v $
 * Revision 1.21  2008/07/23 20:51:38  dmorissette
 * Fixed GCC 4.1.x compile warnings related to use of char vs unsigned char
 * (GDAL/OGR ticket http://trac.osgeo.org/gdal/ticket/2495)
 *
 * Revision 1.20  2006/06/27 18:38:43  dmorissette
 * Cleaned up E00 reading (bug 1497, patch from James F.)
 *
 * Revision 1.19  2006/06/14 16:31:28  daniel
 * Added support for AVCCoverPC2 type (bug 1491)
 *
 * Revision 1.18  2006/03/02 22:46:26  daniel
 * Accept empty subclass names for TX6/TX7 sections (bug 1261)
 *
 * Revision 1.17  2005/06/03 03:49:59  daniel
 * Update email address, website url, and copyright dates
 *
 * Revision 1.16  2002/08/27 15:46:15  daniel
 * Applied fix made in GDAL/OGR by 'aubin' (moved include ctype.h after avc.h)
 *
 * Revision 1.15  2002/04/16 21:19:10  daniel
 * Use VSIRmdir()
 *
 * Revision 1.14  2002/03/18 19:00:44  daniel
 * Use VSIMkdir() and not VSIMkDir()
 *
 * Revision 1.13  2002/02/18 21:16:33  warmerda
 * modified to use VSIMkDir
 *
 * Revision 1.12  2001/05/23 15:23:17  daniel
 * Remove trailing '/' in info directory path when creating the info dir.
 *
 * Revision 1.11  2000/09/26 20:21:04  daniel
 * Added AVCCoverPC write
 *
 * Revision 1.10  2000/09/22 19:45:21  daniel
 * Switch to MIT-style license
 *
 * Revision 1.9  2000/05/29 22:47:39  daniel
 * Made validation on new coverage name more flexible.
 *
 * Revision 1.8  2000/05/29 15:31:31  daniel
 * Added Japanese DBCS support
 *
 * Revision 1.7  2000/02/14 17:19:53  daniel
 * Accept '-' character in new coverage name
 *
 * Revision 1.6  2000/01/10 02:57:44  daniel
 * Little changes to accommodate read support for "weird" coverages
 *
 * Revision 1.5  1999/12/24 07:18:34  daniel
 * Added PC Arc/Info coverages support
 *
 * Revision 1.4  1999/08/26 17:36:36  daniel
 * Avoid overwriting arc.dir on Windows... happened only when several
 * coverages are created by the same process on Windows.
 *
 * Revision 1.3  1999/08/23 18:23:35  daniel
 * Added AVCE00DeleteCoverage()
 *
 * Revision 1.2  1999/05/17 16:23:36  daniel
 * Added AVC_DEFAULT_PREC + more cover name validation in AVCE00WriteOpen().
 *
 * Revision 1.1  1999/05/11 02:34:46  daniel
 * Initial revision
 *
 **********************************************************************/

#include "cpl_port.h"
#include "cpl_vsi.h"
#include "avc.h"
#include <ctype.h> /* tolower() */

static GBool _IsStringAlnum(const char *pszFname);

/**********************************************************************
 *                          AVCE00WriteOpen()
 *
 * Open (create) an Arc/Info coverage, ready to be receive a stream
 * of ASCII E00 lines and convert that to the binary coverage format.
 *
 * For now, writing to or overwriting existing coverages is not supported
 * (and may quite well never be!)... you can only create new coverages.
 *
 * Important Note: The E00 source lines are assumed to be valid... the
 * library performs no validation on the consistency of what it is
 * given as input (i.e. topology, polygons consistency, etc.).
 * So the coverage that will be created will be only as good as the
 * E00 input that is used to generate it.
 *
 * pszCoverPath MUST be the name of the coverage directory, including
 * the path to it.
 * (contrary to AVCE00ReadOpen(), you cannot pass the name of one of
 *  the files in the coverage directory).
 * The name of the coverage MUST be included in pszCoverPath... this
 * means that passing "." is invalid.
 *
 * eNewCoverType is the type of coverage to create.
 *               Either AVCCoverV7 (Arc/Info V7 (Unix) coverage)
 *               or     AVCCoverPC (PC Arc/Info coverage)
 *
 * nPrecision should always be AVC_DEFAULT_PREC to automagically detect the
 *            source coverage's precision and use that same precision
 *            for the new coverage.
 *
 *            This parameter has been included to allow adding the
 *            possibility to eventually create coverages with a precision
 *            different from the source E00.
 *            Given the way the lib is built, it could be possible to
 *            also pass  AVC_SINGLE_PREC or AVC_DOUBLE_PREC to explicitly
 *            request the creation of a coverage with that precision,
 *            but the library does not (not yet!) properly convert the
 *            TABLE attributes' precision, and the resulting coverage may
 *            be invalid in some cases.
 *            This improvement is on the ToDo list!
 *
 * Returns a new AVCE00WritePtr handle or nullptr if the coverage could
 * not be created or if a coverage with that name already exists.
 *
 * The handle will eventually have to be released with AVCE00ReadClose().
 **********************************************************************/
AVCE00WritePtr AVCE00WriteOpen(const char *pszCoverPath,
                               AVCCoverType eNewCoverType, int nPrecision)
{
    AVCE00WritePtr psInfo;
    int i, nLen;
    VSIStatBufL sStatBuf;

    CPLErrorReset();

    /*-----------------------------------------------------------------
     * Create pszCoverPath directory.
     *----------------------------------------------------------------*/
    if (pszCoverPath == nullptr || strlen(pszCoverPath) == 0)
    {
        CPLError(CE_Failure, CPLE_AssertionFailed,
                 "Invalid (empty) coverage directory name.");
        return nullptr;
    }
    else if (VSIStatL(pszCoverPath, &sStatBuf) == 0 &&
             VSI_ISDIR(sStatBuf.st_mode))
    {
        /*-------------------------------------------------------------
         * Directory already exists... make sure it is empty
         * otherwise we can't use it as a coverage directory.
         *------------------------------------------------------------*/
        char **papszFiles;
        papszFiles = VSIReadDir(pszCoverPath);
        for (i = 0; papszFiles && papszFiles[i]; i++)
        {
            if (!EQUAL(".", papszFiles[i]) && !EQUAL("..", papszFiles[i]))
            {
                CPLError(CE_Failure, CPLE_OpenFailed,
                         "Cannot create coverage %s: directory already exists "
                         "and is not empty.",
                         pszCoverPath);
                CSLDestroy(papszFiles);
                papszFiles = nullptr;
                return nullptr;
            }
        }

        CSLDestroy(papszFiles);
        papszFiles = nullptr;
    }
    else
    {
        /*-------------------------------------------------------------
         * Create new pszCoverPath directory.
         * This will fail if a file with the same name already exists.
         *------------------------------------------------------------*/
        if (VSIMkdir(pszCoverPath, 0777) != 0)
        {
            CPLError(CE_Failure, CPLE_OpenFailed,
                     "Unable to create coverage directory: %s.", pszCoverPath);
            return nullptr;
        }
    }

    /*-----------------------------------------------------------------
     * Alloc the AVCE00WritePtr handle
     *----------------------------------------------------------------*/
    psInfo = (AVCE00WritePtr)CPLCalloc(1, sizeof(struct AVCE00WriteInfo_t));

    /*-----------------------------------------------------------------
     * Validate and store coverage type
     *----------------------------------------------------------------*/
    if (eNewCoverType == AVCCoverV7 || eNewCoverType == AVCCoverPC)
        psInfo->eCoverType = eNewCoverType;
    else
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Requested coverage type cannot be created.  Please use "
                 "the AVCCoverV7 or AVCCoverPC coverage type.");
        CPLFree(psInfo);
        return nullptr;
    }

    /*-----------------------------------------------------------------
     * Requested precision for the new coverage... for now only
     * AVC_DEFAULT_PREC is supported.  When the first section is
     * read, then this section's precision will be used for the whole
     * coverage.  (This is done inside AVCE00WriteNextLine())
     *----------------------------------------------------------------*/
    if (psInfo->eCoverType == AVCCoverPC)
        psInfo->nPrecision = AVC_SINGLE_PREC; /* PC Cover always single prec.*/
    else if (nPrecision == AVC_DEFAULT_PREC)
        psInfo->nPrecision = nPrecision;
    else
    {
        CPLError(CE_Failure, CPLE_IllegalArg,
                 "Coverages can only be created using AVC_DEFAULT_PREC. "
                 "Please see the documentation for AVCE00WriteOpen().");
        CPLFree(psInfo);
        return nullptr;
    }

    /*-----------------------------------------------------------------
     * Make sure coverage directory name is terminated with a '/' (or '\\')
     *----------------------------------------------------------------*/
    nLen = (int)strlen(pszCoverPath);

    if (pszCoverPath[nLen - 1] == '/' || pszCoverPath[nLen - 1] == '\\')
        psInfo->pszCoverPath = CPLStrdup(pszCoverPath);
    else
    {
#ifdef _WIN32
        psInfo->pszCoverPath = CPLStrdup(CPLSPrintf("%s\\", pszCoverPath));
#else
        psInfo->pszCoverPath = CPLStrdup(CPLSPrintf("%s/", pszCoverPath));
#endif
    }

    /*-----------------------------------------------------------------
     * Extract the coverage name from the coverage path.  Note that
     * for this the coverage path must be in the form:
     * "dir1/dir2/dir3/covername/" ... if it is not the case, then
     * we would have to use getcwd() to find the current directory name...
     * but for now we'll just produce an error if this happens.
     *----------------------------------------------------------------*/
    nLen = 0;
    for (i = (int)strlen(psInfo->pszCoverPath) - 1;
         i > 0 && psInfo->pszCoverPath[i - 1] != '/' &&
         psInfo->pszCoverPath[i - 1] != '\\' &&
         psInfo->pszCoverPath[i - 1] != ':';
         i--)
    {
        nLen++;
    }

    if (nLen > 0)
    {
        psInfo->pszCoverName = CPLStrdup(psInfo->pszCoverPath + i);
        psInfo->pszCoverName[nLen] = '\0';
    }
    else
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                 "Invalid coverage path (%s): "
                 "coverage name must be included in path.",
                 pszCoverPath);

        CPLFree(psInfo->pszCoverPath);
        CPLFree(psInfo);
        return nullptr;
    }

    if (strlen(psInfo->pszCoverName) > 13 ||
        !_IsStringAlnum(psInfo->pszCoverName))
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                 "Invalid coverage name (%s): "
                 "coverage name must be 13 chars or less and contain only "
                 "alphanumerical characters, '-' or '_'.",
                 psInfo->pszCoverName);

        CPLFree(psInfo->pszCoverPath);
        CPLFree(psInfo->pszCoverName);
        CPLFree(psInfo);
        return nullptr;
    }

    if (psInfo->eCoverType == AVCCoverPC || psInfo->eCoverType == AVCCoverPC2)
    {
        /*-------------------------------------------------------------
         * No 'info' directory is required for PC coverages
         *------------------------------------------------------------*/
        psInfo->pszInfoPath = nullptr;
    }
    else
    {
        /*-------------------------------------------------------------
         * Lazy way to build the INFO path: simply add "../info/"...
         * this could probably be improved!
         *------------------------------------------------------------*/
        size_t nInfoPathLen = strlen(psInfo->pszCoverPath) + 9;
        psInfo->pszInfoPath = (char *)CPLMalloc(nInfoPathLen);
#ifdef _WIN32
#define AVC_INFOPATH "..\\info\\"
#else
#define AVC_INFOPATH "../info/"
#endif
        snprintf(psInfo->pszInfoPath, nInfoPathLen, "%s%s",
                 psInfo->pszCoverPath, AVC_INFOPATH);

        /*-------------------------------------------------------------
         * Check if the info directory exists and contains the "arc.dir"
         * if the info dir does not exist, then make sure we can create
         * the arc.dir file (i.e. try to create an empty one)
         *
         * Note: On Windows, this VSIStat() call seems to sometimes fail even
         *       when the directory exists (buffering issue?), and the
         *       following if() block is sometimes executed even if it
         *       should not, but this should not cause problems since the
         *       arc.dir is opened with "a+b" access.
         *------------------------------------------------------------*/
        if (VSIStatL(psInfo->pszInfoPath, &sStatBuf) == -1)
        {
            VSILFILE *fp;
            char *pszArcDir;
            char *pszInfoDir;

            pszArcDir =
                CPLStrdup(CPLSPrintf("%s%s", psInfo->pszInfoPath, "arc.dir"));

            /* Remove the trailing "/" from pszInfoPath.  Most OSes are
             * forgiving, and allow mkdir to include the trailing character,
             * but some UNIXes are not. [GEH 2001/05/17]
             */
            pszInfoDir = CPLStrdup(psInfo->pszInfoPath);
            pszInfoDir[strlen(pszInfoDir) - 1] = '\0';

            VSIMkdir(pszInfoDir, 0777);
            fp = VSIFOpenL(pszArcDir, "a+b");

            CPLFree(pszArcDir);
            CPLFree(pszInfoDir);
            if (fp)
            {
                VSIFCloseL(fp);
            }
            else
            {
                CPLError(CE_Failure, CPLE_OpenFailed,
                         "Unable to create (or write to) 'info' directory %s",
                         psInfo->pszInfoPath);
                CPLFree(psInfo->pszCoverPath);
                CPLFree(psInfo->pszInfoPath);
                CPLFree(psInfo);
                return nullptr;
            }
        }
    }

    /*-----------------------------------------------------------------
     * Init the E00 parser.
     *----------------------------------------------------------------*/
    psInfo->hParseInfo = AVCE00ParseInfoAlloc();
    psInfo->eCurFileType = AVCFileUnknown;

    /*-----------------------------------------------------------------
     * Init multibyte encoding info
     *----------------------------------------------------------------*/
    psInfo->psDBCSInfo = AVCAllocDBCSInfo();

    /*-----------------------------------------------------------------
     * If an error happened during the open call, cleanup and return nullptr.
     *----------------------------------------------------------------*/
    if (CPLGetLastErrorNo() != 0)
    {
        AVCE00WriteClose(psInfo);
        psInfo = nullptr;
    }

    return psInfo;
}

/**********************************************************************
 *                          AVCE00WriteClose()
 *
 * Close a coverage and release all memory used by the AVCE00WritePtr
 * handle.
 **********************************************************************/
void AVCE00WriteClose(AVCE00WritePtr psInfo)
{
    CPLErrorReset();

    if (psInfo == nullptr)
        return;

    CPLFree(psInfo->pszCoverPath);
    CPLFree(psInfo->pszCoverName);
    CPLFree(psInfo->pszInfoPath);

    if (psInfo->hFile)
        AVCBinWriteClose(psInfo->hFile);

    if (psInfo->hParseInfo)
        AVCE00ParseInfoFree(psInfo->hParseInfo);

    AVCFreeDBCSInfo(psInfo->psDBCSInfo);

    CPLFree(psInfo);
}

/**********************************************************************
 *                          _IsStringAlnum()
 *
 * Scan a string, and return TRUE if it contains only valid characters,
 * Return FALSE otherwise.
 *
 * We used to accept only isalnum() chars, but since extended chars with
 * accents seem to be accepted, we will only check for chars that
 * could confuse the lib.
 **********************************************************************/
static GBool _IsStringAlnum(const char *pszFname)
{
    GBool bOK = TRUE;

    while (bOK && *pszFname != '\0')
    {
        if (strchr(" \t.,/\\", (unsigned char)*pszFname) != nullptr)
            bOK = FALSE;
        pszFname++;
    }

    return bOK;
}

/**********************************************************************
 *                          _AVCE00WriteRenameTable()
 *
 * Rename the table and the system fields in a tabledef that will
 * be written to a new coverage.
 **********************************************************************/
static void _AVCE00WriteRenameTable(AVCTableDef *psTableDef,
                                    const char *pszNewCoverName)
{
    char szOldName[40], szOldExt[40], szNewName[40], *pszTmp;
    char szSysId[40], szUserId[40];
    int i;

    if (psTableDef == nullptr)
    {
        CPLAssert(false);
        return;
    }

    snprintf(szNewName, sizeof(szNewName), "%s", pszNewCoverName);
    for (i = 0; szNewName[i] != '\0'; i++)
        szNewName[i] =
            (char)CPLToupper(static_cast<unsigned char>(szNewName[i]));

    /*-----------------------------------------------------------------
     * Extract components from the current table name.
     *----------------------------------------------------------------*/
    strcpy(szOldName, psTableDef->szTableName);

    if (!EQUAL(psTableDef->szExternal, "XX") ||
        (pszTmp = strchr(szOldName, '.')) == nullptr)
        return; /* We don't deal with that table */

    *pszTmp = '\0';
    pszTmp++;

    snprintf(szOldExt, sizeof(szOldExt), "%s", pszTmp);
    if ((pszTmp = strchr(szOldExt, ' ')) != nullptr)
        *pszTmp = '\0';

    if (strlen(szOldExt) < 3)
        return; /* We don't deal with that table */

    /*-----------------------------------------------------------------
     * Look for system attributes with same name as table
     * If the table name extension is followed by a subclass name
     * (e.g. "TEST.PATCOUNTY") then this subclass is used to build
     * the system attributes (COUNTY# and COUNTY-ID) and thus we do
     * not need to rename them
     * Otherwise (e.g. COUNTY.PAT) the coverage name is used and then
     * we need to rename these attribs for the new coverage name.
     *----------------------------------------------------------------*/
    if (strlen(szOldExt) == 3)
    {
        snprintf(szSysId, sizeof(szSysId), "%s#", szOldName);
        snprintf(szUserId, sizeof(szUserId), "%s-ID", szOldName);

        for (i = 0; i < psTableDef->numFields; i++)
        {
            /* Remove trailing spaces */
            if ((pszTmp = strchr(psTableDef->pasFieldDef[i].szName, ' ')) !=
                nullptr)
                *pszTmp = '\0';

            if (EQUAL(psTableDef->pasFieldDef[i].szName, szSysId))
            {
                snprintf(psTableDef->pasFieldDef[i].szName,
                         sizeof(psTableDef->pasFieldDef[i].szName), "%s#",
                         szNewName);
            }
            else if (EQUAL(psTableDef->pasFieldDef[i].szName, szUserId))
            {
                snprintf(psTableDef->pasFieldDef[i].szName,
                         sizeof(psTableDef->pasFieldDef[i].szName), "%s-ID",
                         szNewName);
            }
        }
    }

    /*-----------------------------------------------------------------
     * Build new table name
     *----------------------------------------------------------------*/
    snprintf(psTableDef->szTableName, sizeof(psTableDef->szTableName), "%s.%s",
             szNewName, szOldExt);
}

/**********************************************************************
 *                          _AVCE00WriteCreateCoverFile()
 *
 * Create a coverage file for the specified file type.
 *
 * The main part of the work is to find the right filename to use based on
 * the file type, the coverage precision, etc... the rest of job is
 * done by AVCBinWriteCreate().
 *
 * Returns 0 on success, or -1 if an error happened.
 *
 * AVCWriteCloseCoverFile() will eventually have to be called to release the
 * resources used by the AVCBinFile structure.
 **********************************************************************/
static int _AVCE00WriteCreateCoverFile(AVCE00WritePtr psInfo, AVCFileType eType,
                                       const char *pszLine,
                                       AVCTableDef *psTableDef)
{
    char *pszPath, szFname[50] = "";
    int i, nStatus = 0;

    /* By now, new coverage precision should have been established */
    CPLAssert(psInfo->nPrecision != AVC_DEFAULT_PREC);

    /*-----------------------------------------------------------------
     * Establish filename based on file type, precision, and possibly the
     * contents of the header line.
     *----------------------------------------------------------------*/
    pszPath = psInfo->pszCoverPath;
    switch (eType)
    {
        case AVCFileARC:
            strcpy(szFname, "arc");
            break;
        case AVCFilePAL:
            strcpy(szFname, "pal");
            break;
        case AVCFileCNT:
            strcpy(szFname, "cnt");
            break;
        case AVCFileLAB:
            strcpy(szFname, "lab");
            break;
        case AVCFileTOL:
            if (psInfo->nPrecision == AVC_SINGLE_PREC)
                strcpy(szFname, "tol");
            else
                strcpy(szFname, "par");
            break;
        case AVCFilePRJ:
            strcpy(szFname, "prj");
            break;
        case AVCFileTXT:
            strcpy(szFname, "txt");
            break;
        case AVCFileTX6:
            /* For TX6/TX7: the filename is subclass_name.txt
             */

            /* See bug 1261: It seems that empty subclass names are valid
             * for TX7. In this case we'll default the filename to txt.txt
             */
            if (pszLine[0] == '\0')
            {
                strcpy(szFname, "txt.txt");
            }
            else if (strlen(pszLine) > 30 || strchr(pszLine, ' ') != nullptr)
                CPLError(CE_Failure, CPLE_IllegalArg,
                         "Invalid TX6/TX7 subclass name \"%s\"", pszLine);
            else
                snprintf(szFname, sizeof(szFname), "%s.txt", pszLine);
            break;
        case AVCFileRPL:
            /* For RPL and RXP: the filename is region_name.pal or
             * region_name.rxp
             */
            if (strlen(pszLine) > 30 || strchr(pszLine, ' ') != nullptr)
                CPLError(CE_Failure, CPLE_IllegalArg,
                         "Invalid RPL region name \"%s\"", pszLine);
            else
                snprintf(szFname, sizeof(szFname), "%s.pal", pszLine);
            break;
        case AVCFileRXP:
            if (strlen(pszLine) > 30 || strchr(pszLine, ' ') != nullptr)
                CPLError(CE_Failure, CPLE_IllegalArg, "Invalid RXP name \"%s\"",
                         pszLine);
            else
                snprintf(szFname, sizeof(szFname), "%s.rxp", pszLine);
            break;
        case AVCFileTABLE:
            /*-------------------------------------------------------------
             * For tables, Filename will be based on info in the psTableDef
             * but we need to rename the table and the system attributes
             * based on the new coverage name.
             *------------------------------------------------------------*/
            if (psInfo->eCoverType != AVCCoverPC &&
                psInfo->eCoverType != AVCCoverPC2)
                pszPath = psInfo->pszInfoPath;
            _AVCE00WriteRenameTable(psTableDef, psInfo->pszCoverName);
            break;
        default:
            CPLError(CE_Failure, CPLE_IllegalArg,
                     "_AVCE00WriteCreateCoverFile(): Unsupported file type!");
            nStatus = -1;
            break;
    }

    /*-----------------------------------------------------------------
     * V7 coverage filenames default to have a .adf extension
     * but PC coverage filenames (except .dbf tables) have no extensions.
     *----------------------------------------------------------------*/
    if (psInfo->eCoverType == AVCCoverV7 && strchr(szFname, '.') == nullptr)
        strcat(szFname, ".adf");

    /*-----------------------------------------------------------------
     * Make sure filename is all lowercase and attempt to create the file
     *----------------------------------------------------------------*/
    for (i = 0; szFname[i] != '\0'; i++)
        szFname[i] = (char)CPLTolower(static_cast<unsigned char>(szFname[i]));

    if (nStatus == 0)
    {
        psInfo->eCurFileType = eType;

        if (eType == AVCFileTABLE)
            psInfo->hFile = AVCBinWriteCreateTable(
                pszPath, psInfo->pszCoverName, psTableDef, psInfo->eCoverType,
                psInfo->nPrecision, psInfo->psDBCSInfo);
        else

            psInfo->hFile =
                AVCBinWriteCreate(pszPath, szFname, psInfo->eCoverType, eType,
                                  psInfo->nPrecision, psInfo->psDBCSInfo);

        if (psInfo->hFile == nullptr)
        {
            nStatus = -1;
            psInfo->eCurFileType = AVCFileUnknown;
        }
    }

    return nStatus;
}

/**********************************************************************
 *                          _AVCE00WriteCloseCoverFile()
 *
 * Close current coverage file and reset the contents of psInfo.
 *
 * File should have been previously opened by _AVCE00WriteCreateCoverFile().
 *
 **********************************************************************/
static void _AVCE00WriteCloseCoverFile(AVCE00WritePtr psInfo)
{
    /*-----------------------------------------------------------------
     * PRJ sections behave differently... since there is only one "object"
     * per section, they accumulate lines while we read them, and we
     * write everything at once when we reach the end-of-section (EOP) line.
     *----------------------------------------------------------------*/
    if (psInfo->eCurFileType == AVCFilePRJ)
    {
        AVCBinWriteObject(psInfo->hFile, psInfo->hParseInfo->aosPrj.List());
    }

    AVCBinWriteClose(psInfo->hFile);
    psInfo->hFile = nullptr;
    psInfo->eCurFileType = AVCFileUnknown;
}

/**********************************************************************
 *                          AVCE00WriteNextLine()
 *
 * Take the next line of E00 input for this coverage, parse it and
 * write the result to the coverage.
 *
 * Important Note: The E00 source lines are assumed to be valid... the
 * library performs no validation on the consistency of what it is
 * given as input (i.e. topology, polygons consistency, etc.).
 * So the coverage that will be created will be only as good as the
 * E00 input that is used to generate it.
 *
 * Returns 0 on success or -1 on error.
 **********************************************************************/
int AVCE00WriteNextLine(AVCE00WritePtr psInfo, const char *pszLine)
{
    /*-----------------------------------------------------------------
     * TODO: Update this call to use _AVCE00ReadNextLineE00(), if
     * possible.
     *----------------------------------------------------------------*/

    int nStatus = 0;

    CPLErrorReset();

    /*-----------------------------------------------------------------
     * If we're at the top level inside a supersection... check if this
     * supersection ends here.
     *----------------------------------------------------------------*/
    if (AVCE00ParseSuperSectionEnd(psInfo->hParseInfo, pszLine) == TRUE)
    {
        /* Nothing to do... it is all been done by the call to
         * AVCE00ParseSuperSectionEnd()
         */
    }
    else if (psInfo->eCurFileType == AVCFileUnknown)
    {
        /*-------------------------------------------------------------
         * We're at the top level or inside a supersection... waiting
         * to encounter a valid section or supersection header
         * (i.e. "ARC  2", etc...)
         *------------------------------------------------------------*/

        /*-------------------------------------------------------------
         * First check for a supersection header (TX6, RXP, IFO, ...)
         *------------------------------------------------------------*/
        if (AVCE00ParseSuperSectionHeader(psInfo->hParseInfo, pszLine) ==
            AVCFileUnknown)
        {
            /*---------------------------------------------------------
             * This was not a supersection header... check if it is a simple
             * section header
             *--------------------------------------------------------*/
            psInfo->eCurFileType =
                AVCE00ParseSectionHeader(psInfo->hParseInfo, pszLine);
        }

        /*-------------------------------------------------------------
         * If the coverage was created using AVC_DEFAULT_PREC and we are
         * processing the first section header, then use this section's
         * precision for the new coverage.
         * (Note: this code segment will be executed only once per
         *        coverage and only if AVC_DEFAULT_PREC was selected)
         *------------------------------------------------------------*/
        if (psInfo->nPrecision == AVC_DEFAULT_PREC &&
            psInfo->eCurFileType != AVCFileUnknown)
        {
            psInfo->nPrecision = psInfo->hParseInfo->nPrecision;
        }

        if (psInfo->eCurFileType == AVCFileTABLE)
        {
            /*---------------------------------------------------------
             * We can't create the file for a TABLE until the
             * whole header has been read... send the first header
             * line to the parser and wait until the whole header has
             * been read.
             *--------------------------------------------------------*/
            AVCE00ParseNextLine(psInfo->hParseInfo, pszLine);
        }
        else if (psInfo->eCurFileType != AVCFileUnknown)
        {
            /*---------------------------------------------------------
             * OK, we've found a valid section header... create the
             * corresponding file in the coverage.
             * Note: supersection headers don't trigger the creation
             *       of any output file... they just alter the psInfo state.
             *--------------------------------------------------------*/

            nStatus = _AVCE00WriteCreateCoverFile(
                psInfo, psInfo->eCurFileType,
                psInfo->hParseInfo->pszSectionHdrLine, nullptr);
        }
    }
    else if (psInfo->eCurFileType == AVCFileTABLE &&
             !psInfo->hParseInfo->bTableHdrComplete)
    {
        /*-------------------------------------------------------------
         * We're reading a TABLE header... continue reading lines
         * from the header, and create the output file only once
         * the header will have been completely read.
         *
         * Note: When parsing a TABLE, the first object returned will
         * be the AVCTableDef, then data records will follow.
         *------------------------------------------------------------*/
        AVCTableDef *psTableDef;
        psTableDef =
            (AVCTableDef *)AVCE00ParseNextLine(psInfo->hParseInfo, pszLine);
        if (psTableDef)
        {
            nStatus = _AVCE00WriteCreateCoverFile(
                psInfo, psInfo->eCurFileType,
                psInfo->hParseInfo->pszSectionHdrLine, psTableDef);
        }
    }
    else
    {
        /*-------------------------------------------------------------
         * We're are in the middle of a section... first check if we
         * have reached the end.
         *
         * note: The first call to AVCE00ParseSectionEnd() with FALSE will
         *       not reset the parser until we close the file... and then
         *       we call the function again to reset the parser.
         *------------------------------------------------------------*/
        if (AVCE00ParseSectionEnd(psInfo->hParseInfo, pszLine, FALSE))
        {
            _AVCE00WriteCloseCoverFile(psInfo);
            AVCE00ParseSectionEnd(psInfo->hParseInfo, pszLine, TRUE);
        }
        else
        /*-------------------------------------------------------------
         * ... not at the end yet, so continue reading objects.
         *------------------------------------------------------------*/
        {
            void *psObj;
            psObj = AVCE00ParseNextLine(psInfo->hParseInfo, pszLine);

            if (psObj)
                AVCBinWriteObject(psInfo->hFile, psObj);
        }
    }

    if (psInfo->hParseInfo->bForceEndOfSection)
    {
        /*-------------------------------------------------------------
         * The last call encountered an implicit end of section, so
         * we close the section now without waiting for an end-of-section
         * line (there won't be any!)... and get ready to proceed with
         * the next section.
         * This is used for TABLEs.
         *------------------------------------------------------------*/
        _AVCE00WriteCloseCoverFile(psInfo);
        AVCE00ParseSectionEnd(psInfo->hParseInfo, pszLine, TRUE);
        /* psInfo->hParseInfo->bForceEndOfSection = FALSE; */
    }

    if (CPLGetLastErrorNo() != 0)
        nStatus = -1;

    return nStatus;
}

/**********************************************************************
 *                          AVCE00DeleteCoverage()
 *
 * Delete a coverage directory, its contents, and the associated info
 * tables.
 *
 * Note:
 * When deleting tables, only the ../info/arc????.nit and arc????.dat
 * need to be deleted; the arc.dir does not need to be updated.  This
 * is exactly what Arc/Info's KILL command does.
 *
 * Returns 0 on success or -1 on error.
 **********************************************************************/
int AVCE00DeleteCoverage(const char *pszCoverToDelete)
{
    int i, j, nStatus = 0;
    char *pszInfoPath, *pszCoverPath, *pszCoverName;
    const char *pszFname;
    char **papszTables = nullptr, **papszFiles = nullptr;
    AVCE00ReadPtr psInfo;
    VSIStatBuf sStatBuf;
    AVCCoverType eCoverType;

    CPLErrorReset();

    /*-----------------------------------------------------------------
     * Since we don't want to duplicate all the logic to figure coverage
     * and info dir name, etc... we'll simply open the coverage and
     * grab the info we need from the coverage handle.
     * By the same way, this will verify that the coverage exists and is
     * valid.
     *----------------------------------------------------------------*/
    psInfo = AVCE00ReadOpen(pszCoverToDelete);

    if (psInfo == nullptr)
    {
        CPLError(CE_Failure, CPLE_FileIO,
                 "Cannot delete coverage %s: it does not appear to be valid\n",
                 pszCoverToDelete);
        return -1;
    }

    pszCoverPath = CPLStrdup(psInfo->pszCoverPath);
    pszInfoPath = CPLStrdup(psInfo->pszInfoPath);
    pszCoverName = CPLStrdup(psInfo->pszCoverName);
    eCoverType = psInfo->eCoverType;

    AVCE00ReadClose(psInfo);

    /*-----------------------------------------------------------------
     * Delete files in cover directory.
     *----------------------------------------------------------------*/
    papszFiles = VSIReadDir(pszCoverPath);
    for (i = 0; nStatus == 0 && papszFiles && papszFiles[i]; i++)
    {
        if (!EQUAL(".", papszFiles[i]) && !EQUAL("..", papszFiles[i]))
        {
            pszFname = CPLSPrintf("%s%s", pszCoverPath, papszFiles[i]);
            if (unlink(pszFname) != 0)
            {
                CPLError(CE_Failure, CPLE_FileIO, "Failed deleting %s%s",
                         pszCoverPath, papszFiles[i]);
                nStatus = -1;
                break;
            }
        }
    }

    CSLDestroy(papszFiles);
    papszFiles = nullptr;

    /*-----------------------------------------------------------------
     * Get the list of info files (ARC????) to delete and delete them
     * (No 'info' directory for PC coverages)
     *----------------------------------------------------------------*/
    if (nStatus == 0 && eCoverType != AVCCoverPC && eCoverType != AVCCoverPC2)
    {
        papszTables =
            AVCBinReadListTables(pszInfoPath, pszCoverName, &papszFiles,
                                 eCoverType, nullptr /*DBCSInfo*/);

        for (i = 0; nStatus == 0 && papszFiles && papszFiles[i]; i++)
        {
            /* Convert table filename to lowercases */
            for (j = 0; papszFiles[i] && papszFiles[i][j] != '\0'; j++)
                papszFiles[i][j] = (char)CPLTolower(
                    static_cast<unsigned char>(papszFiles[i][j]));

            /* Delete the .DAT file */
            pszFname = CPLSPrintf("%s%s.dat", pszInfoPath, papszFiles[i]);
            if (VSIStat(pszFname, &sStatBuf) != -1 && unlink(pszFname) != 0)
            {
                CPLError(CE_Failure, CPLE_FileIO, "Failed deleting %s%s",
                         pszInfoPath, papszFiles[i]);
                nStatus = -1;
                break;
            }

            /* Delete the .DAT file */
            pszFname = CPLSPrintf("%s%s.nit", pszInfoPath, papszFiles[i]);
            if (VSIStat(pszFname, &sStatBuf) != -1 && unlink(pszFname) != 0)
            {
                CPLError(CE_Failure, CPLE_FileIO, "Failed deleting %s%s",
                         pszInfoPath, papszFiles[i]);
                nStatus = -1;
                break;
            }
        }

        CSLDestroy(papszTables);
        CSLDestroy(papszFiles);
    }

    /*-----------------------------------------------------------------
     * Delete the coverage directory itself
     * In some cases, the directory could be locked by another application
     * on the same system or somewhere on the network.
     * Define AVC_IGNORE_RMDIR_ERROR at compile time if you want this
     * error to be ignored.
     *----------------------------------------------------------------*/
    if (VSIRmdir(pszCoverPath) != 0)
    {
#ifndef AVC_IGNORE_RMDIR_ERROR
        CPLError(CE_Failure, CPLE_FileIO, "Failed deleting directory %s",
                 pszCoverPath);
        nStatus = -1;
#endif
    }

    CPLFree(pszCoverPath);
    CPLFree(pszInfoPath);
    CPLFree(pszCoverName);

    return nStatus;
}
