/******************************************************************************
 *
 * Project:  Microstation DGN Access Library
 * Purpose:  DGN Access functions related to writing DGN elements.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2002, Frank Warmerdam <warmerdam@pobox.com>
 * Copyright (c) 2011-2013, Even Rouault <even dot rouault at spatialys.com>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "dgnlibp.h"

#include <cmath>

#include <algorithm>

static void DGNPointToInt(DGNInfo *psDGN, DGNPoint *psPoint,
                          unsigned char *pabyTarget);

/************************************************************************/
/*                          DGNResizeElement()                          */
/************************************************************************/

/**
 * Resize an existing element.
 *
 * If the new size is the same as the old nothing happens.
 *
 * Otherwise, the old element in the file is marked as deleted, and the
 * DGNElemCore.offset and element_id are set to -1 indicating that the
 * element should be written to the end of file when next written by
 * DGNWriteElement().  The internal raw data buffer is updated to the new
 * size.
 *
 * Only elements with "raw_data" loaded may be moved.
 *
 * In normal use the DGNResizeElement() call would be called on a previously
 * loaded element, and afterwards the raw_data would be updated before calling
 * DGNWriteElement().  If DGNWriteElement() isn't called after
 * DGNResizeElement() then the element will be lost having been marked as
 * deleted in its old position but never written at the new location.
 *
 * @param hDGN the DGN file on which the element lives.
 * @param psElement the element to alter.
 * @param nNewSize the desired new size of the element in bytes.  Must be
 * a multiple of 2.
 *
 * @return TRUE on success, or FALSE on error.
 */

int DGNResizeElement(DGNHandle hDGN, DGNElemCore *psElement, int nNewSize)

{
    DGNInfo *psDGN = (DGNInfo *)hDGN;

    /* -------------------------------------------------------------------- */
    /*      Check various conditions.                                       */
    /* -------------------------------------------------------------------- */
    if (psElement->raw_bytes == 0 || psElement->raw_bytes != psElement->size)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Raw bytes not loaded, or not matching element size.");
        return FALSE;
    }

    if (nNewSize % 2 == 1)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "DGNResizeElement(%d): "
                 "can't change to odd (not divisible by two) size.",
                 nNewSize);
        return FALSE;
    }

    if (nNewSize == psElement->raw_bytes)
        return TRUE;

    /* -------------------------------------------------------------------- */
    /*      Mark the existing element as deleted if the element has to      */
    /*      move to the end of the file.                                    */
    /* -------------------------------------------------------------------- */

    if (psElement->offset != -1)
    {
        vsi_l_offset nOldFLoc = VSIFTellL(psDGN->fp);
        unsigned char abyLeader[2];

        if (VSIFSeekL(psDGN->fp, psElement->offset, SEEK_SET) != 0 ||
            VSIFReadL(abyLeader, sizeof(abyLeader), 1, psDGN->fp) != 1)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Failed seek or read when trying to mark existing\n"
                     "element as deleted in DGNResizeElement()\n");
            return FALSE;
        }

        abyLeader[1] |= 0x80;

        if (VSIFSeekL(psDGN->fp, psElement->offset, SEEK_SET) != 0 ||
            VSIFWriteL(abyLeader, sizeof(abyLeader), 1, psDGN->fp) != 1 ||
            VSIFSeekL(psDGN->fp, nOldFLoc, SEEK_SET) != 0)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Failed seek or write when trying to mark existing\n"
                     "element as deleted in DGNResizeElement()\n");
            return FALSE;
        }

        if (psElement->element_id != -1 && psDGN->index_built)
            psDGN->element_index[psElement->element_id].flags |= DGNEIF_DELETED;
    }

    psElement->offset = -1; /* move to end of file. */
    psElement->element_id = -1;

    /* -------------------------------------------------------------------- */
    /*      Set the new size information, and realloc the raw data buffer.  */
    /* -------------------------------------------------------------------- */
    psElement->size = nNewSize;
    psElement->raw_data =
        (unsigned char *)CPLRealloc(psElement->raw_data, nNewSize);
    psElement->raw_bytes = nNewSize;

    /* -------------------------------------------------------------------- */
    /*      Update the size information within the raw buffer.              */
    /* -------------------------------------------------------------------- */
    const int nWords = (nNewSize / 2) - 2;

    psElement->raw_data[2] = (unsigned char)(nWords % 256);
    psElement->raw_data[3] = (unsigned char)(nWords / 256);

    return TRUE;
}

/************************************************************************/
/*                          DGNWriteElement()                           */
/************************************************************************/

/**
 * Write element to file.
 *
 * Only elements with "raw_data" loaded may be written.  This should
 * include elements created with the various DGNCreate*() functions, and
 * those read from the file with the DGNO_CAPTURE_RAW_DATA flag turned on
 * with DGNSetOptions().
 *
 * The passed element is written to the indicated file.  If the
 * DGNElemCore.offset field is -1 then the element is written at the end of
 * the file (and offset/element are reset properly) otherwise the element
 * is written back to the location indicated by DGNElemCore.offset.
 *
 * If the element is added at the end of the file, and if an element index
 * has already been built, it will be updated to reference the new element.
 *
 * This function takes care of ensuring that the end-of-file marker is
 * maintained after the last element.
 *
 * @param hDGN the file to write the element to.
 * @param psElement the element to write.
 *
 * @return TRUE on success or FALSE in case of failure.
 */

int DGNWriteElement(DGNHandle hDGN, DGNElemCore *psElement)

{
    DGNInfo *psDGN = (DGNInfo *)hDGN;

    /* ==================================================================== */
    /*      If this element hasn't been positioned yet, place it at the     */
    /*      end of the file.                                                */
    /* ==================================================================== */
    if (psElement->offset == -1)
    {
        // We must have an index, in order to properly assign the
        // element id of the newly written element.  Ensure it is built.
        if (!psDGN->index_built)
            DGNBuildIndex(psDGN);

        // Read the current "last" element.
        if (!DGNGotoElement(hDGN, psDGN->element_count - 1))
            return FALSE;

        int nJunk = 0;
        if (!DGNLoadRawElement(psDGN, &nJunk, &nJunk))
            return FALSE;

        // Establish the position of the new element.
        psElement->offset = static_cast<int>(VSIFTellL(psDGN->fp));
        psElement->element_id = psDGN->element_count;

        // Grow element buffer if needed.
        if (psDGN->element_count == psDGN->max_element_count)
        {
            psDGN->max_element_count += 500;

            psDGN->element_index = (DGNElementInfo *)CPLRealloc(
                psDGN->element_index,
                psDGN->max_element_count * sizeof(DGNElementInfo));
        }

        // Set up the element info
        DGNElementInfo *psInfo = psDGN->element_index + psDGN->element_count;
        psInfo->level = (unsigned char)psElement->level;
        psInfo->type = (unsigned char)psElement->type;
        psInfo->stype = (unsigned char)psElement->stype;
        psInfo->offset = psElement->offset;
        if (psElement->complex)
            psInfo->flags = DGNEIF_COMPLEX;
        else
            psInfo->flags = 0;

        psDGN->element_count++;
    }

    /* -------------------------------------------------------------------- */
    /*      Write out the element.                                          */
    /* -------------------------------------------------------------------- */
    if (VSIFSeekL(psDGN->fp, psElement->offset, SEEK_SET) != 0 ||
        VSIFWriteL(psElement->raw_data, psElement->raw_bytes, 1, psDGN->fp) !=
            1)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Error seeking or writing new element of %d bytes at %d.",
                 psElement->offset, psElement->raw_bytes);
        return FALSE;
    }

    psDGN->next_element_id = psElement->element_id + 1;

    /* -------------------------------------------------------------------- */
    /*      Write out the end of file 0xffff marker (if we were             */
    /*      extending the file), but push the file pointer back before      */
    /*      this EOF when done.                                             */
    /* -------------------------------------------------------------------- */
    if (psDGN->next_element_id == psDGN->element_count)
    {
        const unsigned char abyEOF[2] = {0xff, 0xff};

        VSIFWriteL(abyEOF, 2, 1, psDGN->fp);
        VSIFSeekL(psDGN->fp, VSIFTellL(psDGN->fp) - 2, SEEK_SET);
    }

    return TRUE;
}

/************************************************************************/
/*                             DGNCreate()                              */
/************************************************************************/

/**
 * Create new DGN file.
 *
 * This function will create a new DGN file based on the provided seed
 * file, and return a handle on which elements may be read and written.
 *
 * The following creation flags may be passed:
 * <ul>
 * <li> DGNCF_USE_SEED_UNITS: The master and subunit resolutions and names
 * from the seed file will be used in the new file.  The nMasterUnitPerSubUnit,
 * nUORPerSubUnit, pszMasterUnits, and pszSubUnits arguments will be ignored.
 * <li> DGNCF_USE_SEED_ORIGIN: The origin from the seed file will be used
 * and the X, Y and Z origin passed into the call will be ignored.
 * <li> DGNCF_COPY_SEED_FILE_COLOR_TABLE: Should the first color table occurring
 * in the seed file also be copied?
 * <li> DGNCF_COPY_WHOLE_SEED_FILE: By default only the first three elements
 * (TCB, Digitizer Setup and Level Symbology) are copied from the seed file.
 * If this flag is provided the entire seed file is copied verbatim (with the
 * TCB origin and units possibly updated).
 * </ul>
 *
 * @param pszNewFilename the filename to create.  If it already exists
 * it will be overwritten.
 * @param pszSeedFile the seed file to copy header from.
 * @param nCreationFlags An ORing of DGNCF_* flags that are to take effect.
 * @param dfOriginX the X origin for the file.
 * @param dfOriginY the Y origin for the file.
 * @param dfOriginZ the Z origin for the file.
 * @param nSubUnitsPerMasterUnit the number of subunits in one master unit.
 * @param nUORPerSubUnit the number of UOR (units of resolution) per subunit.
 * @param pszMasterUnits the name of the master units (2 characters).
 * @param pszSubUnits the name of the subunits (2 characters).
 */

DGNHandle DGNCreate(const char *pszNewFilename, const char *pszSeedFile,
                    int nCreationFlags, double dfOriginX, double dfOriginY,
                    double dfOriginZ, int nSubUnitsPerMasterUnit,
                    int nUORPerSubUnit, const char *pszMasterUnits,
                    const char *pszSubUnits)

