/******************************************************************************
 *
 * Project:  DXF Translator
 * Purpose:  Implements BlockMap reading and management portion of
 *           OGRDXFDataSource class
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2009, Frank Warmerdam <warmerdam@pobox.com>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "ogr_dxf.h"
#include "cpl_conv.h"
#include "cpl_string.h"
#include "cpl_csv.h"

#include <algorithm>

/************************************************************************/
/*                          ReadBlockSection()                          */
/************************************************************************/

bool OGRDXFDataSource::ReadBlocksSection()

{
    // Force inlining of blocks to false, for when OGRDXFLayer processes
    // INSERT entities
    const bool bOldInlineBlocks = bInlineBlocks;
    bInlineBlocks = false;

    OGRDXFLayer *poReaderLayer =
        cpl::down_cast<OGRDXFLayer *>(GetLayerByName("Entities"));

    iEntitiesOffset = poReader->GetCurrentFilePos();
    iEntitiesLineNumber = poReader->nLineNumber;

    char szLineBuf[257];
    int nCode = 0;
    while ((nCode = ReadValue(szLineBuf, sizeof(szLineBuf))) > -1 &&
           !EQUAL(szLineBuf, "ENDSEC"))
    {
        // We are only interested in extracting blocks.
        if (nCode != 0 || !EQUAL(szLineBuf, "BLOCK"))
            continue;

        // Process contents of BLOCK definition till we find the
        // first entity.
        CPLString osBlockName;
        CPLString osBlockRecordHandle;
        OGRDXFInsertTransformer oBasePointTransformer;

        while ((nCode = ReadValue(szLineBuf, sizeof(szLineBuf))) > 0)
        {
            switch (nCode)
            {
                case 2:
                    osBlockName = szLineBuf;
                    break;

                case 330:
                    // get the block record handle as well, for arrowheads
                    osBlockRecordHandle = szLineBuf;
                    break;

                case 10:
                    oBasePointTransformer.dfXOffset = -CPLAtof(szLineBuf);
                    break;

                case 20:
                    oBasePointTransformer.dfYOffset = -CPLAtof(szLineBuf);
                    break;

                case 30:
                    oBasePointTransformer.dfZOffset = -CPLAtof(szLineBuf);
                    break;
            }
        }
        if (nCode < 0)
        {
            bInlineBlocks = bOldInlineBlocks;
            DXF_READER_ERROR();
            return false;
        }

        // store the block record handle mapping even if the block is empty
        oBlockRecordHandles[osBlockRecordHandle] = osBlockName;

        if (EQUAL(szLineBuf, "ENDBLK"))
            continue;

        UnreadValue();

        if (oBlockMap.find(osBlockName) != oBlockMap.end())
        {
            bInlineBlocks = bOldInlineBlocks;
            DXF_READER_ERROR();
            return false;
        }

        // Now we will process entities till we run out at the ENDBLK code.

        PushBlockInsertion(osBlockName);

        OGRDXFFeature *poFeature = nullptr;
        int nIters = 0;
        const int nMaxIters =
            atoi(CPLGetConfigOption("DXF_FEATURE_LIMIT_PER_BLOCK", "10000"));
        while ((poFeature = poReaderLayer->GetNextUnfilteredFeature()) !=
               nullptr)
        {
            if (nMaxIters >= 0 && nIters == nMaxIters)
            {
                delete poFeature;
                CPLError(CE_Warning, CPLE_AppDefined,
                         "Limit of %d features for block %s reached. "
                         "If you need more, set the "
                         "DXF_FEATURE_LIMIT_PER_BLOCK configuration "
                         "option to the maximum value (or -1 for no limit)",
                         nMaxIters, osBlockName.c_str());
                break;
            }

            // Apply the base point translation
            OGRGeometry *poFeatureGeom = poFeature->GetGeometryRef();
            if (poFeatureGeom)
                poFeatureGeom->transform(&oBasePointTransformer);

            // Also apply the base point translation to the original
            // coordinates of block references
            if (poFeature->IsBlockReference())
            {
                DXFTriple oTriple = poFeature->GetInsertOCSCoords();
                OGRPoint oPoint(oTriple.dfX, oTriple.dfY, oTriple.dfZ);
                oPoint.transform(&oBasePointTransformer);
                poFeature->SetInsertOCSCoords(
                    DXFTriple(oPoint.getX(), oPoint.getY(), oPoint.getZ()));
            }

            oBlockMap[osBlockName].apoFeatures.push_back(poFeature);
            nIters++;
        }

        PopBlockInsertion();
    }
    if (nCode < 0)
    {
        bInlineBlocks = bOldInlineBlocks;
        DXF_READER_ERROR();
        return false;
    }

    CPLDebug("DXF", "Read %d blocks with meaningful geometry.",
             (int)oBlockMap.size());

    // Restore old inline blocks setting
    bInlineBlocks = bOldInlineBlocks;

    return true;
}

/************************************************************************/
/*                            LookupBlock()                             */
/*                                                                      */
/*      Find the geometry collection corresponding to a name if it      */
/*      exists.  Note that the returned geometry pointer is to a        */
/*      geometry that continues to be owned by the datasource.  It      */
/*      should be cloned for use.                                       */
/************************************************************************/

DXFBlockDefinition *OGRDXFDataSource::LookupBlock(const char *pszName)

{
    CPLString l_osName = pszName;

    if (oBlockMap.count(l_osName) == 0)
        return nullptr;
    else
        return &(oBlockMap[l_osName]);
}

/************************************************************************/
/*                     GetBlockNameByRecordHandle()                     */
/*                                                                      */
/*      Find the name of the block with the given BLOCK_RECORD handle.  */
/*      If there is no such block, an empty string is returned.         */
/************************************************************************/

CPLString OGRDXFDataSource::GetBlockNameByRecordHandle(const char *pszID)

{
    CPLString l_osID = pszID;

    if (oBlockRecordHandles.count(l_osID) == 0)
        return "";
    else
        return oBlockRecordHandles[l_osID];
}

/************************************************************************/
/*                         PushBlockInsertion()                         */
/*                                                                      */
/*      Add a block name to the stack of blocks being inserted.         */
/*      Returns false if we are already inserting this block.           */
/************************************************************************/

bool OGRDXFDataSource::PushBlockInsertion(const CPLString &osBlockName)

{
    // Make sure we are not recursing too deeply (avoid stack overflows) or
    // inserting a block within itself (avoid billion-laughs type issues).
    // 128 is a totally arbitrary limit
    if (aosBlockInsertionStack.size() > 128 ||
        std::find(aosBlockInsertionStack.begin(), aosBlockInsertionStack.end(),
                  osBlockName) != aosBlockInsertionStack.end())
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "Dangerous block recursion detected. "
                 "Some blocks have not been inserted.");
        return false;
    }

    aosBlockInsertionStack.push_back(osBlockName);
    return true;
}

/************************************************************************/
/*                        ~DXFBlockDefinition()                         */
/*                                                                      */
/*      Safe cleanup of a block definition.                             */
/************************************************************************/

DXFBlockDefinition::~DXFBlockDefinition()
{
    while (!apoFeatures.empty())
    {
        delete apoFeatures.back();
        apoFeatures.pop_back();
    }
}