{
    /* -------------------------------------------------------------------- */
    /*      Open output file.                                               */
    /* -------------------------------------------------------------------- */
    VSILFILE *fpNew = VSIFOpenL(pszNewFilename, "wb");
    if (fpNew == nullptr)
    {
        CPLError(CE_Failure, CPLE_OpenFailed, "Failed to open output file: %s",
                 pszNewFilename);
        return nullptr;
    }

    /* -------------------------------------------------------------------- */
    /*      Open seed file, and read TCB element.                           */
    /* -------------------------------------------------------------------- */
    DGNInfo *psSeed = (DGNInfo *)DGNOpen(pszSeedFile, FALSE);
    if (psSeed == nullptr)
    {
        VSIFCloseL(fpNew);
        return nullptr;
    }

    DGNSetOptions(psSeed, DGNO_CAPTURE_RAW_DATA);

    DGNElemCore *psSrcTCB = DGNReadElement(psSeed);

    CPLAssert(psSrcTCB->raw_bytes >= 1536);

    /* -------------------------------------------------------------------- */
    /*      Modify TCB appropriately for the output file.                   */
    /* -------------------------------------------------------------------- */
    GByte *pabyRawTCB = static_cast<GByte *>(CPLMalloc(psSrcTCB->raw_bytes));

    memcpy(pabyRawTCB, psSrcTCB->raw_data, psSrcTCB->raw_bytes);

    if (!(nCreationFlags & DGNCF_USE_SEED_UNITS))
    {
        memcpy(pabyRawTCB + 1120, pszMasterUnits, 2);
        memcpy(pabyRawTCB + 1122, pszSubUnits, 2);

        DGN_WRITE_INT32(nUORPerSubUnit, pabyRawTCB + 1116);
        DGN_WRITE_INT32(nSubUnitsPerMasterUnit, pabyRawTCB + 1112);
    }
    else
    {
        nUORPerSubUnit = DGN_INT32(pabyRawTCB + 1116);
        nSubUnitsPerMasterUnit = DGN_INT32(pabyRawTCB + 1112);
    }

    if (!(nCreationFlags & DGNCF_USE_SEED_ORIGIN))
    {
        dfOriginX *= (nUORPerSubUnit * nSubUnitsPerMasterUnit);
        dfOriginY *= (nUORPerSubUnit * nSubUnitsPerMasterUnit);
        dfOriginZ *= (nUORPerSubUnit * nSubUnitsPerMasterUnit);

        memcpy(pabyRawTCB + 1240, &dfOriginX, 8);
        memcpy(pabyRawTCB + 1248, &dfOriginY, 8);
        memcpy(pabyRawTCB + 1256, &dfOriginZ, 8);

        IEEE2DGNDouble(pabyRawTCB + 1240);
        IEEE2DGNDouble(pabyRawTCB + 1248);
        IEEE2DGNDouble(pabyRawTCB + 1256);
    }

    /* -------------------------------------------------------------------- */
    /*      Write TCB and EOF to new file.                                  */
    /* -------------------------------------------------------------------- */
    VSIFWriteL(pabyRawTCB, psSrcTCB->raw_bytes, 1, fpNew);
    CPLFree(pabyRawTCB);

    unsigned char abyEOF[2] = {0xff, 0xff};

    VSIFWriteL(abyEOF, 2, 1, fpNew);

    DGNFreeElement(psSeed, psSrcTCB);

    /* -------------------------------------------------------------------- */
    /*      Close and re-open using DGN API.                                */
    /* -------------------------------------------------------------------- */
    VSIFCloseL(fpNew);

    DGNInfo *psDGN = (DGNInfo *)DGNOpen(pszNewFilename, TRUE);

    /* -------------------------------------------------------------------- */
    /*      Now copy over elements according to options in effect.          */
    /* -------------------------------------------------------------------- */
    DGNElemCore *psSrcElement = nullptr;
    DGNElemCore *psDstElement = nullptr;

    while ((psSrcElement = DGNReadElement(psSeed)) != nullptr)
    {
        if ((nCreationFlags & DGNCF_COPY_WHOLE_SEED_FILE) ||
            (psSrcElement->stype == DGNST_COLORTABLE &&
             nCreationFlags & DGNCF_COPY_SEED_FILE_COLOR_TABLE) ||
            psSrcElement->element_id <= 2)
        {
            psDstElement = DGNCloneElement(psSeed, psDGN, psSrcElement);
            DGNWriteElement(psDGN, psDstElement);
            DGNFreeElement(psDGN, psDstElement);
        }

        DGNFreeElement(psSeed, psSrcElement);
    }

    DGNClose(psSeed);

    return psDGN;
}

/************************************************************************/
/*                          DGNCloneElement()                           */
/************************************************************************/

/**
 * Clone a retargeted element.
 *
 * Creates a copy of an element in a suitable form to write to a
 * different file than that it was read from.
 *
 * NOTE: At this time the clone operation will fail if the source
 * and destination file have a different origin or master/sub units.
 *
 * @param hDGNSrc the source file (from which psSrcElement was read).
 * @param hDGNDst the destination file (to which the returned element may be
 * written).
 * @param psSrcElement the element to be cloned (from hDGNSrc).
 *
 * @return NULL on failure, or an appropriately modified copy of
 * the source element suitable to write to hDGNDst.
 */

DGNElemCore *DGNCloneElement(CPL_UNUSED DGNHandle hDGNSrc, DGNHandle hDGNDst,
                             DGNElemCore *psSrcElement)

{
    DGNElemCore *psClone = nullptr;

    DGNLoadTCB(hDGNDst);

    /* -------------------------------------------------------------------- */
    /*      Per structure specific copying.  The core is fixed up later.    */
    /* -------------------------------------------------------------------- */
    if (psSrcElement->stype == DGNST_CORE)
    {
        psClone = static_cast<DGNElemCore *>(CPLMalloc(sizeof(DGNElemCore)));
        memcpy(psClone, psSrcElement, sizeof(DGNElemCore));
    }
    else if (psSrcElement->stype == DGNST_MULTIPOINT)
    {
        DGNElemMultiPoint *psSrcMP = (DGNElemMultiPoint *)psSrcElement;

        const size_t nSize = sizeof(DGNElemMultiPoint) +
                             sizeof(DGNPoint) * (psSrcMP->num_vertices - 1);

        DGNElemMultiPoint *psMP =
            static_cast<DGNElemMultiPoint *>(CPLMalloc(nSize));
        memcpy(psMP, psSrcElement, nSize);

        psClone = (DGNElemCore *)psMP;
    }
    else if (psSrcElement->stype == DGNST_ARC)
    {
        DGNElemArc *psArc =
            static_cast<DGNElemArc *>(CPLMalloc(sizeof(DGNElemArc)));
        memcpy(psArc, psSrcElement, sizeof(DGNElemArc));

        psClone = (DGNElemCore *)psArc;
    }
    else if (psSrcElement->stype == DGNST_TEXT)
    {
        DGNElemText *psSrcText = (DGNElemText *)psSrcElement;
        const size_t nSize = sizeof(DGNElemText) + strlen(psSrcText->string);

        DGNElemText *psText = static_cast<DGNElemText *>(CPLMalloc(nSize));
        memcpy(psText, psSrcElement, nSize);

        psClone = (DGNElemCore *)psText;
    }
    else if (psSrcElement->stype == DGNST_TEXT_NODE)
    {
        DGNElemTextNode *psNode =
            static_cast<DGNElemTextNode *>(CPLMalloc(sizeof(DGNElemTextNode)));
        memcpy(psNode, psSrcElement, sizeof(DGNElemTextNode));

        psClone = (DGNElemCore *)psNode;
    }
    else if (psSrcElement->stype == DGNST_COMPLEX_HEADER)
    {
        DGNElemComplexHeader *psCH = static_cast<DGNElemComplexHeader *>(
            CPLMalloc(sizeof(DGNElemComplexHeader)));
        memcpy(psCH, psSrcElement, sizeof(DGNElemComplexHeader));

        psClone = (DGNElemCore *)psCH;
    }
    else if (psSrcElement->stype == DGNST_COLORTABLE)
    {
        DGNElemColorTable *psCT = static_cast<DGNElemColorTable *>(
            CPLMalloc(sizeof(DGNElemColorTable)));
        memcpy(psCT, psSrcElement, sizeof(DGNElemColorTable));

        psClone = (DGNElemCore *)psCT;
    }
    else if (psSrcElement->stype == DGNST_TCB)
    {
        DGNElemTCB *psTCB =
            static_cast<DGNElemTCB *>(CPLMalloc(sizeof(DGNElemTCB)));
        memcpy(psTCB, psSrcElement, sizeof(DGNElemTCB));

        psClone = (DGNElemCore *)psTCB;
    }
    else if (psSrcElement->stype == DGNST_CELL_HEADER)
    {
        DGNElemCellHeader *psCH = static_cast<DGNElemCellHeader *>(
            CPLMalloc(sizeof(DGNElemCellHeader)));
        memcpy(psCH, psSrcElement, sizeof(DGNElemCellHeader));

        psClone = (DGNElemCore *)psCH;
    }
    else if (psSrcElement->stype == DGNST_CELL_LIBRARY)
    {
        DGNElemCellLibrary *psCL = static_cast<DGNElemCellLibrary *>(
            CPLMalloc(sizeof(DGNElemCellLibrary)));
        memcpy(psCL, psSrcElement, sizeof(DGNElemCellLibrary));

        psClone = (DGNElemCore *)psCL;
    }
    else if (psSrcElement->stype == DGNST_TAG_VALUE)
    {
        DGNElemTagValue *psTV =
            static_cast<DGNElemTagValue *>(CPLMalloc(sizeof(DGNElemTagValue)));
        memcpy(psTV, psSrcElement, sizeof(DGNElemTagValue));

        if (psTV->tagType == 1)
            psTV->tagValue.string = CPLStrdup(psTV->tagValue.string);

        psClone = (DGNElemCore *)psTV;
    }
    else if (psSrcElement->stype == DGNST_TAG_SET)
    {
        DGNElemTagSet *psTS =
            static_cast<DGNElemTagSet *>(CPLMalloc(sizeof(DGNElemTagSet)));
        memcpy(psTS, psSrcElement, sizeof(DGNElemTagSet));

        psTS->tagSetName = CPLStrdup(psTS->tagSetName);

        DGNTagDef *pasTagList = static_cast<DGNTagDef *>(
            CPLMalloc(sizeof(DGNTagDef) * psTS->tagCount));
        memcpy(pasTagList, psTS->tagList, sizeof(DGNTagDef) * psTS->tagCount);

        for (int iTag = 0; iTag < psTS->tagCount; iTag++)
        {
            pasTagList[iTag].name = CPLStrdup(pasTagList[iTag].name);
            pasTagList[iTag].prompt = CPLStrdup(pasTagList[iTag].prompt);
            if (pasTagList[iTag].type == 1)
                pasTagList[iTag].defaultValue.string =
                    CPLStrdup(pasTagList[iTag].defaultValue.string);
        }

        psTS->tagList = pasTagList;
        psClone = (DGNElemCore *)psTS;
    }
    else if (psSrcElement->stype == DGNST_CONE)
    {
        DGNElemCone *psCone =
            static_cast<DGNElemCone *>(CPLMalloc(sizeof(DGNElemCone)));
        memcpy(psCone, psSrcElement, sizeof(DGNElemCone));

        psClone = (DGNElemCore *)psCone;
    }
    else if (psSrcElement->stype == DGNST_BSPLINE_SURFACE_HEADER)
    {
        DGNElemBSplineSurfaceHeader *psSurface =
            static_cast<DGNElemBSplineSurfaceHeader *>(
                CPLMalloc(sizeof(DGNElemBSplineSurfaceHeader)));
        memcpy(psSurface, psSrcElement, sizeof(DGNElemBSplineSurfaceHeader));

        psClone = (DGNElemCore *)psSurface;
    }
    else if (psSrcElement->stype == DGNST_BSPLINE_CURVE_HEADER)
    {
        DGNElemBSplineCurveHeader *psCurve =
            static_cast<DGNElemBSplineCurveHeader *>(
                CPLMalloc(sizeof(DGNElemBSplineCurveHeader)));
        memcpy(psCurve, psSrcElement, sizeof(DGNElemBSplineCurveHeader));

        psClone = (DGNElemCore *)psCurve;
    }
    else if (psSrcElement->stype == DGNST_BSPLINE_SURFACE_BOUNDARY)
    {
        DGNElemBSplineSurfaceBoundary *psSrcBSB =
            (DGNElemBSplineSurfaceBoundary *)psSrcElement;

        const size_t nSize = sizeof(DGNElemBSplineSurfaceBoundary) +
                             sizeof(DGNPoint) * (psSrcBSB->numverts - 1);

        DGNElemBSplineSurfaceBoundary *psBSB =
            static_cast<DGNElemBSplineSurfaceBoundary *>(CPLMalloc(nSize));
        memcpy(psBSB, psSrcElement, nSize);

        psClone = (DGNElemCore *)psBSB;
    }
    else if (psSrcElement->stype == DGNST_KNOT_WEIGHT)
    {
        // FIXME: Is it OK to assume that the # of elements corresponds
        // directly to the element size? kintel 20051218.
        const int numelems =
            (psSrcElement->size - 36 - psSrcElement->attr_bytes) / 4;

        /* DGNElemKnotWeight *psSrcArray = (DGNElemKnotWeight *) psSrcElement;
         */

        const size_t nSize =
            sizeof(DGNElemKnotWeight) + sizeof(long) * (numelems - 1);

        DGNElemKnotWeight *psArray =
            static_cast<DGNElemKnotWeight *>(CPLMalloc(nSize));
        memcpy(psArray, psSrcElement, nSize);

        psClone = (DGNElemCore *)psArray;
    }
    else if (psSrcElement->stype == DGNST_SHARED_CELL_DEFN)
    {
        DGNElemSharedCellDefn *psCH = static_cast<DGNElemSharedCellDefn *>(
            CPLMalloc(sizeof(DGNElemSharedCellDefn)));
        memcpy(psCH, psSrcElement, sizeof(DGNElemSharedCellDefn));

        psClone = (DGNElemCore *)psCH;
    }
    else
    {
        CPLAssert(false);
        return nullptr;
    }

    /* -------------------------------------------------------------------- */
    /*      Copy core raw data, and attributes.                             */
    /* -------------------------------------------------------------------- */
    if (psClone->raw_bytes != 0)
    {
        psClone->raw_data =
            static_cast<unsigned char *>(CPLMalloc(psClone->raw_bytes));
        memcpy(psClone->raw_data, psSrcElement->raw_data, psClone->raw_bytes);
    }

    if (psClone->attr_bytes != 0)
    {
        psClone->attr_data =
            static_cast<unsigned char *>(CPLMalloc(psClone->attr_bytes));
        memcpy(psClone->attr_data, psSrcElement->attr_data,
               psClone->attr_bytes);
    }

    /* -------------------------------------------------------------------- */
    /*      Clear location and id information.                              */
    /* -------------------------------------------------------------------- */
    psClone->offset = -1;
    psClone->element_id = -1;

    return psClone;
}

/************************************************************************/
/*                         DGNUpdateElemCore()                          */
/************************************************************************/

/**
 * Change element core values.
 *
 * The indicated values in the element are updated in the structure, as well
 * as in the raw data.  The updated element is not written to disk.  That
 * must be done with DGNWriteElement().   The element must have raw_data
 * loaded.
 *
 * @param hDGN the file on which the element belongs.
 * @param psElement the element to modify.
 * @param nLevel the new level value.
 * @param nGraphicGroup the new graphic group value.
 * @param nColor the new color index.
 * @param nWeight the new element weight.
 * @param nStyle the new style value for the element.
 *
 * @return Returns TRUE on success or FALSE on failure.
 */

int DGNUpdateElemCore(DGNHandle hDGN, DGNElemCore *psElement, int nLevel,
                      int nGraphicGroup, int nColor, int nWeight, int nStyle)

{
    psElement->level = nLevel;
    psElement->graphic_group = nGraphicGroup;
    psElement->color = nColor;
    psElement->weight = nWeight;
    psElement->style = nStyle;

    return DGNUpdateElemCoreExtended(hDGN, psElement);
}

/************************************************************************/
/*                     DGNUpdateElemCoreExtended()                      */
/************************************************************************/

/**
 * Update internal raw data representation.
 *
 * The raw_data representation of the passed element is updated to reflect
 * the various core fields.  The DGNElemCore level, type, complex, deleted,
 * graphic_group, properties, color, weight and style values are all
 * applied to the raw_data representation.  Spatial bounds, element type
 * specific information and attributes are not updated in the raw data.
 *
 * @param hDGN the file to which the element belongs.
 * @param psElement the element to be updated.
 *
 * @return TRUE on success, or FALSE on failure.
 */

int DGNUpdateElemCoreExtended(CPL_UNUSED DGNHandle hDGN, DGNElemCore *psElement)
{
    GByte *rd = psElement->raw_data;
    const int nWords = (psElement->raw_bytes / 2) - 2;

    if (psElement->raw_data == nullptr || psElement->raw_bytes < 36)
    {
        CPLAssert(false);
        return FALSE;
    }

    /* -------------------------------------------------------------------- */
    /*      Setup first four bytes.                                         */
    /* -------------------------------------------------------------------- */
    rd[0] = (GByte)psElement->level;
    if (psElement->complex)
        rd[0] |= 0x80;

    rd[1] = (GByte)psElement->type;
    if (psElement->deleted)
        rd[1] |= 0x80;

    rd[2] = (GByte)(nWords % 256);
    rd[3] = (GByte)(nWords / 256);

    /* -------------------------------------------------------------------- */
    /*      If the attribute offset hasn't been set, set it now under       */
    /*      the assumption it should point to the end of the element.       */
    /* -------------------------------------------------------------------- */
    if (psElement->raw_data[30] == 0 && psElement->raw_data[31] == 0)
    {
        const int nAttIndex = (psElement->raw_bytes - 32) / 2;

        psElement->raw_data[30] = (GByte)(nAttIndex % 256);
        psElement->raw_data[31] = (GByte)(nAttIndex / 256);
    }
    /* -------------------------------------------------------------------- */
    /*      Handle the graphic properties.                                  */
    /* -------------------------------------------------------------------- */
    if (psElement->raw_bytes > 36 && DGNElemTypeHasDispHdr(psElement->type))
    {
        rd[28] = (GByte)(psElement->graphic_group % 256);
        rd[29] = (GByte)(psElement->graphic_group / 256);
        rd[32] = (GByte)(psElement->properties % 256);
        rd[33] = (GByte)(psElement->properties / 256);
        rd[34] = (GByte)(psElement->style | (psElement->weight << 3));
        rd[35] = (GByte)psElement->color;
    }

    return TRUE;
}

/************************************************************************/
/*                         DGNInitializeElemCore()                      */
/************************************************************************/

static void DGNInitializeElemCore(CPL_UNUSED DGNHandle hDGN,
                                  DGNElemCore *psElement)
{
    memset(psElement, 0, sizeof(DGNElemCore));

    psElement->offset = -1;
    psElement->element_id = -1;
}

/************************************************************************/
/*                           DGNWriteBounds()                           */
/*                                                                      */
/*      Write bounds to element raw data.                               */
/************************************************************************/

static void DGNWriteBounds(DGNInfo *psInfo, DGNElemCore *psElement,
                           DGNPoint *psMin, DGNPoint *psMax)

{
    CPLAssert(psElement->raw_bytes >= 28);

    DGNInverseTransformPointToInt(psInfo, psMin, psElement->raw_data + 4);
    DGNInverseTransformPointToInt(psInfo, psMax, psElement->raw_data + 16);

    /* convert from twos complement to "binary offset" format. */

    psElement->raw_data[5] ^= 0x80;
    psElement->raw_data[9] ^= 0x80;
    psElement->raw_data[13] ^= 0x80;
    psElement->raw_data[17] ^= 0x80;
    psElement->raw_data[21] ^= 0x80;
    psElement->raw_data[25] ^= 0x80;
}

/************************************************************************/
/*                      DGNCreateMultiPointElem()                       */
/************************************************************************/

/**
 * Create new multi-point element.
 *
 * The newly created element will still need to be written to file using
 * DGNWriteElement(). Also the level and other core values will be defaulted.
 * Use DGNUpdateElemCore() on the element before writing to set these values.
 *
 * NOTE: There are restrictions on the nPointCount for some elements. For
 * instance, DGNT_LINE can only have 2 points. Maximum element size
 * precludes very large numbers of points.
 *
 * @param hDGN the file on which the element will eventually be written.
 * @param nType the type of the element to be created.  It must be one of
 * DGNT_LINE, DGNT_LINE_STRING, DGNT_SHAPE, DGNT_CURVE or DGNT_BSPLINE_POLE.
 * @param nPointCount the number of points in the pasVertices list.
 * @param pasVertices the list of points to be written.
 *
 * @return the new element (a DGNElemMultiPoint structure) or NULL on failure.
 */

DGNElemCore *DGNCreateMultiPointElem(DGNHandle hDGN, int nType, int nPointCount,
                                     DGNPoint *pasVertices)

{
    DGNInfo *psDGN = (DGNInfo *)hDGN;

    CPLAssert(nType == DGNT_LINE || nType == DGNT_LINE_STRING ||
              nType == DGNT_SHAPE || nType == DGNT_CURVE ||
              nType == DGNT_BSPLINE_POLE);

    DGNLoadTCB(hDGN);

    /* -------------------------------------------------------------------- */
    /*      Is this too many vertices to write to a single element?         */
    /* -------------------------------------------------------------------- */
    if (nPointCount > 101)
    {
        CPLError(CE_Failure, CPLE_ElementTooBig,
                 "Attempt to create %s element with %d points failed.\n"
                 "Element would be too large.",
                 DGNTypeToName(nType), nPointCount);
        return nullptr;
    }

    /* -------------------------------------------------------------------- */
    /*      Allocate element.                                               */
    /* -------------------------------------------------------------------- */
    DGNElemMultiPoint *psMP = static_cast<DGNElemMultiPoint *>(CPLCalloc(
        sizeof(DGNElemMultiPoint) + sizeof(DGNPoint) * (nPointCount - 1), 1));
    DGNElemCore *psCore = &(psMP->core);

    DGNInitializeElemCore(hDGN, psCore);
    psCore->stype = DGNST_MULTIPOINT;
    psCore->type = nType;

    /* -------------------------------------------------------------------- */
    /*      Set multipoint specific information in the structure.           */
    /* -------------------------------------------------------------------- */
    psMP->num_vertices = nPointCount;
    // coverity[overrun-buffer-arg]
    memcpy(psMP->vertices + 0, pasVertices, sizeof(DGNPoint) * nPointCount);

    /* -------------------------------------------------------------------- */
    /*      Setup Raw data for the multipoint section.                      */
    /* -------------------------------------------------------------------- */
    if (nType == DGNT_LINE)
    {
        CPLAssert(nPointCount == 2);

        psCore->raw_bytes = 36 + psDGN->dimension * 4 * nPointCount;

        psCore->raw_data =
            static_cast<unsigned char *>(CPLCalloc(psCore->raw_bytes, 1));

        DGNInverseTransformPointToInt(psDGN, pasVertices + 0,
                                      psCore->raw_data + 36);
        DGNInverseTransformPointToInt(psDGN, pasVertices + 1,
                                      psCore->raw_data + 36 +
                                          psDGN->dimension * 4);
    }
    else
    {
        CPLAssert(nPointCount >= 2);

        psCore->raw_bytes = 38 + psDGN->dimension * 4 * nPointCount;
        psCore->raw_data =
            static_cast<unsigned char *>(CPLCalloc(psCore->raw_bytes, 1));

        psCore->raw_data[36] = (unsigned char)(nPointCount % 256);
        psCore->raw_data[37] = (unsigned char)(nPointCount / 256);

        for (int i = 0; i < nPointCount; i++)
            DGNInverseTransformPointToInt(psDGN, pasVertices + i,
                                          psCore->raw_data + 38 +
                                              psDGN->dimension * i * 4);
    }

    /* -------------------------------------------------------------------- */
    /*      Set the core raw data, including the bounds.                    */
    /* -------------------------------------------------------------------- */
    DGNUpdateElemCoreExtended(hDGN, psCore);

    DGNPoint sMin = pasVertices[0];
    DGNPoint sMax = pasVertices[0];
    for (int i = 1; i < nPointCount; i++)
    {
        sMin.x = std::min(pasVertices[i].x, sMin.x);
        sMin.y = std::min(pasVertices[i].y, sMin.y);
        sMin.z = std::min(pasVertices[i].z, sMin.z);
        sMax.x = std::max(pasVertices[i].x, sMax.x);
        sMax.y = std::max(pasVertices[i].y, sMax.y);
        sMax.z = std::max(pasVertices[i].z, sMax.z);
    }

    DGNWriteBounds(psDGN, psCore, &sMin, &sMax);

    return (DGNElemCore *)psMP;
}

/************************************************************************/
/*                         DGNCreateArcElem2D()                         */
/************************************************************************/

DGNElemCore *DGNCreateArcElem2D(DGNHandle hDGN, int nType, double dfOriginX,
                                double dfOriginY, double dfPrimaryAxis,
                                double dfSecondaryAxis, double dfRotation,
                                double dfStartAngle, double dfSweepAngle)

{
    return DGNCreateArcElem(hDGN, nType, dfOriginX, dfOriginY, 0.0,
                            dfPrimaryAxis, dfSecondaryAxis, dfStartAngle,
                            dfSweepAngle, dfRotation, nullptr);
}

/************************************************************************/
/*                          DGNCreateArcElem()                          */
/************************************************************************/

/**
 * Create Arc or Ellipse element.
 *
 * Create a new 2D or 3D arc or ellipse element.  The start angle, and sweep
 * angle are ignored for DGNT_ELLIPSE but used for DGNT_ARC.
 *
 * The newly created element will still need to be written to file using
 * DGNWriteElement(). Also the level and other core values will be defaulted.
 * Use DGNUpdateElemCore() on the element before writing to set these values.
 *
 * @param hDGN the DGN file on which the element will eventually be written.
 * @param nType either DGNT_ELLIPSE or DGNT_ARC to select element type.
 * @param dfOriginX the origin (center of rotation) of the arc (X).
 * @param dfOriginY the origin (center of rotation) of the arc (Y).
 * @param dfOriginZ the origin (center of rotation) of the arc (Y).
 * @param dfPrimaryAxis the length of the primary axis.
 * @param dfSecondaryAxis the length of the secondary axis.
 * @param dfStartAngle start angle, degrees counterclockwise of primary axis.
 * @param dfSweepAngle sweep angle, degrees
 * @param dfRotation Counterclockwise rotation in degrees.
 * @param panQuaternion 3D orientation quaternion (NULL to use rotation).
 *
 * @return the new element (DGNElemArc) or NULL on failure.
 */

DGNElemCore *DGNCreateArcElem(DGNHandle hDGN, int nType, double dfOriginX,
                              double dfOriginY, double dfOriginZ,
                              double dfPrimaryAxis, double dfSecondaryAxis,
                              double dfStartAngle, double dfSweepAngle,
                              double dfRotation, int *panQuaternion)

{
    CPLAssert(nType == DGNT_ARC || nType == DGNT_ELLIPSE);

    DGNInfo *psDGN = (DGNInfo *)hDGN;
    DGNLoadTCB(hDGN);

    /* -------------------------------------------------------------------- */
    /*      Allocate element.                                               */
    /* -------------------------------------------------------------------- */
    DGNElemArc *psArc =
        static_cast<DGNElemArc *>(CPLCalloc(sizeof(DGNElemArc), 1));
    DGNElemCore *psCore = &(psArc->core);

    DGNInitializeElemCore(hDGN, psCore);
    psCore->stype = DGNST_ARC;
    psCore->type = nType;

    /* -------------------------------------------------------------------- */
    /*      Set arc specific information in the structure.                  */
    /* -------------------------------------------------------------------- */
    DGNPoint sOrigin = {dfOriginX, dfOriginY, dfOriginZ};

    psArc->origin = sOrigin;
    psArc->primary_axis = dfPrimaryAxis;
    psArc->secondary_axis = dfSecondaryAxis;
    memset(psArc->quat, 0, sizeof(int) * 4);
    psArc->startang = dfStartAngle;
    psArc->sweepang = dfSweepAngle;

    psArc->rotation = dfRotation;
    if (panQuaternion == nullptr)
    {
        DGNRotationToQuaternion(dfRotation, psArc->quat);
    }
    else
    {
        memcpy(psArc->quat, panQuaternion, sizeof(int) * 4);
    }

    /* -------------------------------------------------------------------- */
    /*      Setup Raw data for the arc section.                             */
    /* -------------------------------------------------------------------- */
    if (nType == DGNT_ARC)
    {
        double dfScaledAxis;

        if (psDGN->dimension == 3)
            psCore->raw_bytes = 100;
        else
            psCore->raw_bytes = 80;
        psCore->raw_data =
            static_cast<unsigned char *>(CPLCalloc(psCore->raw_bytes, 1));

        /* start angle */
        GInt32 nAngle = (int)(dfStartAngle * 360000.0);
        DGN_WRITE_INT32(nAngle, psCore->raw_data + 36);

        /* sweep angle */
        if (dfSweepAngle < 0.0)
        {
            nAngle = static_cast<int>(std::abs(dfSweepAngle) * 360000.0);
            nAngle |= 0x80000000;
        }
        else if (dfSweepAngle > 364.9999)
        {
            nAngle = 0;
        }
        else
        {
            nAngle = (int)(dfSweepAngle * 360000.0);
        }
        DGN_WRITE_INT32(nAngle, psCore->raw_data + 40);

        /* axes */
        dfScaledAxis = dfPrimaryAxis / psDGN->scale;
        memcpy(psCore->raw_data + 44, &dfScaledAxis, 8);
        IEEE2DGNDouble(psCore->raw_data + 44);

        dfScaledAxis = dfSecondaryAxis / psDGN->scale;
        memcpy(psCore->raw_data + 52, &dfScaledAxis, 8);
        IEEE2DGNDouble(psCore->raw_data + 52);

        if (psDGN->dimension == 3)
        {
            /* quaternion */
            DGN_WRITE_INT32(psArc->quat[0], psCore->raw_data + 60);
            DGN_WRITE_INT32(psArc->quat[1], psCore->raw_data + 64);
            DGN_WRITE_INT32(psArc->quat[2], psCore->raw_data + 68);
            DGN_WRITE_INT32(psArc->quat[3], psCore->raw_data + 72);

            /* origin */
            DGNInverseTransformPoint(psDGN, &sOrigin);
            memcpy(psCore->raw_data + 76, &(sOrigin.x), 8);
            memcpy(psCore->raw_data + 84, &(sOrigin.y), 8);
            memcpy(psCore->raw_data + 92, &(sOrigin.z), 8);
            IEEE2DGNDouble(psCore->raw_data + 76);
            IEEE2DGNDouble(psCore->raw_data + 84);
            IEEE2DGNDouble(psCore->raw_data + 92);
        }
        else
        {
            /* rotation */
            nAngle = (int)(dfRotation * 360000.0);
            DGN_WRITE_INT32(nAngle, psCore->raw_data + 60);

            /* origin */
            DGNInverseTransformPoint(psDGN, &sOrigin);
            memcpy(psCore->raw_data + 64, &(sOrigin.x), 8);
            memcpy(psCore->raw_data + 72, &(sOrigin.y), 8);
            IEEE2DGNDouble(psCore->raw_data + 64);
            IEEE2DGNDouble(psCore->raw_data + 72);
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Setup Raw data for the ellipse section.                         */
    /* -------------------------------------------------------------------- */
    else
    {
        double dfScaledAxis;

        if (psDGN->dimension == 3)
            psCore->raw_bytes = 92;
        else
            psCore->raw_bytes = 72;
        psCore->raw_data = (unsigned char *)CPLCalloc(psCore->raw_bytes, 1);

        /* axes */
        dfScaledAxis = dfPrimaryAxis / psDGN->scale;
        memcpy(psCore->raw_data + 36, &dfScaledAxis, 8);
        IEEE2DGNDouble(psCore->raw_data + 36);

        dfScaledAxis = dfSecondaryAxis / psDGN->scale;
        memcpy(psCore->raw_data + 44, &dfScaledAxis, 8);
        IEEE2DGNDouble(psCore->raw_data + 44);

        if (psDGN->dimension == 3)
        {
            /* quaternion */
            DGN_WRITE_INT32(psArc->quat[0], psCore->raw_data + 52);
            DGN_WRITE_INT32(psArc->quat[1], psCore->raw_data + 56);
            DGN_WRITE_INT32(psArc->quat[2], psCore->raw_data + 60);
            DGN_WRITE_INT32(psArc->quat[3], psCore->raw_data + 64);

            /* origin */
            DGNInverseTransformPoint(psDGN, &sOrigin);
            memcpy(psCore->raw_data + 68, &(sOrigin.x), 8);
            memcpy(psCore->raw_data + 76, &(sOrigin.y), 8);
            memcpy(psCore->raw_data + 84, &(sOrigin.z), 8);
            IEEE2DGNDouble(psCore->raw_data + 68);
            IEEE2DGNDouble(psCore->raw_data + 76);
            IEEE2DGNDouble(psCore->raw_data + 84);
        }
        else
        {
            /* rotation */
            GInt32 nAngle = (int)(dfRotation * 360000.0);
            DGN_WRITE_INT32(nAngle, psCore->raw_data + 52);

            /* origin */
            DGNInverseTransformPoint(psDGN, &sOrigin);
            memcpy(psCore->raw_data + 56, &(sOrigin.x), 8);
            memcpy(psCore->raw_data + 64, &(sOrigin.y), 8);
            IEEE2DGNDouble(psCore->raw_data + 56);
            IEEE2DGNDouble(psCore->raw_data + 64);
        }

        psArc->startang = 0.0;
        psArc->sweepang = 360.0;
    }

    /* -------------------------------------------------------------------- */
    /*      Set the core raw data, including the bounds.                    */
    /* -------------------------------------------------------------------- */
    DGNUpdateElemCoreExtended(hDGN, psCore);

    DGNPoint sMin = {dfOriginX - std::max(dfPrimaryAxis, dfSecondaryAxis),
                     dfOriginY - std::max(dfPrimaryAxis, dfSecondaryAxis),
                     dfOriginZ - std::max(dfPrimaryAxis, dfSecondaryAxis)};
    DGNPoint sMax = {dfOriginX + std::max(dfPrimaryAxis, dfSecondaryAxis),
                     dfOriginY + std::max(dfPrimaryAxis, dfSecondaryAxis),
                     dfOriginZ + std::max(dfPrimaryAxis, dfSecondaryAxis)};

    DGNWriteBounds(psDGN, psCore, &sMin, &sMax);

    return (DGNElemCore *)psArc;
}

/************************************************************************/
/*                          DGNCreateConeElem()                         */
/************************************************************************/

/**
 * Create Cone element.
 *
 * Create a new 3D cone element.
 *
 * The newly created element will still need to be written to file using
 * DGNWriteElement(). Also the level and other core values will be defaulted.
 * Use DGNUpdateElemCore() on the element before writing to set these values.
 *
 * @param hDGN the DGN file on which the element will eventually be written.
 * @param dfCenter_1X the center of the first bounding circle (X).
 * @param dfCenter_1Y the center of the first bounding circle (Y).
 * @param dfCenter_1Z the center of the first bounding circle (Z).
 * @param dfRadius_1 the radius of the first bounding circle.
 * @param dfCenter_2X the center of the second bounding circle (X).
 * @param dfCenter_2Y the center of the second bounding circle (Y).
 * @param dfCenter_2Z the center of the second bounding circle (Z).
 * @param dfRadius_2 the radius of the second bounding circle.
 * @param panQuaternion 3D orientation quaternion (NULL for default orientation
 * - circles parallel to the X-Y plane).
 *
 * @return the new element (DGNElemCone) or NULL on failure.
 */

DGNElemCore *DGNCreateConeElem(DGNHandle hDGN, double dfCenter_1X,
                               double dfCenter_1Y, double dfCenter_1Z,
                               double dfRadius_1, double dfCenter_2X,
                               double dfCenter_2Y, double dfCenter_2Z,
                               double dfRadius_2, int *panQuaternion)
{
    DGNInfo *psDGN = (DGNInfo *)hDGN;

    DGNLoadTCB(hDGN);

    /* -------------------------------------------------------------------- */
    /*      Allocate element.                                               */
    /* -------------------------------------------------------------------- */
    DGNElemCone *psCone = (DGNElemCone *)CPLCalloc(sizeof(DGNElemCone), 1);
    DGNElemCore *psCore = &(psCone->core);

    DGNInitializeElemCore(hDGN, psCore);
    psCore->stype = DGNST_CONE;
    psCore->type = DGNT_CONE;

    /* -------------------------------------------------------------------- */
    /*      Set cone specific information in the structure.                 */
    /* -------------------------------------------------------------------- */
    DGNPoint sCenter_1 = {dfCenter_1X, dfCenter_1Y, dfCenter_1Z};
    DGNPoint sCenter_2 = {dfCenter_2X, dfCenter_2Y, dfCenter_2Z};
    psCone->center_1 = sCenter_1;
    psCone->center_2 = sCenter_2;
    psCone->radius_1 = dfRadius_1;
    psCone->radius_2 = dfRadius_2;

    memset(psCone->quat, 0, sizeof(int) * 4);
    if (panQuaternion != nullptr)
    {
        memcpy(psCone->quat, panQuaternion, sizeof(int) * 4);
    }
    else
    {
        psCone->quat[0] = static_cast<int>(1U << 31);
        psCone->quat[1] = 0;
        psCone->quat[2] = 0;
        psCone->quat[3] = 0;
    }

    /* -------------------------------------------------------------------- */
    /*      Setup Raw data for the cone.                                    */
    /* -------------------------------------------------------------------- */
    psCore->raw_bytes = 118;
    psCore->raw_data = (unsigned char *)CPLCalloc(psCore->raw_bytes, 1);

    /* unknown data */
    psCore->raw_data[36] = 0;
    psCore->raw_data[37] = 0;

    /* quaternion */
    DGN_WRITE_INT32(psCone->quat[0], psCore->raw_data + 38);
    DGN_WRITE_INT32(psCone->quat[1], psCore->raw_data + 42);
    DGN_WRITE_INT32(psCone->quat[2], psCore->raw_data + 46);
    DGN_WRITE_INT32(psCone->quat[3], psCore->raw_data + 50);

    /* center_1 */
    DGNInverseTransformPoint(psDGN, &sCenter_1);
    memcpy(psCore->raw_data + 54, &sCenter_1.x, 8);
    memcpy(psCore->raw_data + 62, &sCenter_1.y, 8);
    memcpy(psCore->raw_data + 70, &sCenter_1.z, 8);
    IEEE2DGNDouble(psCore->raw_data + 54);
    IEEE2DGNDouble(psCore->raw_data + 62);
    IEEE2DGNDouble(psCore->raw_data + 70);

    /* radius_1 */
    double dfScaledRadius = psCone->radius_1 / psDGN->scale;
    memcpy(psCore->raw_data + 78, &dfScaledRadius, 8);
    IEEE2DGNDouble(psCore->raw_data + 78);

    /* center_2 */
    DGNInverseTransformPoint(psDGN, &sCenter_2);
    memcpy(psCore->raw_data + 86, &sCenter_2.x, 8);
    memcpy(psCore->raw_data + 94, &sCenter_2.y, 8);
    memcpy(psCore->raw_data + 102, &sCenter_2.z, 8);
    IEEE2DGNDouble(psCore->raw_data + 86);
    IEEE2DGNDouble(psCore->raw_data + 94);
    IEEE2DGNDouble(psCore->raw_data + 102);

    /* radius_2 */
    dfScaledRadius = psCone->radius_2 / psDGN->scale;
    memcpy(psCore->raw_data + 110, &dfScaledRadius, 8);
    IEEE2DGNDouble(psCore->raw_data + 110);

    /* -------------------------------------------------------------------- */
    /*      Set the core raw data, including the bounds.                    */
    /* -------------------------------------------------------------------- */
    DGNUpdateElemCoreExtended(hDGN, psCore);

    // FIXME: Calculate bounds. Do we need to take the quaternion into account?
    //  kintel 20030819

    // Old implementation attempt:
    // What if center_1.z > center_2.z ?
    //     double largestRadius =
    //       psCone->radius_1>psCone->radius_2?psCone->radius_1:psCone->radius_2;
    //     sMin.x = psCone->center_1.x-largestRadius;
    //     sMin.y = psCone->center_1.y-largestRadius;
    //     sMin.z = psCone->center_1.z;
    //     sMax.x = psCone->center_2.x+largestRadius;
    //     sMax.y = psCone->center_2.y+largestRadius;
    //     sMax.z = psCone->center_2.z;

    DGNPoint sMin = {0.0, 0.0, 0.0};
    DGNPoint sMax = {0.0, 0.0, 0.0};
    DGNWriteBounds(psDGN, psCore, &sMin, &sMax);

    return (DGNElemCore *)psCone;
}

/************************************************************************/
/*                         DGNCreateTextElem()                          */
/************************************************************************/

/**
 * Create text element.
 *
 * The newly created element will still need to be written to file using
 * DGNWriteElement(). Also the level and other core values will be defaulted.
 * Use DGNUpdateElemCore() on the element before writing to set these values.
 *
 * @param hDGN the file on which the element will eventually be written.
 * @param pszText the string of text.
 * @param nFontId microstation font id for the text.  1 may be used as default.
 * @param nJustification text justification.  One of DGNJ_LEFT_TOP,
 * DGNJ_LEFT_CENTER, DGNJ_LEFT_BOTTOM, DGNJ_CENTER_TOP, DGNJ_CENTER_CENTER,
 * DGNJ_CENTER_BOTTOM, DGNJ_RIGHT_TOP, DGNJ_RIGHT_CENTER, DGNJ_RIGHT_BOTTOM.
 * @param dfLengthMult character width in master units.
 * @param dfHeightMult character height in master units.
 * @param dfRotation Counterclockwise text rotation in degrees.
 * @param panQuaternion 3D orientation quaternion (NULL to use rotation).
 * @param dfOriginX Text origin (X).
 * @param dfOriginY Text origin (Y).
 * @param dfOriginZ Text origin (Z).
 *
 * @return the new element (DGNElemText) or NULL on failure.
 */

DGNElemCore *DGNCreateTextElem(DGNHandle hDGN, const char *pszText, int nFontId,
                               int nJustification, double dfLengthMult,
                               double dfHeightMult, double dfRotation,
                               int *panQuaternion, double dfOriginX,
                               double dfOriginY, double dfOriginZ)

{
    DGNInfo *psDGN = (DGNInfo *)hDGN;

    DGNLoadTCB(hDGN);

    /* -------------------------------------------------------------------- */
    /*      Allocate element.                                               */
    /* -------------------------------------------------------------------- */
    DGNElemText *psText =
        (DGNElemText *)CPLCalloc(sizeof(DGNElemText) + strlen(pszText), 1);
    DGNElemCore *psCore = &(psText->core);

    DGNInitializeElemCore(hDGN, psCore);
    psCore->stype = DGNST_TEXT;
    psCore->type = DGNT_TEXT;

    /* -------------------------------------------------------------------- */
    /*      Set arc specific information in the structure.                  */
    /* -------------------------------------------------------------------- */
    psText->font_id = nFontId;
    psText->justification = nJustification;
    psText->length_mult = dfLengthMult;
    psText->height_mult = dfHeightMult;
    psText->rotation = dfRotation;
    psText->origin.x = dfOriginX;
    psText->origin.y = dfOriginY;
    psText->origin.z = dfOriginZ;
    strcpy(psText->string, pszText);

    /* -------------------------------------------------------------------- */
    /*      Setup Raw data for the text specific portion.                   */
    /* -------------------------------------------------------------------- */
    if (psDGN->dimension == 2)
        psCore->raw_bytes = 60 + static_cast<int>(strlen(pszText));
    else
        psCore->raw_bytes = 76 + static_cast<int>(strlen(pszText));

    psCore->raw_bytes += (psCore->raw_bytes % 2);
    psCore->raw_data = (unsigned char *)CPLCalloc(psCore->raw_bytes, 1);

    psCore->raw_data[36] = (unsigned char)nFontId;
    psCore->raw_data[37] = (unsigned char)nJustification;

    GInt32 nIntValue =
        static_cast<int>(dfLengthMult * 1000.0 / (psDGN->scale * 6.0) + 0.5);
    DGN_WRITE_INT32(nIntValue, psCore->raw_data + 38);

    nIntValue = (int)(dfHeightMult * 1000.0 / (psDGN->scale * 6.0) + 0.5);
    DGN_WRITE_INT32(nIntValue, psCore->raw_data + 42);

    GInt32 nBase = 0;

    if (psDGN->dimension == 2)
    {
        nIntValue = (int)(dfRotation * 360000.0);
        DGN_WRITE_INT32(nIntValue, psCore->raw_data + 46);

        DGNInverseTransformPointToInt(psDGN, &(psText->origin),
                                      psCore->raw_data + 50);

        nBase = 58;
    }
    else
    {
        int anQuaternion[4];

        if (panQuaternion == nullptr)
            DGNRotationToQuaternion(dfRotation, anQuaternion);
        else
            memcpy(anQuaternion, panQuaternion, sizeof(int) * 4);

        DGN_WRITE_INT32(anQuaternion[0], psCore->raw_data + 46);
        DGN_WRITE_INT32(anQuaternion[1], psCore->raw_data + 50);
        DGN_WRITE_INT32(anQuaternion[2], psCore->raw_data + 54);
        DGN_WRITE_INT32(anQuaternion[3], psCore->raw_data + 58);

        DGNInverseTransformPointToInt(psDGN, &(psText->origin),
                                      psCore->raw_data + 62);
        nBase = 74;
    }

    psCore->raw_data[nBase] = (unsigned char)strlen(pszText);
    psCore->raw_data[nBase + 1] = 0; /* edflds? */
    memcpy(psCore->raw_data + nBase + 2, pszText, strlen(pszText));

    /* -------------------------------------------------------------------- */
    /*      Set the core raw data, including the bounds.                    */
    /*                                                                      */
    /*      Code contributed by Mart Kelder.                                */
    /* -------------------------------------------------------------------- */
    DGNUpdateElemCoreExtended(hDGN, psCore);

    // calculate bounds if rotation is 0
    DGNPoint sMin = {dfOriginX, dfOriginY, 0.0};
    DGNPoint sMax = {dfOriginX + dfLengthMult * strlen(pszText),
                     dfOriginY + dfHeightMult, 0.0};

#if 0
    //calculate rotated bounding box coordinates
    const double length = sMax.x-sMin.x;
    const double height = sMax.y-sMin.y;
    const double diagonal=sqrt(length*length+height*height);
    const DGNPoint sLowLeft = { sMin.x, sMin.y, 0.0 };
    const DGNPoint sLowRight = {
       sMin.x+cos(psText->rotation*M_PI/180.0)*length,
       sMin.y+sin(psText->rotation*M_PI/180.0)*length,
       0.0
    };
    const DGNPoint sUpRight = {
        sMin.x+cos((psText->rotation*M_PI/180.0)+atan(height/length))*diagonal,
        sMin.y+sin((psText->rotation*M_PI/180.0)+atan(height/length))*diagonal,
        0.0
    };
    const DGNPoint sUpLeft = {
        sMin.x+cos((psText->rotation+90.0)*M_PI/180.0)*height,
        sMin.y+sin((psText->rotation+90.0)*M_PI/180.0)*height,
        0.0
    };

    //calculate new values for bounding box
    sMin.x = std::min(sLowLeft.x,
                      std::min(sLowRight.x, std::min(sUpLeft.x, sUpRight.x)));
    sMin.y = std::min(sLowLeft.y,
                      std::min(sLowRight.y, std::min(sUpLeft.y, sUpRight.y)));
    sMax.x = std::max(sLowLeft.x,
                      std::max(sLowRight.x, std::max(sUpLeft.x, sUpRight.x)));
    sMax.y = std::max(sLowLeft.y,
                      std::max(sLowRight.y, std::max(sUpLeft.y, sUpRight.y)));
#endif
    sMin.x = dfOriginX - dfLengthMult * strlen(pszText);
    sMin.y = dfOriginY - dfHeightMult;
    sMin.z = 0.0;
    sMax.x = dfOriginX + dfLengthMult * strlen(pszText);
    sMax.y = dfOriginY + dfHeightMult;
    sMax.z = 0.0;

    DGNWriteBounds(psDGN, psCore, &sMin, &sMax);

    return (DGNElemCore *)psText;
}

/************************************************************************/
/*                      DGNCreateColorTableElem()                       */
/************************************************************************/

/**
 * Create color table element.
 *
 * Creates a color table element with the indicated color table.
 *
 * Note that color table elements are actually of type DGNT_GROUP_DATA(5)
 * and always on level 1.  Do not alter the level with DGNUpdateElemCore()
 * or the element will essentially be corrupt.
 *
 * The newly created element will still need to be written to file using
 * DGNWriteElement(). Also the level and other core values will be defaulted.
 * Use DGNUpdateElemCore() on the element before writing to set these values.
 *
 * @param hDGN the file to which the element will eventually be written.
 * @param nScreenFlag the screen to which the color table applies
 * (0 = left, 1 = right).
 * @param abyColorInfo array of 256 color entries. The first is
 * the background color.
 *
 * @return the new element (DGNElemColorTable) or NULL on failure.
 */

DGNElemCore *DGNCreateColorTableElem(DGNHandle hDGN, int nScreenFlag,
                                     GByte abyColorInfo[256][3])

{
    /* -------------------------------------------------------------------- */
    /*      Allocate element.                                               */
    /* -------------------------------------------------------------------- */
    DGNElemColorTable *psCT =
        (DGNElemColorTable *)CPLCalloc(sizeof(DGNElemColorTable), 1);
    DGNElemCore *psCore = &(psCT->core);

    DGNInitializeElemCore(hDGN, psCore);
    psCore->stype = DGNST_COLORTABLE;
    psCore->type = DGNT_GROUP_DATA;
    psCore->level = DGN_GDL_COLOR_TABLE;

    /* -------------------------------------------------------------------- */
    /*      Set colortable specific information in the structure.           */
    /* -------------------------------------------------------------------- */
    psCT->screen_flag = nScreenFlag;
    memcpy(psCT->color_info, abyColorInfo, 768);

    /* -------------------------------------------------------------------- */
    /*      Setup Raw data for the color table specific portion.            */
    /* -------------------------------------------------------------------- */
    psCore->raw_bytes = 41 + (256 - 1) * 3;
    psCore->raw_data = (unsigned char *)CPLCalloc(psCore->raw_bytes, 1);

    psCore->raw_data[36] = (unsigned char)(nScreenFlag % 256);
    psCore->raw_data[37] = (unsigned char)(nScreenFlag / 256);

    memcpy(psCore->raw_data + 38, abyColorInfo[255], 3);
    memcpy(psCore->raw_data + 41, abyColorInfo, (256 - 1) * 3);

    /* -------------------------------------------------------------------- */
    /*      Set the core raw data.                                          */
    /* -------------------------------------------------------------------- */
    DGNUpdateElemCoreExtended(hDGN, psCore);

    return (DGNElemCore *)psCT;
}

/************************************************************************/
/*                     DGNCreateComplexHeaderElem()                     */
/************************************************************************/

/**
 * Create complex chain/shape header.
 *
 * The newly created element will still need to be written to file using
 * DGNWriteElement(). Also the level and other core values will be defaulted.
 * Use DGNUpdateElemCore() on the element before writing to set these values.
 *
 * The nTotLength is the sum of the size of all elements in the complex
 * group plus 5.  The DGNCreateComplexHeaderFromGroup() can be used to build
 * a complex element from the members more conveniently.
 *
 * @param hDGN the file on which the element will be written.
 * @param nType DGNT_COMPLEX_CHAIN_HEADER or DGNT_COMPLEX_SHAPE_HEADER.
 * depending on whether the list is open or closed (last point equal to last)
 * or if the object represents a surface or a solid.
 * @param nTotLength the value of the totlength field in the element.
 * @param nNumElems the number of elements in the complex group not including
 * the header element.
 *
 * @return the new element (DGNElemComplexHeader) or NULL on failure.
 */
DGNElemCore *DGNCreateComplexHeaderElem(DGNHandle hDGN, int nType,
                                        int nTotLength, int nNumElems)
{
    unsigned char abyRawZeroLinkage[8] = {0, 0, 0, 0, 0, 0, 0, 0};

    CPLAssert(nType == DGNT_COMPLEX_CHAIN_HEADER ||
              nType == DGNT_COMPLEX_SHAPE_HEADER);

    DGNLoadTCB(hDGN);

    /* -------------------------------------------------------------------- */
    /*      Allocate element.                                               */
    /* -------------------------------------------------------------------- */
    DGNElemComplexHeader *psCH =
        (DGNElemComplexHeader *)CPLCalloc(sizeof(DGNElemComplexHeader), 1);
    DGNElemCore *psCore = &(psCH->core);

    DGNInitializeElemCore(hDGN, psCore);
    psCore->complex = TRUE;
    psCore->stype = DGNST_COMPLEX_HEADER;
    psCore->type = nType;

    /* -------------------------------------------------------------------- */
    /*      Set complex header specific information in the structure.       */
    /* -------------------------------------------------------------------- */
    psCH->totlength = nTotLength - 4;
    psCH->numelems = nNumElems;
    psCH->surftype = 0;
    psCH->boundelms = 0;

    /* -------------------------------------------------------------------- */
    /*      Setup Raw data for the complex specific portion.                */
    /* -------------------------------------------------------------------- */
    psCore->raw_bytes = 40;
    psCore->raw_data = (unsigned char *)CPLCalloc(psCore->raw_bytes, 1);

    psCore->raw_data[36] = (unsigned char)((nTotLength - 4) % 256);
    psCore->raw_data[37] = (unsigned char)((nTotLength - 4) / 256);
    psCore->raw_data[38] = (unsigned char)(nNumElems % 256);
    psCore->raw_data[39] = (unsigned char)(nNumElems / 256);

    /* -------------------------------------------------------------------- */
    /*      Set the core raw data.                                          */
    /* -------------------------------------------------------------------- */
    DGNUpdateElemCoreExtended(hDGN, psCore);

    /* -------------------------------------------------------------------- */
    /*      Elements have to be at least 48 bytes long, so we have to       */
    /*      add a dummy bit of attribute data to fill out the length.       */
    /* -------------------------------------------------------------------- */
    DGNAddRawAttrLink(hDGN, psCore, 8, abyRawZeroLinkage);

    return (DGNElemCore *)psCH;
}

/************************************************************************/
/*                  DGNCreateComplexHeaderFromGroup()                   */
/************************************************************************/

/**
 * Create complex chain/shape header.
 *
 * This function is similar to DGNCreateComplexHeaderElem(), but it takes
 * care of computing the total size of the set of elements being written,
 * and collecting the bounding extents.  It also takes care of some other
 * convenience issues, like marking all the member elements as complex, and
 * setting the level based on the level of the member elements.
 *
 * @param hDGN the file on which the element will be written.
 * @param nType DGNT_COMPLEX_CHAIN_HEADER or DGNT_COMPLEX_SHAPE_HEADER.
 * depending on whether the list is open or closed (last point equal to last)
 * or if the object represents a surface or a solid.
 * @param nNumElems the number of elements in the complex group not including
 * the header element.
 * @param papsElems array of pointers to nNumElems elements in the complex
 * group.  Some updates may be made to these elements.
 *
 * @return the new element (DGNElemComplexHeader) or NULL on failure.
 */

DGNElemCore *DGNCreateComplexHeaderFromGroup(DGNHandle hDGN, int nType,
                                             int nNumElems,
                                             DGNElemCore **papsElems)

{
    DGNLoadTCB(hDGN);

    if (nNumElems < 1 || papsElems == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Need at least one element to form a complex group.");
        return nullptr;
    }

    /* -------------------------------------------------------------------- */
    /*      Collect the total size, and bounds.                             */
    /* -------------------------------------------------------------------- */
    int nTotalLength = 5;
    const int nLevel = papsElems[0]->level;
    DGNPoint sMin = {0.0, 0.0, 0.0};
    DGNPoint sMax = {0.0, 0.0, 0.0};

    for (int i = 0; i < nNumElems; i++)
    {
        nTotalLength += papsElems[i]->raw_bytes / 2;

        papsElems[i]->complex = TRUE;
        papsElems[i]->raw_data[0] |= 0x80;

        if (papsElems[i]->level != nLevel)
        {
            CPLError(CE_Warning, CPLE_AppDefined,
                     "Not all level values matching in a complex set group!");
        }

        DGNPoint sThisMin = {0.0, 0.0, 0.0};
        DGNPoint sThisMax = {0.0, 0.0, 0.0};

        DGNGetElementExtents(hDGN, papsElems[i], &sThisMin, &sThisMax);
        if (i == 0)
        {
            sMin = sThisMin;
            sMax = sThisMax;
        }
        else
        {
            sMin.x = std::min(sMin.x, sThisMin.x);
            sMin.y = std::min(sMin.y, sThisMin.y);
            sMin.z = std::min(sMin.z, sThisMin.z);
            sMax.x = std::max(sMax.x, sThisMax.x);
            sMax.y = std::max(sMax.y, sThisMax.y);
            sMax.z = std::max(sMax.z, sThisMax.z);
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Create the corresponding complex header.                        */
    /* -------------------------------------------------------------------- */
    DGNElemCore *psCH =
        DGNCreateComplexHeaderElem(hDGN, nType, nTotalLength, nNumElems);
    DGNUpdateElemCore(hDGN, psCH, papsElems[0]->level, psCH->graphic_group,
                      psCH->color, psCH->weight, psCH->style);

    DGNWriteBounds((DGNInfo *)hDGN, psCH, &sMin, &sMax);

    return psCH;
}

/************************************************************************/
/*                     DGNCreateSolidHeaderElem()                       */
/************************************************************************/

/**
 * Create 3D solid/surface.
 *
 * The newly created element will still need to be written to file using
 * DGNWriteElement(). Also the level and other core values will be defaulted.
 * Use DGNUpdateElemCore() on the element before writing to set these values.
 *
 * The nTotLength is the sum of the size of all elements in the solid
 * group plus 6.  The DGNCreateSolidHeaderFromGroup() can be used to build
 * a solid element from the members more conveniently.
 *
 * @param hDGN the file on which the element will be written.
 * @param nType DGNT_3DSURFACE_HEADER or DGNT_3DSOLID_HEADER.
 * @param nSurfType the surface/solid type, one of DGNSUT_* or DGNSOT_*.
 * @param nBoundElems the number of elements in each boundary.
 * @param nTotLength the value of the totlength field in the element.
 * @param nNumElems the number of elements in the solid not including
 * the header element.
 *
 * @return the new element (DGNElemComplexHeader) or NULL on failure.
 */
DGNElemCore *DGNCreateSolidHeaderElem(DGNHandle hDGN, int nType, int nSurfType,
                                      int nBoundElems, int nTotLength,
                                      int nNumElems)
{
    CPLAssert(nType == DGNT_3DSURFACE_HEADER || nType == DGNT_3DSOLID_HEADER);

    DGNLoadTCB(hDGN);

    /* -------------------------------------------------------------------- */
    /*      Allocate element.                                               */
    /* -------------------------------------------------------------------- */
    DGNElemComplexHeader *psCH =
        (DGNElemComplexHeader *)CPLCalloc(sizeof(DGNElemComplexHeader), 1);
    DGNElemCore *psCore = &(psCH->core);

    DGNInitializeElemCore(hDGN, psCore);
    psCore->complex = TRUE;
    psCore->stype = DGNST_COMPLEX_HEADER;
    psCore->type = nType;

    /* -------------------------------------------------------------------- */
    /*      Set solid header specific information in the structure.         */
    /* -------------------------------------------------------------------- */
    psCH->totlength = nTotLength - 4;
    psCH->numelems = nNumElems;
    psCH->surftype = nSurfType;
    psCH->boundelms = nBoundElems;

    /* -------------------------------------------------------------------- */
    /*      Setup Raw data for the solid specific portion.                  */
    /* -------------------------------------------------------------------- */
    psCore->raw_bytes = 42;

    psCore->raw_data = (unsigned char *)CPLCalloc(psCore->raw_bytes, 1);

    psCore->raw_data[36] = (unsigned char)((nTotLength - 4) % 256);
    psCore->raw_data[37] = (unsigned char)((nTotLength - 4) / 256);
    psCore->raw_data[38] = (unsigned char)(nNumElems % 256);
    psCore->raw_data[39] = (unsigned char)(nNumElems / 256);
    psCore->raw_data[40] = (unsigned char)psCH->surftype;
    psCore->raw_data[41] = (unsigned char)psCH->boundelms - 1;

    /* -------------------------------------------------------------------- */
    /*      Set the core raw data.                                          */
    /* -------------------------------------------------------------------- */
    DGNUpdateElemCoreExtended(hDGN, psCore);

    /* -------------------------------------------------------------------- */
    /*      Elements have to be at least 48 bytes long, so we have to       */
    /*      add a dummy bit of attribute data to fill out the length.       */
    /* -------------------------------------------------------------------- */
    unsigned char abyRawZeroLinkage[8] = {0, 0, 0, 0, 0, 0, 0, 0};
    DGNAddRawAttrLink(hDGN, psCore, 8, abyRawZeroLinkage);

    return (DGNElemCore *)psCH;
}

/************************************************************************/
/*                  DGNCreateSolidHeaderFromGroup()                     */
/************************************************************************/

/**
 * Create 3D solid/surface header.
 *
 * This function is similar to DGNCreateSolidHeaderElem(), but it takes
 * care of computing the total size of the set of elements being written,
 * and collecting the bounding extents.  It also takes care of some other
 * convenience issues, like marking all the member elements as complex, and
 * setting the level based on the level of the member elements.
 *
 * @param hDGN the file on which the element will be written.
 * @param nType DGNT_3DSURFACE_HEADER or DGNT_3DSOLID_HEADER.
 * @param nSurfType the surface/solid type, one of DGNSUT_* or DGNSOT_*.
 * @param nBoundElems the number of boundary elements.
 * @param nNumElems the number of elements in the solid not including
 * the header element.
 * @param papsElems array of pointers to nNumElems elements in the solid.
 * Some updates may be made to these elements.
 *
 * @return the new element (DGNElemComplexHeader) or NULL on failure.
 */

DGNElemCore *DGNCreateSolidHeaderFromGroup(DGNHandle hDGN, int nType,
                                           int nSurfType, int nBoundElems,
                                           int nNumElems,
                                           DGNElemCore **papsElems)

{
    DGNLoadTCB(hDGN);

    if (nNumElems < 1 || papsElems == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Need at least one element to form a solid.");
        return nullptr;
    }

    /* -------------------------------------------------------------------- */
    /*      Collect the total size, and bounds.                             */
    /* -------------------------------------------------------------------- */
    const int nLevel = papsElems[0]->level;
    int nTotalLength = 6;
    DGNPoint sMin = {0.0, 0.0, 0.0};
    DGNPoint sMax = {0.0, 0.0, 0.0};

    for (int i = 0; i < nNumElems; i++)
    {
        nTotalLength += papsElems[i]->raw_bytes / 2;

        papsElems[i]->complex = TRUE;
        papsElems[i]->raw_data[0] |= 0x80;

        if (papsElems[i]->level != nLevel)
        {
            CPLError(CE_Warning, CPLE_AppDefined,
                     "Not all level values matching in a complex set group!");
        }

        DGNPoint sThisMin = {0.0, 0.0, 0.0};
        DGNPoint sThisMax = {0.0, 0.0, 0.0};
        DGNGetElementExtents(hDGN, papsElems[i], &sThisMin, &sThisMax);
        if (i == 0)
        {
            sMin = sThisMin;
            sMax = sThisMax;
        }
        else
        {
            sMin.x = std::min(sMin.x, sThisMin.x);
            sMin.y = std::min(sMin.y, sThisMin.y);
            sMin.z = std::min(sMin.z, sThisMin.z);
            sMax.x = std::max(sMax.x, sThisMax.x);
            sMax.y = std::max(sMax.y, sThisMax.y);
            sMax.z = std::max(sMax.z, sThisMax.z);
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Create the corresponding solid header.                          */
    /* -------------------------------------------------------------------- */
    DGNElemCore *psCH = DGNCreateSolidHeaderElem(
        hDGN, nType, nSurfType, nBoundElems, nTotalLength, nNumElems);
    DGNUpdateElemCore(hDGN, psCH, papsElems[0]->level, psCH->graphic_group,
                      psCH->color, psCH->weight, psCH->style);

    DGNWriteBounds((DGNInfo *)hDGN, psCH, &sMin, &sMax);

    return psCH;
}

/************************************************************************/
/*                      DGNCreateCellHeaderElem()                       */
/************************************************************************/

DGNElemCore CPL_DLL *
DGNCreateCellHeaderElem(DGNHandle hDGN, int nTotLength, const char *pszName,
                        short nClass, short *panLevels, DGNPoint *psRangeLow,
                        DGNPoint *psRangeHigh, DGNPoint *psOrigin,
                        double dfXScale, double dfYScale, double dfRotation)

/**
 * Create cell header.
 *
 * The newly created element will still need to be written to file using
 * DGNWriteElement(). Also the level and other core values will be defaulted.
 * Use DGNUpdateElemCore() on the element before writing to set these values.
 *
 * Generally speaking the function DGNCreateCellHeaderFromGroup() should
 * be used instead of this function.
 *
 * @param hDGN the file handle on which the element is to be written.
 * @param nTotLength total length of cell in words not including the 38 bytes
 * of the cell header that occur before the totlength indicator.
 * @param nClass the class value for the cell.
 * @param panLevels an array of shorts holding the bit mask of levels in
 * effect for this cell.  This array should contain 4 shorts (64 bits).
 * @param psRangeLow the cell diagonal origin in original cell file
 * coordinates.
 * @param psRangeHigh the cell diagonal top left corner in original cell file
 * coordinates.
 * @param psOrigin the origin of the cell in output file coordinates.
 * @param dfXScale the amount of scaling applied in the X dimension in
 * mapping from cell file coordinates to output file coordinates.
 * @param dfYScale the amount of scaling applied in the Y dimension in
 * mapping from cell file coordinates to output file coordinates.
 * @param dfRotation the amount of rotation (degrees counterclockwise) in
 * mapping from cell coordinates to output file coordinates.
 *
 * @return the new element (DGNElemCellHeader) or NULL on failure.
 */

{
    DGNInfo *psInfo = (DGNInfo *)hDGN;

    DGNLoadTCB(hDGN);

    /* -------------------------------------------------------------------- */
    /*      Allocate element.                                               */
    /* -------------------------------------------------------------------- */
    DGNElemCellHeader *psCH =
        (DGNElemCellHeader *)CPLCalloc(sizeof(DGNElemCellHeader), 1);
    DGNElemCore *psCore = &(psCH->core);

    DGNInitializeElemCore(hDGN, psCore);
    psCore->stype = DGNST_CELL_HEADER;
    psCore->type = DGNT_CELL_HEADER;

    /* -------------------------------------------------------------------- */
    /*      Set complex header specific information in the structure.       */
    /* -------------------------------------------------------------------- */
    psCH->totlength = nTotLength;

    /* -------------------------------------------------------------------- */
    /*      Setup Raw data for the cell header specific portion.            */
    /* -------------------------------------------------------------------- */
    if (psInfo->dimension == 2)
        psCore->raw_bytes = 92;
    else
        psCore->raw_bytes = 124;
    psCore->raw_data = (unsigned char *)CPLCalloc(psCore->raw_bytes, 1);

    psCore->raw_data[36] = (unsigned char)(nTotLength % 256);
    psCore->raw_data[37] = (unsigned char)(nTotLength / 256);

    DGNAsciiToRad50(pszName, (unsigned short *)(psCore->raw_data + 38));
    if (strlen(pszName) > 3)
        DGNAsciiToRad50(pszName + 3, (unsigned short *)(psCore->raw_data + 40));

    psCore->raw_data[42] = (unsigned char)(nClass % 256);
    psCore->raw_data[43] = (unsigned char)(nClass / 256);

    memcpy(psCore->raw_data + 44, panLevels, 8);

    if (psInfo->dimension == 2)
    {
        DGNPointToInt(psInfo, psRangeLow, psCore->raw_data + 52);
        DGNPointToInt(psInfo, psRangeHigh, psCore->raw_data + 60);

        DGNInverseTransformPointToInt(psInfo, psOrigin, psCore->raw_data + 84);
    }
    else
    {
        DGNPointToInt(psInfo, psRangeLow, psCore->raw_data + 52);
        DGNPointToInt(psInfo, psRangeHigh, psCore->raw_data + 64);

        DGNInverseTransformPointToInt(psInfo, psOrigin, psCore->raw_data + 112);
    }

    /* -------------------------------------------------------------------- */
    /*      Produce a transformation matrix that approximates the           */
    /*      requested scaling and rotation.                                 */
    /* -------------------------------------------------------------------- */
    if (psInfo->dimension == 2)
    {
        long anTrans[4];
        double cos_a = cos(-dfRotation * M_PI / 180.0);
        double sin_a = sin(-dfRotation * M_PI / 180.0);

        anTrans[0] = (long)(cos_a * dfXScale * 214748);
        anTrans[1] = (long)(sin_a * dfYScale * 214748);
        anTrans[2] = (long)(-sin_a * dfXScale * 214748);
        anTrans[3] = (long)(cos_a * dfYScale * 214748);

        DGN_WRITE_INT32(anTrans[0], psCore->raw_data + 68);
        DGN_WRITE_INT32(anTrans[1], psCore->raw_data + 72);
        DGN_WRITE_INT32(anTrans[2], psCore->raw_data + 76);
        DGN_WRITE_INT32(anTrans[3], psCore->raw_data + 80);
    }
    else
    {
        long anTrans[9];

        // NOTE: This is still just rotation in the plane
        double cos_a = cos(-dfRotation * M_PI / 180.0);
        double sin_a = sin(-dfRotation * M_PI / 180.0);
        double dfZScale = 1.0;  // Should we get this from somewhere?

        anTrans[0] = (long)(cos_a * dfXScale * 214748);
        anTrans[1] = (long)(sin_a * dfYScale * 214748);
        anTrans[2] = (long)(sin_a * dfZScale * 214748);

        anTrans[3] = (long)(-sin_a * dfXScale * 214748);
        anTrans[4] = (long)(cos_a * dfYScale * 214748);
        anTrans[5] = (long)(sin_a * dfZScale * 214748);

        anTrans[6] = (long)(-sin_a * dfXScale * 214748);
        anTrans[7] = (long)(-sin_a * dfYScale * 214748);
        anTrans[8] = (long)(cos_a * dfZScale * 214748);

        DGN_WRITE_INT32(anTrans[0], psCore->raw_data + 76);
        DGN_WRITE_INT32(anTrans[1], psCore->raw_data + 80);
        DGN_WRITE_INT32(anTrans[2], psCore->raw_data + 84);
        DGN_WRITE_INT32(anTrans[3], psCore->raw_data + 88);
        DGN_WRITE_INT32(anTrans[4], psCore->raw_data + 92);
        DGN_WRITE_INT32(anTrans[5], psCore->raw_data + 96);
        DGN_WRITE_INT32(anTrans[6], psCore->raw_data + 100);
        DGN_WRITE_INT32(anTrans[7], psCore->raw_data + 104);
        DGN_WRITE_INT32(anTrans[8], psCore->raw_data + 108);
    }

    /* -------------------------------------------------------------------- */
    /*      Set the core raw data.                                          */
    /* -------------------------------------------------------------------- */
    DGNUpdateElemCoreExtended(hDGN, psCore);

    return (DGNElemCore *)psCH;
}

/************************************************************************/
/*                           DGNPointToInt()                            */
/*                                                                      */
/*      Convert a point directly to integer coordinates and write to    */
/*      the indicate memory location.  Intended to be used for the      */
/*      range section of the CELL HEADER.                               */
/************************************************************************/

static void DGNPointToInt(DGNInfo *psDGN, DGNPoint *psPoint,
                          unsigned char *pabyTarget)

{
    double adfCT[3] = {psPoint->x, psPoint->y, psPoint->z};

    const int nIter = std::min(3, psDGN->dimension);
    for (int i = 0; i < nIter; i++)
    {
        GInt32 nCTI = static_cast<GInt32>(
            std::max(-2147483647.0, std::min(2147483647.0, adfCT[i])));
        unsigned char abyCTI[4];
        memcpy(abyCTI, &nCTI, sizeof(GInt32));

#ifdef WORDS_BIGENDIAN
        pabyTarget[i * 4 + 0] = abyCTI[1];
        pabyTarget[i * 4 + 1] = abyCTI[0];
        pabyTarget[i * 4 + 2] = abyCTI[3];
        pabyTarget[i * 4 + 3] = abyCTI[2];
#else
        pabyTarget[i * 4 + 3] = abyCTI[1];
        pabyTarget[i * 4 + 2] = abyCTI[0];
        pabyTarget[i * 4 + 1] = abyCTI[3];
        pabyTarget[i * 4 + 0] = abyCTI[2];
#endif
    }
}

/************************************************************************/
/*                    DGNCreateCellHeaderFromGroup()                    */
/************************************************************************/

/**
 * Create cell header from a group of elements.
 *
 * The newly created element will still need to be written to file using
 * DGNWriteElement(). Also the level and other core values will be defaulted.
 * Use DGNUpdateElemCore() on the element before writing to set these values.
 *
 * This function will compute the total length, bounding box, and diagonal
 * range values from the set of provided elements.  Note that the proper
 * diagonal range values will only be written if 1.0 is used for the x and y
 * scale values, and 0.0 for the rotation.  Use of other values will result
 * in incorrect scaling handles being presented to the user in Microstation
 * when they select the element.
 *
 * @param hDGN the file handle on which the element is to be written.
 * @param nClass the class value for the cell.
 * @param panLevels an array of shorts holding the bit mask of levels in
 * effect for this cell.  This array should contain 4 shorts (64 bits).
 * This array would normally be passed in as NULL, and the function will
 * build a mask from the passed list of elements.
 * @param psOrigin the origin of the cell in output file coordinates.
 * @param dfXScale the amount of scaling applied in the X dimension in
 * mapping from cell file coordinates to output file coordinates.
 * @param dfYScale the amount of scaling applied in the Y dimension in
 * mapping from cell file coordinates to output file coordinates.
 * @param dfRotation the amount of rotation (degrees counterclockwise) in
 * mapping from cell coordinates to output file coordinates.
 *
 * @return the new element (DGNElemCellHeader) or NULL on failure.
 */

DGNElemCore *DGNCreateCellHeaderFromGroup(DGNHandle hDGN, const char *pszName,
                                          short nClass, short *panLevels,
                                          int nNumElems,
                                          DGNElemCore **papsElems,
                                          DGNPoint *psOrigin, double dfXScale,
                                          double dfYScale, double dfRotation)

{
    DGNInfo *psInfo = (DGNInfo *)hDGN;

    DGNLoadTCB(hDGN);

    if (nNumElems < 1 || papsElems == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Need at least one element to form a cell.");
        return nullptr;
    }

    /* -------------------------------------------------------------------- */
    /*      Collect the total size, and bounds.                             */
    /* -------------------------------------------------------------------- */
    int nTotalLength = psInfo->dimension == 2 ? 27 : 43;
    // nLevel = papsElems[0]->level;x
    DGNPoint sMin = {0.0, 0.0, 0.0};
    DGNPoint sMax = {0.0, 0.0, 0.0};
    unsigned char abyLevelsOccurring[8] = {0, 0, 0, 0, 0, 0, 0, 0};

    for (int i = 0; i < nNumElems; i++)
    {
        nTotalLength += papsElems[i]->raw_bytes / 2;

        /* mark as complex */
        papsElems[i]->complex = TRUE;
        papsElems[i]->raw_data[0] |= 0x80;

        /* establish level */
        int nLevel = papsElems[i]->level;
        nLevel = std::max(1, std::min(nLevel, 64));
        abyLevelsOccurring[(nLevel - 1) >> 3] |= (0x1 << ((nLevel - 1) & 0x7));

        DGNPoint sThisMin = {0.0, 0.0, 0.0};
        DGNPoint sThisMax = {0.0, 0.0, 0.0};
        DGNGetElementExtents(hDGN, papsElems[i], &sThisMin, &sThisMax);
        if (i == 0)
        {
            sMin = sThisMin;
            sMax = sThisMax;
        }
        else
        {
            sMin.x = std::min(sMin.x, sThisMin.x);
            sMin.y = std::min(sMin.y, sThisMin.y);
            sMin.z = std::min(sMin.z, sThisMin.z);
            sMax.x = std::max(sMax.x, sThisMax.x);
            sMax.y = std::max(sMax.y, sThisMax.y);
            sMax.z = std::max(sMax.z, sThisMax.z);
        }
    }

/* -------------------------------------------------------------------- */
/*      It seems that the range needs to be adjusted according to       */
/*      the rotation and scaling.                                       */
/*                                                                      */
/*      NOTE: Omitting code ... this is already done in                 */
/*      DGNInverseTransformPoint() called from DGNWriteBounds().        */
/* -------------------------------------------------------------------- */
#ifdef notdef
    sMin.x -= psOrigin->x;
    sMin.y -= psOrigin->y;
    sMin.z -= psOrigin->z;
    sMax.x -= psOrigin->x;
    sMax.y -= psOrigin->y;
    sMax.z -= psOrigin->z;

    sMin.x /= ((DGNInfo *)hDGN)->scale;
    sMin.y /= ((DGNInfo *)hDGN)->scale;
    sMin.z /= ((DGNInfo *)hDGN)->scale;
    sMax.x /= ((DGNInfo *)hDGN)->scale;
    sMax.y /= ((DGNInfo *)hDGN)->scale;
    sMax.z /= ((DGNInfo *)hDGN)->scale;
#endif

    /* -------------------------------------------------------------------- */
    /*      Create the corresponding cell header.                           */
    /* -------------------------------------------------------------------- */
    if (panLevels == nullptr)
        panLevels = (short *)abyLevelsOccurring + 0;

    DGNElemCore *psCH = DGNCreateCellHeaderElem(
        hDGN, nTotalLength, pszName, nClass, panLevels, &sMin, &sMax, psOrigin,
        dfXScale, dfYScale, dfRotation);
    DGNWriteBounds((DGNInfo *)hDGN, psCH, &sMin, &sMax);

    return psCH;
}

/************************************************************************/
/*                            DGNAddMSLink()                            */
/************************************************************************/

/**
 * Add a database link to element.
 *
 * The target element must already have raw_data loaded, and it will be
 * resized (see DGNResizeElement()) as needed for the new attribute data.
 * Note that the element is not written to disk immediate.  Use
 * DGNWriteElement() for that.
 *
 * @param hDGN the file to which the element corresponds.
 * @param psElement the element being updated.
 * @param nLinkageType link type (DGNLT_*).  Usually one of DGNLT_DMRS,
 * DGNLT_INFORMIX, DGNLT_ODBC, DGNLT_ORACLE, DGNLT_RIS, DGNLT_SYBASE,
 * or DGNLT_XBASE.
 * @param nEntityNum indicator of the table referenced on target database.
 * @param nMSLink indicator of the record referenced on target table.
 *
 * @return -1 on failure, or the link index.
 */

int DGNAddMSLink(DGNHandle hDGN, DGNElemCore *psElement, int nLinkageType,
                 int nEntityNum, int nMSLink)

{
    unsigned char abyLinkage[32] = {};
    int nLinkageSize = 0;

    if (nLinkageType == DGNLT_DMRS)
    {
        nLinkageSize = 8;
        abyLinkage[0] = 0x00;
        abyLinkage[1] = 0x00;
        abyLinkage[2] = (GByte)(nEntityNum % 256);
        abyLinkage[3] = (GByte)(nEntityNum / 256);
        abyLinkage[4] = (GByte)(nMSLink % 256);
        abyLinkage[5] = (GByte)((nMSLink / 256) % 256);
        abyLinkage[6] = (GByte)(nMSLink / 65536);
        abyLinkage[7] = 0x01;
    }
    else
    {
        nLinkageSize = 16;
        abyLinkage[0] = 0x07;
        abyLinkage[1] = 0x10;
        abyLinkage[2] = (GByte)(nLinkageType % 256);
        abyLinkage[3] = (GByte)(nLinkageType / 256);
        abyLinkage[4] = (GByte)(0x81);
        abyLinkage[5] = (GByte)(0x0F);
        abyLinkage[6] = (GByte)(nEntityNum % 256);
        abyLinkage[7] = (GByte)(nEntityNum / 256);
        abyLinkage[8] = (GByte)(nMSLink % 256);
        abyLinkage[9] = (GByte)((nMSLink / 256) % 256);
        abyLinkage[10] = (GByte)((nMSLink / 65536) % 256);
        abyLinkage[11] = (GByte)(nMSLink / 16777216);
        abyLinkage[12] = 0x00;
        abyLinkage[13] = 0x00;
        abyLinkage[14] = 0x00;
        abyLinkage[15] = 0x00;
    }

    return DGNAddRawAttrLink(hDGN, psElement, nLinkageSize, abyLinkage);
}

/************************************************************************/
/*                         DGNAddRawAttrLink()                          */
/************************************************************************/

/**
 * Add a raw attribute linkage to element.
 *
 * Given a raw data buffer, append it to this element as an attribute linkage
 * without trying to interpret the linkage data.
 *
 * The target element must already have raw_data loaded, and it will be
 * resized (see DGNResizeElement()) as needed for the new attribute data.
 * Note that the element is not written to disk immediate.  Use
 * DGNWriteElement() for that.
 *
 * This function will take care of updating the "totlength" field of
 * complex chain or shape headers to account for the extra attribute space
 * consumed in the header element.
 *
 * @param hDGN the file to which the element corresponds.
 * @param psElement the element being updated.
 * @param nLinkSize the size of the linkage in bytes.
 * @param pabyRawLinkData the raw linkage data (nLinkSize bytes worth).
 *
 * @return -1 on failure, or the link index.
 */

int DGNAddRawAttrLink(DGNHandle hDGN, DGNElemCore *psElement, int nLinkSize,
                      unsigned char *pabyRawLinkData)

{
    if (nLinkSize % 2 == 1)
        nLinkSize++;

    if (psElement->size + nLinkSize > 768)
    {
        CPLError(CE_Failure, CPLE_ElementTooBig,
                 "Attempt to add %d byte linkage to element exceeds maximum"
                 " element size.",
                 nLinkSize);
        return -1;
    }

    /* -------------------------------------------------------------------- */
    /*      Ensure the attribute linkage bit is set.                        */
    /* -------------------------------------------------------------------- */
    psElement->properties |= DGNPF_ATTRIBUTES;

    /* -------------------------------------------------------------------- */
    /*      Append the attribute linkage to the linkage area.               */
    /* -------------------------------------------------------------------- */
    psElement->attr_bytes += nLinkSize;
    psElement->attr_data = (unsigned char *)CPLRealloc(psElement->attr_data,
                                                       psElement->attr_bytes);

    memcpy(psElement->attr_data + (psElement->attr_bytes - nLinkSize),
           pabyRawLinkData, nLinkSize);

    /* -------------------------------------------------------------------- */
    /*      Grow the raw data, if we have rawdata.                          */
    /* -------------------------------------------------------------------- */
    psElement->raw_bytes += nLinkSize;
    psElement->raw_data =
        (unsigned char *)CPLRealloc(psElement->raw_data, psElement->raw_bytes);

    memcpy(psElement->raw_data + (psElement->raw_bytes - nLinkSize),
           pabyRawLinkData, nLinkSize);

    /* -------------------------------------------------------------------- */
    /*      If the element is a shape or chain complex header, then we      */
    /*      need to increase the total complex group size appropriately.    */
    /* -------------------------------------------------------------------- */
    if (psElement->stype == DGNST_COMPLEX_HEADER ||
        psElement->stype == DGNST_TEXT_NODE)  // compatible structures
    {
        DGNElemComplexHeader *psCT = (DGNElemComplexHeader *)psElement;

        psCT->totlength += (nLinkSize / 2);

        psElement->raw_data[36] = (unsigned char)(psCT->totlength % 256);
        psElement->raw_data[37] = (unsigned char)(psCT->totlength / 256);
    }

    /* -------------------------------------------------------------------- */
    /*      Ensure everything is updated properly, including element        */
    /*      length and properties.                                          */
    /* -------------------------------------------------------------------- */
    DGNUpdateElemCoreExtended(hDGN, psElement);

    /* -------------------------------------------------------------------- */
    /*      Figure out what the linkage index is.                           */
    /* -------------------------------------------------------------------- */
    int iLinkage = 0;  // Used after for.
    for (;; iLinkage++)
    {
        if (DGNGetLinkage(hDGN, psElement, iLinkage, nullptr, nullptr, nullptr,
                          nullptr) == nullptr)
            break;
    }

    return iLinkage - 1;
}

/************************************************************************/
/*                        DGNAddShapeFileInfo()                         */
/************************************************************************/

/**
 * Add a shape fill attribute linkage.
 *
 * The target element must already have raw_data loaded, and it will be
 * resized (see DGNResizeElement()) as needed for the new attribute data.
 * Note that the element is not written to disk immediate.  Use
 * DGNWriteElement() for that.
 *
 * @param hDGN the file to which the element corresponds.
 * @param psElement the element being updated.
 * @param nColor fill color (color index from palette).
 *
 * @return -1 on failure, or the link index.
 */

int DGNAddShapeFillInfo(DGNHandle hDGN, DGNElemCore *psElement, int nColor)

{
    unsigned char abyFillInfo[16] = {0x07, 0x10, 0x41, 0x00, 0x02, 0x08,
                                     0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
                                     0x00, 0x00, 0x00, 0x00};

    abyFillInfo[8] = (unsigned char)nColor;

    // coverity[overrun-buffer-arg]
    return DGNAddRawAttrLink(hDGN, psElement, 16, abyFillInfo);
}
