/******************************************************************************
 *
 * Project:  SXF Translator
 * Purpose:  Definition of classes for OGR SXF Datasource.
 * Author:   Ben Ahmed Daho Ali, bidandou(at)yahoo(dot)fr
 *           Dmitry Baryshnikov, polimax@mail.ru
 *           Alexandr Lisovenko, alexander.lisovenko@gmail.com
 *
 ******************************************************************************
 * Copyright (c) 2011, Ben Ahmed Daho Ali
 * Copyright (c) 2013, NextGIS
 * Copyright (c) 2014, Even Rouault <even dot rouault at spatialys.com>
 * Copyright (c) 2019, NextGIS, <info@nextgis.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "cpl_conv.h"
#include "ogr_sxf.h"
#include "cpl_string.h"
#include "cpl_multiproc.h"

#include <math.h>
#include <map>
#include <string>

// EPSG code range http://gis.stackexchange.com/a/18676/9904
constexpr int MIN_EPSG = 1000;
constexpr int MAX_EPSG = 32768;

/************************************************************************/
/*                         OGRSXFDataSource()                           */
/************************************************************************/

OGRSXFDataSource::OGRSXFDataSource()
{
    memset(&oSXFPassport.informationFlags, 0,
           sizeof(oSXFPassport.informationFlags));
}

/************************************************************************/
/*                          ~OGRSXFDataSource()                         */
/************************************************************************/

OGRSXFDataSource::~OGRSXFDataSource()

{
    m_apoLayers.clear();

    if (nullptr != oSXFPassport.stMapDescription.pSpatRef)
    {
        oSXFPassport.stMapDescription.pSpatRef->Release();
    }

    CloseFile();

    if (hIOMutex != nullptr)
    {
        CPLDestroyMutex(hIOMutex);
        hIOMutex = nullptr;
    }
}

/************************************************************************/
/*                     CloseFile()                                      */
/************************************************************************/
void OGRSXFDataSource::CloseFile()
{
    if (nullptr != fpSXF)
    {
        VSIFCloseL(fpSXF);
        fpSXF = nullptr;
    }
}

/************************************************************************/
/*                           TestCapability()                           */
/************************************************************************/

int OGRSXFDataSource::TestCapability(const char *pszCap)
{
    if (EQUAL(pszCap, ODsCZGeometries))
        return true;

    return false;
}

/************************************************************************/
/*                              GetLayer()                              */
/************************************************************************/

OGRLayer *OGRSXFDataSource::GetLayer(int iLayer)

{
    if (iLayer < 0 || iLayer >= GetLayerCount())
        return nullptr;
    else
        return m_apoLayers[iLayer].get();
}

/************************************************************************/
/*                                Open()                                */
/************************************************************************/

int OGRSXFDataSource::Open(const char *pszFilename, bool bUpdateIn,
                           const char *const *papszOpenOpts)
{
    if (bUpdateIn)
    {
        return FALSE;
    }

    pszName = pszFilename;

    fpSXF = VSIFOpenL(pszName, "rb");
    if (fpSXF == nullptr)
    {
        CPLError(CE_Warning, CPLE_OpenFailed, "SXF open file %s failed",
                 pszFilename);
        return FALSE;
    }

    // read header
    const int nFileHeaderSize = sizeof(SXFHeader);
    SXFHeader stSXFFileHeader;
    const size_t nObjectsRead =
        VSIFReadL(&stSXFFileHeader, nFileHeaderSize, 1, fpSXF);

    if (nObjectsRead != 1)
    {
        CPLError(CE_Failure, CPLE_None, "SXF head read failed");
        CloseFile();
        return FALSE;
    }
    CPL_LSBPTR32(&(stSXFFileHeader.nHeaderLength));
    CPL_LSBPTR32(&(stSXFFileHeader.nCheckSum));

    // check version
    oSXFPassport.version = 0;
    if (stSXFFileHeader.nHeaderLength > 256)  // if size == 400 then version >=
                                              // 4
    {
        oSXFPassport.version = stSXFFileHeader.nFormatVersion[2];
    }
    else
    {
        oSXFPassport.version = stSXFFileHeader.nFormatVersion[1];
    }

    if (oSXFPassport.version < 3)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "SXF File version not supported");
        CloseFile();
        return FALSE;
    }

    // read description
    if (ReadSXFDescription(fpSXF, oSXFPassport) != OGRERR_NONE)
    {
        CPLError(CE_Failure, CPLE_NotSupported, "SXF. Wrong description.");
        CloseFile();
        return FALSE;
    }

    // read flags
    if (ReadSXFInformationFlags(fpSXF, oSXFPassport) != OGRERR_NONE)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "SXF. Wrong state of the data.");
        CloseFile();
        return FALSE;
    }

    if (oSXFPassport.version == 3 &&
        oSXFPassport.informationFlags.bProjectionDataCompliance == false)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "SXF. Data does not correspond to the projection.");
        CloseFile();
        return FALSE;
    }

    // read spatial data
    if (ReadSXFMapDescription(fpSXF, oSXFPassport, papszOpenOpts) !=
        OGRERR_NONE)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "SXF. Wrong state of the data.");
        CloseFile();
        return FALSE;
    }

    if (oSXFPassport.informationFlags.bRealCoordinatesCompliance == false)
    {
        CPLError(CE_Warning, CPLE_NotSupported,
                 "SXF. Given material may be rotated in the conditional system "
                 "of coordinates");
    }

    /*---------------- TRY READ THE RSC FILE HEADER  -----------------------*/

    CPLString soRSCRileName;
    const char *pszRSCRileName =
        CSLFetchNameValueDef(papszOpenOpts, "SXF_RSC_FILENAME",
                             CPLGetConfigOption("SXF_RSC_FILENAME", ""));
    if (pszRSCRileName != nullptr &&
        CPLCheckForFile((char *)pszRSCRileName, nullptr) == TRUE)
    {
        soRSCRileName = pszRSCRileName;
    }

    if (soRSCRileName.empty())
    {
        pszRSCRileName = CPLResetExtension(pszFilename, "rsc");
        if (CPLCheckForFile((char *)pszRSCRileName, nullptr) == TRUE)
        {
            soRSCRileName = pszRSCRileName;
        }
    }

    if (soRSCRileName.empty())
    {
        pszRSCRileName = CPLResetExtension(pszFilename, "RSC");
        if (CPLCheckForFile((char *)pszRSCRileName, nullptr) == TRUE)
        {
            soRSCRileName = pszRSCRileName;
        }
    }

    // 1. Create layers from RSC file or create default set of layers from
    // gdal_data/default.rsc.

    if (soRSCRileName.empty())
    {
        pszRSCRileName = CPLFindFile("gdal", "default.rsc");
        if (nullptr != pszRSCRileName)
        {
            soRSCRileName = pszRSCRileName;
        }
        else
        {
            CPLDebug("OGRSXFDataSource", "Default RSC file not found");
        }
    }

    if (soRSCRileName.empty())
    {
        CPLError(CE_Warning, CPLE_None, "RSC file for %s not exist",
                 pszFilename);
    }
    else
    {
        VSILFILE *fpRSC = VSIFOpenL(soRSCRileName, "rb");
        if (fpRSC == nullptr)
        {
            CPLError(CE_Warning, CPLE_OpenFailed, "RSC file %s open failed",
                     soRSCRileName.c_str());
        }
        else
        {
            CPLDebug("OGRSXFDataSource", "RSC Filename: %s",
                     soRSCRileName.c_str());
            CreateLayers(fpRSC, papszOpenOpts);
            VSIFCloseL(fpRSC);
        }
    }

    if (m_apoLayers.empty())  // create default set of layers
    {
        CreateLayers();
    }

    FillLayers();

    return TRUE;
}

OGRErr OGRSXFDataSource::ReadSXFDescription(VSILFILE *fpSXFIn,
                                            SXFPassport &passport)
{
    // int nObjectsRead = 0;

    if (passport.version == 3)
    {
        // 78
        GByte buff[62];
        /* nObjectsRead = */ VSIFReadL(&buff, 62, 1, fpSXFIn);
        char date[3] = {0};

        // read year
        memcpy(date, buff, 2);
        passport.dtCrateDate.nYear = static_cast<GUInt16>(atoi(date));
        if (passport.dtCrateDate.nYear < 50)
            passport.dtCrateDate.nYear += 2000;
        else
            passport.dtCrateDate.nYear += 1900;

        memcpy(date, buff + 2, 2);

        passport.dtCrateDate.nMonth = static_cast<GUInt16>(atoi(date));

        memcpy(date, buff + 4, 2);

        passport.dtCrateDate.nDay = static_cast<GUInt16>(atoi(date));

        char szName[26] = {0};
        memcpy(szName, buff + 8, 24);
        szName[sizeof(szName) - 1] = '\0';
        char *pszRecoded =
            CPLRecode(szName, "CP1251", CPL_ENC_UTF8);  // szName + 2
        passport.sMapSheet =
            pszRecoded;  // TODO: check the encoding in SXF created in Linux
        CPLFree(pszRecoded);

        memcpy(&passport.nScale, buff + 32, 4);
        CPL_LSBPTR32(&passport.nScale);

        memcpy(szName, buff + 36, 26);
        szName[sizeof(szName) - 1] = '\0';
        pszRecoded = CPLRecode(szName, "CP866", CPL_ENC_UTF8);
        passport.sMapSheetName =
            pszRecoded;  // TODO: check the encoding in SXF created in Linux
        CPLFree(pszRecoded);
    }
    else if (passport.version == 4)
    {
        // 96
        GByte buff[80];
        /* nObjectsRead = */ VSIFReadL(&buff, 80, 1, fpSXFIn);
        char date[5] = {0};

        // read year
        memcpy(date, buff, 4);
        passport.dtCrateDate.nYear = static_cast<GUInt16>(atoi(date));

        memcpy(date, buff + 4, 2);
        memset(date + 2, 0, 3);

        passport.dtCrateDate.nMonth = static_cast<GUInt16>(atoi(date));

        memcpy(date, buff + 6, 2);

        passport.dtCrateDate.nDay = static_cast<GUInt16>(atoi(date));

        char szName[32] = {0};
        memcpy(szName, buff + 12, 32);
        szName[sizeof(szName) - 1] = '\0';
        char *pszRecoded =
            CPLRecode(szName, "CP1251", CPL_ENC_UTF8);  // szName + 2
        passport.sMapSheet =
            pszRecoded;  // TODO: check the encoding in SXF created in Linux
        CPLFree(pszRecoded);

        memcpy(&passport.nScale, buff + 44, 4);
        CPL_LSBPTR32(&passport.nScale);

        memcpy(szName, buff + 48, 32);
        szName[sizeof(szName) - 1] = '\0';
        pszRecoded = CPLRecode(szName, "CP1251", CPL_ENC_UTF8);
        passport.sMapSheetName =
            pszRecoded;  // TODO: check the encoding in SXF created in Linux
        CPLFree(pszRecoded);
    }

    SetMetadataItem("SHEET", passport.sMapSheet);
    SetMetadataItem("SHEET_NAME", passport.sMapSheetName);
    SetMetadataItem("SHEET_CREATE_DATE",
                    CPLSPrintf("%.2u-%.2u-%.4u", passport.dtCrateDate.nDay,
                               passport.dtCrateDate.nMonth,
                               passport.dtCrateDate.nYear));
    SetMetadataItem("SXF_VERSION", CPLSPrintf("%u", passport.version));
    SetMetadataItem("SCALE", CPLSPrintf("1 : %u", passport.nScale));

    return OGRERR_NONE;
}

OGRErr OGRSXFDataSource::ReadSXFInformationFlags(VSILFILE *fpSXFIn,
                                                 SXFPassport &passport)
{
    // int nObjectsRead = 0;
    GByte val[4];
    /* nObjectsRead = */ VSIFReadL(&val, 4, 1, fpSXFIn);

    if (!(CHECK_BIT(val[0], 0) && CHECK_BIT(val[0], 1)))  // xxxxxx11
    {
        return OGRERR_UNSUPPORTED_OPERATION;
    }

    if (CHECK_BIT(val[0], 2))  // xxxxx0xx or xxxxx1xx
    {
        passport.informationFlags.bProjectionDataCompliance = true;
    }
    else
    {
        passport.informationFlags.bProjectionDataCompliance = false;
    }

    if (CHECK_BIT(val[0], 4))
    {
        passport.informationFlags.bRealCoordinatesCompliance = true;
    }
    else
    {
        passport.informationFlags.bRealCoordinatesCompliance = false;
    }

    if (CHECK_BIT(val[0], 6))
    {
        passport.informationFlags.stCodingType = SXF_SEM_TXT;
    }
    else
    {
        if (CHECK_BIT(val[0], 5))
        {
            passport.informationFlags.stCodingType = SXF_SEM_HEX;
        }
        else
        {
            passport.informationFlags.stCodingType = SXF_SEM_DEC;
        }
    }

    if (CHECK_BIT(val[0], 7))
    {
        passport.informationFlags.stGenType = SXF_GT_LARGE_SCALE;
    }
    else
    {
        passport.informationFlags.stGenType = SXF_GT_SMALL_SCALE;
    }

    // version specific

    if (passport.version == 3)
    {
        // degrees are ints * 100 000 000
        // meters are ints / 10
        passport.informationFlags.stEnc = SXF_ENC_DOS;
        passport.informationFlags.stCoordAcc = SXF_COORD_ACC_DM;
        passport.informationFlags.bSort = false;
    }
    else if (passport.version == 4)
    {
        if (val[1] <= SXF_ENC_LAST)
        {
            passport.informationFlags.stEnc =
                static_cast<SXFTextEncoding>(val[1]);
        }
        else
        {
            CPLDebug("SXF",
                     "Invalid passport.informationFlags.stEnc = %d. "
                     "Defaulting to SXF_ENC_DOS",
                     val[1]);
            passport.informationFlags.stEnc = SXF_ENC_DOS;
        }

        if (val[2] <= SXF_COORD_ACC_LAST)
        {
            passport.informationFlags.stCoordAcc =
                static_cast<SXFCoordinatesAccuracy>(val[2]);
        }
        else
        {
            CPLDebug("SXF",
                     "Invalid passport.informationFlags.stCoordAcc = %d. "
                     "Defaulting to SXF_COORD_ACC_UNDEFINED",
                     val[1]);
            passport.informationFlags.stCoordAcc = SXF_COORD_ACC_UNDEFINED;
        }

        if (CHECK_BIT(val[3], 0))
        {
            passport.informationFlags.bSort = true;
        }
        else
        {
            passport.informationFlags.bSort = false;
        }
    }

    return OGRERR_NONE;
}

void OGRSXFDataSource::SetVertCS(const long iVCS, SXFPassport &passport,
                                 const char *const *papszOpenOpts)
{
    const char *pszSetVertCS =
        CSLFetchNameValueDef(papszOpenOpts, "SXF_SET_VERTCS",
                             CPLGetConfigOption("SXF_SET_VERTCS", "NO"));
    if (!CPLTestBool(pszSetVertCS))
        return;

    passport.stMapDescription.pSpatRef->importVertCSFromPanorama(
        static_cast<int>(iVCS));
}

OGRErr OGRSXFDataSource::ReadSXFMapDescription(VSILFILE *fpSXFIn,
                                               SXFPassport &passport,
                                               const char *const *papszOpenOpts)
{
    // int nObjectsRead = 0;
    passport.stMapDescription.Env.MaxX = -100000000;
    passport.stMapDescription.Env.MinX = 100000000;
    passport.stMapDescription.Env.MaxY = -100000000;
    passport.stMapDescription.Env.MinY = 100000000;

    bool bIsX = true;  // passport.informationFlags.bRealCoordinatesCompliance;
                       // //if real coordinates we need to swap x & y

    // version specific
    if (passport.version == 3)
    {
        short nNoObjClass, nNoSemClass;
        if (VSIFReadL(&nNoObjClass, 2, 1, fpSXFIn) != 1)
            return OGRERR_FAILURE;
        if (VSIFReadL(&nNoSemClass, 2, 1, fpSXFIn) != 1)
            return OGRERR_FAILURE;
        GByte byMask[8];
        if (VSIFReadL(&byMask, 8, 1, fpSXFIn) != 1)
            return OGRERR_FAILURE;

        int nCorners[8];

        // get projected corner coords
        if (VSIFReadL(&nCorners, 32, 1, fpSXFIn) != 1)
            return OGRERR_FAILURE;

        for (int i = 0; i < 8; i++)
        {
            CPL_LSBPTR32(&nCorners[i]);
            passport.stMapDescription.stProjCoords[i] =
                double(nCorners[i]) / 10.0;
            if (bIsX)  // X
            {
                if (passport.stMapDescription.Env.MaxY <
                    passport.stMapDescription.stProjCoords[i])
                    passport.stMapDescription.Env.MaxY =
                        passport.stMapDescription.stProjCoords[i];
                if (passport.stMapDescription.Env.MinY >
                    passport.stMapDescription.stProjCoords[i])
                    passport.stMapDescription.Env.MinY =
                        passport.stMapDescription.stProjCoords[i];
            }
            else
            {
                if (passport.stMapDescription.Env.MaxX <
                    passport.stMapDescription.stProjCoords[i])
                    passport.stMapDescription.Env.MaxX =
                        passport.stMapDescription.stProjCoords[i];
                if (passport.stMapDescription.Env.MinX >
                    passport.stMapDescription.stProjCoords[i])
                    passport.stMapDescription.Env.MinX =
                        passport.stMapDescription.stProjCoords[i];
            }
            bIsX = !bIsX;
        }
        // get geographic corner coords
        if (VSIFReadL(&nCorners, 32, 1, fpSXFIn) != 1)
            return OGRERR_FAILURE;

        for (int i = 0; i < 8; i++)
        {
            CPL_LSBPTR32(&nCorners[i]);
            passport.stMapDescription.stGeoCoords[i] =
                double(nCorners[i]) *
                0.00000057295779513082;  // from radians to degree * 100 000 000
        }
    }
    else if (passport.version == 4)
    {
        int nEPSG = 0;
        if (VSIFReadL(&nEPSG, 4, 1, fpSXFIn) != 1)
            return OGRERR_FAILURE;
        CPL_LSBPTR32(&nEPSG);

        if (nEPSG >= MIN_EPSG &&
            nEPSG <= MAX_EPSG)  // TODO: check epsg valid range
        {
            passport.stMapDescription.pSpatRef = new OGRSpatialReference();
            passport.stMapDescription.pSpatRef->importFromEPSG(nEPSG);
        }

        double dfCorners[8];
        if (VSIFReadL(&dfCorners, 64, 1, fpSXFIn) != 1)
            return OGRERR_FAILURE;

        for (int i = 0; i < 8; i++)
        {
            CPL_LSBPTR64(&dfCorners[i]);
            passport.stMapDescription.stProjCoords[i] = dfCorners[i];
            if (bIsX)  // X
            {
                if (passport.stMapDescription.Env.MaxY <
                    passport.stMapDescription.stProjCoords[i])
                    passport.stMapDescription.Env.MaxY =
                        passport.stMapDescription.stProjCoords[i];
                if (passport.stMapDescription.Env.MinY >
                    passport.stMapDescription.stProjCoords[i])
                    passport.stMapDescription.Env.MinY =
                        passport.stMapDescription.stProjCoords[i];
            }
            else
            {
                if (passport.stMapDescription.Env.MaxX <
                    passport.stMapDescription.stProjCoords[i])
                    passport.stMapDescription.Env.MaxX =
                        passport.stMapDescription.stProjCoords[i];
                if (passport.stMapDescription.Env.MinX >
                    passport.stMapDescription.stProjCoords[i])
                    passport.stMapDescription.Env.MinX =
                        passport.stMapDescription.stProjCoords[i];
            }
            bIsX = !bIsX;
        }
        // get geographic corner coords
        if (VSIFReadL(&dfCorners, 64, 1, fpSXFIn) != 1)
            return OGRERR_FAILURE;

        for (int i = 0; i < 8; i++)
        {
            CPL_LSBPTR64(&dfCorners[i]);
            passport.stMapDescription.stGeoCoords[i] =
                dfCorners[i] * TO_DEGREES;  // to degree
        }
    }

    if (nullptr != passport.stMapDescription.pSpatRef)
    {
        return OGRERR_NONE;
    }

    GByte anData[8] = {0};
    if (VSIFReadL(&anData, 8, 1, fpSXFIn) != 1)
        return OGRERR_FAILURE;
    long iEllips = anData[0];
    long iVCS = anData[1];
    long iProjSys = anData[2];
    /* long iDatum = anData[3]; Unused. */
    double dfProjScale = 1;

    double adfPrjParams[8] = {0};

    if (passport.version == 3)
    {
        switch (anData[4])
        {
            case 1:
                passport.stMapDescription.eUnitInPlan = SXF_COORD_MU_DECIMETRE;
                break;
            case 2:
                passport.stMapDescription.eUnitInPlan = SXF_COORD_MU_CENTIMETRE;
                break;
            case 3:
                passport.stMapDescription.eUnitInPlan = SXF_COORD_MU_MILLIMETRE;
                break;
            case 130:
                passport.stMapDescription.eUnitInPlan = SXF_COORD_MU_RADIAN;
                break;
            case 129:
                passport.stMapDescription.eUnitInPlan = SXF_COORD_MU_DEGREE;
                break;
            default:
                passport.stMapDescription.eUnitInPlan = SXF_COORD_MU_METRE;
                break;
        }

        VSIFSeekL(fpSXFIn, 212, SEEK_SET);
        struct _buff
        {
            GUInt32 nRes;
            GInt16 anFrame[8];
            // cppcheck-suppress unusedStructMember
            GUInt32 nFrameCode;
        } buff;
        if (VSIFReadL(&buff, 20, 1, fpSXFIn) != 1)
            return OGRERR_FAILURE;
        CPL_LSBPTR32(&buff.nRes);
        CPL_LSBPTR32(&buff.nFrameCode);
        passport.stMapDescription.nResolution = buff.nRes;  // resolution

        for (int i = 0; i < 8; i++)
        {
            CPL_LSBPTR16(&(buff.anFrame[i]));
            passport.stMapDescription.stFrameCoords[i] = buff.anFrame[i];
        }

        int anParams[5];
        if (VSIFReadL(&anParams, 20, 1, fpSXFIn) != 1)
            return OGRERR_FAILURE;
        for (int i = 0; i < 5; i++)
        {
            CPL_LSBPTR32(&anParams[i]);
        }

        if (anParams[0] != -1)
            dfProjScale = double(anParams[0]) / 100000000.0;

        if (anParams[2] != -1)
            passport.stMapDescription.dfXOr =
                double(anParams[2]) / 100000000.0 * TO_DEGREES;
        else
            passport.stMapDescription.dfXOr = 0;

        if (anParams[3] != -1)
            passport.stMapDescription.dfYOr =
                double(anParams[2]) / 100000000.0 * TO_DEGREES;
        else
            passport.stMapDescription.dfYOr = 0;

        passport.stMapDescription.dfFalseNorthing = 0;
        passport.stMapDescription.dfFalseEasting = 0;

        // adfPrjParams[0] = double(anParams[0]) / 100000000.0; // to radians
        // adfPrjParams[1] = double(anParams[1]) / 100000000.0;
        // adfPrjParams[2] = double(anParams[2]) / 100000000.0;
        // adfPrjParams[3] = double(anParams[3]) / 100000000.0;
        adfPrjParams[4] = dfProjScale;  //?
        // adfPrjParams[5] = 0;//?
        // adfPrjParams[6] = 0;//?
        // adfPrjParams[7] = 0;// importFromPanorama calc it by itself
    }
    else if (passport.version == 4)
    {
        switch (anData[4])
        {
            case 64:
                passport.stMapDescription.eUnitInPlan = SXF_COORD_MU_RADIAN;
                break;
            case 65:
                passport.stMapDescription.eUnitInPlan = SXF_COORD_MU_DEGREE;
                break;
            default:
                passport.stMapDescription.eUnitInPlan = SXF_COORD_MU_METRE;
                break;
        }

        VSIFSeekL(fpSXFIn, 312, SEEK_SET);
        GUInt32 buff[10];
        if (VSIFReadL(&buff, 40, 1, fpSXFIn) != 1)
            return OGRERR_FAILURE;
        for (int i = 0; i < 10; i++)
        {
            CPL_LSBPTR32(&buff[i]);
        }

        passport.stMapDescription.nResolution = buff[0];  // resolution
        for (int i = 0; i < 8; i++)
            passport.stMapDescription.stFrameCoords[i] = buff[1 + i];

        double adfParams[6] = {};
        if (VSIFReadL(&adfParams, 48, 1, fpSXFIn) != 1)
            return OGRERR_FAILURE;
        for (int i = 0; i < 6; i++)
        {
            CPL_LSBPTR64(&adfParams[i]);
        }

        if (adfParams[1] != -1)
            dfProjScale = adfParams[1];
        passport.stMapDescription.dfXOr = adfParams[2] * TO_DEGREES;
        passport.stMapDescription.dfYOr = adfParams[3] * TO_DEGREES;
        passport.stMapDescription.dfFalseNorthing = adfParams[4];
        passport.stMapDescription.dfFalseEasting = adfParams[5];

        // adfPrjParams[0] = adfParams[0]; // to radians
        // adfPrjParams[1] = adfParams[1];
        // adfPrjParams[2] = adfParams[2];
        // adfPrjParams[3] = adfParams[3];
        adfPrjParams[4] = dfProjScale;  //?
        // adfPrjParams[5] = adfParams[4];
        // adfPrjParams[6] = adfParams[5];
        // adfPrjParams[7] = 0;// importFromPanorama calc it by itself
    }

    passport.stMapDescription.dfScale = passport.nScale;

    if (passport.stMapDescription.nResolution == 0)
    {
        return OGRERR_FAILURE;
    }
    const double dfCoeff = double(passport.stMapDescription.dfScale) /
                           passport.stMapDescription.nResolution;
    passport.stMapDescription.bIsRealCoordinates =
        passport.informationFlags.bRealCoordinatesCompliance;
    passport.stMapDescription.stCoordAcc = passport.informationFlags.stCoordAcc;

    if (!passport.stMapDescription.bIsRealCoordinates)
    {
        if (passport.stMapDescription.stFrameCoords[0] == 0 &&
            passport.stMapDescription.stFrameCoords[1] == 0 &&
            passport.stMapDescription.stFrameCoords[2] == 0 &&
            passport.stMapDescription.stFrameCoords[3] == 0 &&
            passport.stMapDescription.stFrameCoords[4] == 0 &&
            passport.stMapDescription.stFrameCoords[5] == 0 &&
            passport.stMapDescription.stFrameCoords[6] == 0 &&
            passport.stMapDescription.stFrameCoords[7] == 0)
        {
            passport.stMapDescription.bIsRealCoordinates = true;
        }
        else
        {
            // origin
            passport.stMapDescription.dfXOr =
                passport.stMapDescription.stProjCoords[1] -
                passport.stMapDescription.stFrameCoords[1] * dfCoeff;
            passport.stMapDescription.dfYOr =
                passport.stMapDescription.stProjCoords[0] -
                passport.stMapDescription.stFrameCoords[0] * dfCoeff;
        }
    }

    // normalize some coordintatessystems
    if ((iEllips == 1 || iEllips == 0) &&
        iProjSys == 1)  // Pulkovo 1942 / Gauss-Kruger
    {
        double dfCenterLongEnv =
            passport.stMapDescription.stGeoCoords[1] +
            fabs(passport.stMapDescription.stGeoCoords[5] -
                 passport.stMapDescription.stGeoCoords[1]) /
                2;

        int nZoneEnv = (int)((dfCenterLongEnv + 3.0) / 6.0 + 0.5);

        if (nZoneEnv > 1 && nZoneEnv < 33)
        {
            int nEPSG = 28400 + nZoneEnv;
            passport.stMapDescription.pSpatRef = new OGRSpatialReference();
            passport.stMapDescription.pSpatRef->SetAxisMappingStrategy(
                OAMS_TRADITIONAL_GIS_ORDER);
            OGRErr eErr =
                passport.stMapDescription.pSpatRef->importFromEPSG(nEPSG);
            SetVertCS(iVCS, passport, papszOpenOpts);
            return eErr;
        }
        else
        {
            adfPrjParams[7] = nZoneEnv;

            if (adfPrjParams[5] == 0)  // False Easting
            {
                if (passport.stMapDescription.Env.MaxX < 500000)
                    adfPrjParams[5] = 500000;
                else
                {
                    if (nZoneEnv >= -60 && nZoneEnv <= 60)
                        adfPrjParams[5] = nZoneEnv * 1000000 + 500000;
                    else
                    {
                        CPLError(CE_Failure, CPLE_AppDefined,
                                 "Wrong nZoneEnv = %d value", nZoneEnv);
                        return OGRERR_FAILURE;
                    }
                }
            }
        }
    }
    else if (iEllips == 9 && iProjSys == 17)  // WGS84 / UTM
    {
        double dfCenterLongEnv =
            passport.stMapDescription.stGeoCoords[1] +
            fabs(passport.stMapDescription.stGeoCoords[5] -
                 passport.stMapDescription.stGeoCoords[1]) /
                2;
        int nZoneEnv = (int)(30 + (dfCenterLongEnv + 3.0) / 6.0 + 0.5);
        bool bNorth = passport.stMapDescription.stGeoCoords[6] +
                          (passport.stMapDescription.stGeoCoords[2] -
                           passport.stMapDescription.stGeoCoords[6]) /
                              2 <
                      0;
        int nEPSG = 0;
        if (bNorth)
        {
            nEPSG = 32600 + nZoneEnv;
        }
        else
        {
            nEPSG = 32700 + nZoneEnv;
        }
        passport.stMapDescription.pSpatRef = new OGRSpatialReference();
        passport.stMapDescription.pSpatRef->SetAxisMappingStrategy(
            OAMS_TRADITIONAL_GIS_ORDER);
        OGRErr eErr = passport.stMapDescription.pSpatRef->importFromEPSG(nEPSG);
        SetVertCS(iVCS, passport, papszOpenOpts);
        return eErr;
    }
    else if (iEllips == 45 && iProjSys == 35)  // Mercator 3857 on sphere wgs84
    {
        passport.stMapDescription.pSpatRef = new OGRSpatialReference(
            "PROJCS[\"WGS 84 / Pseudo-Mercator\",GEOGCS[\"WGS "
            "84\",DATUM[\"WGS_1984\",SPHEROID[\"WGS "
            "84\",6378137,298.257223563,AUTHORITY[\"EPSG\",\"7030\"]],"
            "AUTHORITY[\"EPSG\",\"6326\"]],PRIMEM[\"Greenwich\",0,AUTHORITY["
            "\"EPSG\",\"8901\"]],UNIT[\"degree\",0.0174532925199433,AUTHORITY["
            "\"EPSG\",\"9122\"]],AUTHORITY[\"EPSG\",\"4326\"]],PROJECTION["
            "\"Mercator_1SP\"],PARAMETER[\"central_meridian\",0],PARAMETER["
            "\"scale_factor\",1],PARAMETER[\"false_easting\",0],PARAMETER["
            "\"false_northing\",0],UNIT[\"metre\",1,AUTHORITY[\"EPSG\","
            "\"9001\"]],AXIS[\"X\",EAST],AXIS[\"Y\",NORTH],EXTENSION[\"PROJ4\","
            "\"+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 "
            "+x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext  "
            "+no_defs\"],AUTHORITY[\"EPSG\",\"3857\"]]");
        passport.stMapDescription.pSpatRef->SetAxisMappingStrategy(
            OAMS_TRADITIONAL_GIS_ORDER);
        OGRErr eErr =
            OGRERR_NONE;  // passport.stMapDescription.pSpatRef->importFromEPSG(3857);
        SetVertCS(iVCS, passport, papszOpenOpts);
        return eErr;
    }
    else if (iEllips == 9 && iProjSys == 35)  // Mercator 3395 on ellips wgs84
    {
        passport.stMapDescription.pSpatRef = new OGRSpatialReference();
        passport.stMapDescription.pSpatRef->SetAxisMappingStrategy(
            OAMS_TRADITIONAL_GIS_ORDER);
        OGRErr eErr = passport.stMapDescription.pSpatRef->importFromEPSG(3395);
        SetVertCS(iVCS, passport, papszOpenOpts);
        return eErr;
    }
    else if (iEllips == 9 && iProjSys == 34)  // Miller 54003 on sphere wgs84
    {
        passport.stMapDescription.pSpatRef = new OGRSpatialReference(
            "PROJCS[\"World_Miller_Cylindrical\",GEOGCS[\"GCS_GLOBE\", "
            "DATUM[\"GLOBE\", SPHEROID[\"GLOBE\", 6367444.6571, "
            "0.0]],PRIMEM[\"Greenwich\",0],UNIT[\"Degree\",0."
            "017453292519943295]],PROJECTION[\"Miller_Cylindrical\"],PARAMETER["
            "\"False_Easting\",0],PARAMETER[\"False_Northing\",0],PARAMETER["
            "\"Central_Meridian\",0],UNIT[\"Meter\",1],AUTHORITY[\"EPSG\","
            "\"54003\"]]");
        passport.stMapDescription.pSpatRef->SetAxisMappingStrategy(
            OAMS_TRADITIONAL_GIS_ORDER);
        OGRErr eErr =
            OGRERR_NONE;  // passport.stMapDescription.pSpatRef->importFromEPSG(3395);
        // OGRErr eErr =
        // passport.stMapDescription.pSpatRef->importFromEPSG(54003);

        SetVertCS(iVCS, passport, papszOpenOpts);
        return eErr;
    }
    else if (iEllips == 9 && iProjSys == 33 &&
             passport.stMapDescription.eUnitInPlan == SXF_COORD_MU_DEGREE)
    {
        passport.stMapDescription.pSpatRef =
            new OGRSpatialReference(SRS_WKT_WGS84_LAT_LONG);
        passport.stMapDescription.pSpatRef->SetAxisMappingStrategy(
            OAMS_TRADITIONAL_GIS_ORDER);
        OGRErr eErr = OGRERR_NONE;
        SetVertCS(iVCS, passport, papszOpenOpts);
        return eErr;
    }

    // TODO: Need to normalize more SRS:
    // PAN_PROJ_WAG1
    // PAN_PROJ_MERCAT
    // PAN_PROJ_PS
    // PAN_PROJ_POLYC
    // PAN_PROJ_EC
    // PAN_PROJ_LCC
    // PAN_PROJ_STEREO
    // PAN_PROJ_AE
    // PAN_PROJ_GNOMON
    // PAN_PROJ_MOLL
    // PAN_PROJ_LAEA
    // PAN_PROJ_EQC
    // PAN_PROJ_CEA
    // PAN_PROJ_IMWP
    //

    passport.stMapDescription.pSpatRef = new OGRSpatialReference();
    passport.stMapDescription.pSpatRef->SetAxisMappingStrategy(
        OAMS_TRADITIONAL_GIS_ORDER);
    OGRErr eErr = passport.stMapDescription.pSpatRef->importFromPanorama(
        anData[2], anData[3], anData[0], adfPrjParams);
    SetVertCS(iVCS, passport, papszOpenOpts);
    return eErr;
}

void OGRSXFDataSource::FillLayers()
{
    CPLDebug("SXF", "Create layers");

    // 2. Read all records (only classify code and offset) and add this to
    // correspondence layer
    int nObjectsRead = 0;
    vsi_l_offset nOffset = 0;

    // get record count
    GUInt32 nRecordCountMax = 0;
    if (oSXFPassport.version == 3)
    {
        VSIFSeekL(fpSXF, 288, SEEK_SET);
        nObjectsRead =
            static_cast<int>(VSIFReadL(&nRecordCountMax, 4, 1, fpSXF));
        nOffset = 300;
        CPL_LSBPTR32(&nRecordCountMax);
    }
    else if (oSXFPassport.version == 4)
    {
        VSIFSeekL(fpSXF, 440, SEEK_SET);
        nObjectsRead =
            static_cast<int>(VSIFReadL(&nRecordCountMax, 4, 1, fpSXF));
        nOffset = 452;
        CPL_LSBPTR32(&nRecordCountMax);
    }
    /* else nOffset and nObjectsRead will be 0 */

    if (nObjectsRead != 1)
    {
        CPLError(CE_Failure, CPLE_FileIO, "Get record count failed");
        return;
    }

    VSIFSeekL(fpSXF, nOffset, SEEK_SET);

    for (GUInt32 nFID = 0; nFID < nRecordCountMax; nFID++)
    {
        GInt32 buff[6];
        nObjectsRead = static_cast<int>(VSIFReadL(&buff, 24, 1, fpSXF));
        for (int i = 0; i < 6; i++)
        {
            CPL_LSBPTR32(&buff[i]);
        }

        if (nObjectsRead != 1 || buff[0] != IDSXFOBJ)
        {
            CPLError(CE_Failure, CPLE_FileIO, "Read record %d failed", nFID);
            return;
        }

        bool bHasSemantic = CHECK_BIT(buff[5], 9);
        if (bHasSemantic)  // check has attributes
        {
            // we have already 24 byte read
            vsi_l_offset nOffsetSemantic = 8 + buff[2];
            VSIFSeekL(fpSXF, nOffsetSemantic, SEEK_CUR);
        }

        int nSemanticSize = buff[1] - 32 - buff[2];
        if (nSemanticSize < 0)
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Invalid value");
            break;
        }

        for (const auto &poLayer : m_apoLayers)
        {
            if (poLayer->AddRecord(nFID, buff[3], nOffset, bHasSemantic,
                                   nSemanticSize) == TRUE)
            {
                break;
            }
        }
        nOffset += buff[1];
        VSIFSeekL(fpSXF, nOffset, SEEK_SET);
    }
    // 3. delete empty layers
    for (size_t i = 0; i < m_apoLayers.size(); /* increment in loop */)
    {
        OGRSXFLayer *pOGRSXFLayer = m_apoLayers[i].get();
        if (pOGRSXFLayer->GetFeatureCount() == 0)
        {
            m_apoLayers.erase(m_apoLayers.begin() + i);
        }
        else
        {
            pOGRSXFLayer->ResetReading();
            ++i;
        }
    }
}

OGRSXFLayer *OGRSXFDataSource::GetLayerById(GByte nID)
{
    for (const auto &poLayer : m_apoLayers)
    {
        if (poLayer->GetId() == nID)
        {
            return poLayer.get();
        }
    }
    return nullptr;
}

void OGRSXFDataSource::CreateLayers()
{
    // default layers set
    m_apoLayers.emplace_back(cpl::make_unique<OGRSXFLayer>(
        fpSXF, &hIOMutex, static_cast<GByte>(0), CPLString("SYSTEM"),
        oSXFPassport.version, oSXFPassport.stMapDescription));
    auto pLayer = m_apoLayers.back().get();

    // default codes
    for (unsigned int i = 1000000001; i < 1000000015; i++)
    {
        pLayer->AddClassifyCode(i);
    }
    pLayer->AddClassifyCode(91000000);

    m_apoLayers.emplace_back(cpl::make_unique<OGRSXFLayer>(
        fpSXF, &hIOMutex, static_cast<GByte>(255), CPLString("Not_Classified"),
        oSXFPassport.version, oSXFPassport.stMapDescription));
}

void OGRSXFDataSource::CreateLayers(VSILFILE *fpRSC,
                                    const char *const *papszOpenOpts)
{

    RSCHeader stRSCFileHeader;
    int nObjectsRead = static_cast<int>(
        VSIFReadL(&stRSCFileHeader, sizeof(stRSCFileHeader), 1, fpRSC));

    if (nObjectsRead != 1)
    {
        CPLError(CE_Warning, CPLE_None, "RSC head read failed");
        return;
    }

    CPL_LSBPTR32(&(stRSCFileHeader.nFileLength));
    CPL_LSBPTR32(&(stRSCFileHeader.nVersion));
    CPL_LSBPTR32(&(stRSCFileHeader.nEncoding));
    CPL_LSBPTR32(&(stRSCFileHeader.nFileState));
    CPL_LSBPTR32(&(stRSCFileHeader.nFileModState));
    CPL_LSBPTR32(&(stRSCFileHeader.nLang));
    CPL_LSBPTR32(&(stRSCFileHeader.nNextID));
    CPL_LSBPTR32(&(stRSCFileHeader.nScale));

#define SWAP_SECTION(x)                                                        \
    CPL_LSBPTR32(&(x.nOffset));                                                \
    CPL_LSBPTR32(&(x.nLength));                                                \
    CPL_LSBPTR32(&(x.nRecordCount));

    SWAP_SECTION(stRSCFileHeader.Objects);
    SWAP_SECTION(stRSCFileHeader.Semantic);
    SWAP_SECTION(stRSCFileHeader.ClassifySemantic);
    SWAP_SECTION(stRSCFileHeader.Defaults);
    SWAP_SECTION(stRSCFileHeader.Semantics);
    SWAP_SECTION(stRSCFileHeader.Layers);
    SWAP_SECTION(stRSCFileHeader.Limits);
    SWAP_SECTION(stRSCFileHeader.Parameters);
    SWAP_SECTION(stRSCFileHeader.Print);
    SWAP_SECTION(stRSCFileHeader.Palettes);
    SWAP_SECTION(stRSCFileHeader.Fonts);
    SWAP_SECTION(stRSCFileHeader.Libs);
    SWAP_SECTION(stRSCFileHeader.ImageParams);
    SWAP_SECTION(stRSCFileHeader.Tables);
    CPL_LSBPTR32(&(stRSCFileHeader.nFontEnc));
    CPL_LSBPTR32(&(stRSCFileHeader.nColorsInPalette));

    GByte szLayersID[4];
    struct _layer
    {
        GUInt32 nLength;
        char szName[32];
        char szShortName[16];
        GByte nNo;
        // cppcheck-suppress unusedStructMember
        GByte nPos;
        // cppcheck-suppress unusedStructMember
        GUInt16 nSemanticCount;
    };

    VSIFSeekL(fpRSC, stRSCFileHeader.Layers.nOffset - sizeof(szLayersID),
              SEEK_SET);
    VSIFReadL(&szLayersID, sizeof(szLayersID), 1, fpRSC);
    vsi_l_offset nOffset = stRSCFileHeader.Layers.nOffset;
    _layer LAYER;

    for (GUInt32 i = 0; i < stRSCFileHeader.Layers.nRecordCount; ++i)
    {
        VSIFReadL(&LAYER, sizeof(LAYER), 1, fpRSC);
        CPL_LSBPTR32(&(LAYER.nLength));
        CPL_LSBPTR16(&(LAYER.nSemanticCount));
        bool bLayerFullName = CPLTestBool(CSLFetchNameValueDef(
            papszOpenOpts, "SXF_LAYER_FULLNAME",
            CPLGetConfigOption("SXF_LAYER_FULLNAME", "NO")));
        char *pszRecoded = nullptr;
        if (bLayerFullName)
        {
            if (LAYER.szName[0] == 0)
                pszRecoded = CPLStrdup("Unnamed");
            else if (stRSCFileHeader.nFontEnc == 125)
                pszRecoded = CPLRecode(LAYER.szName, "KOI8-R", CPL_ENC_UTF8);
            else if (stRSCFileHeader.nFontEnc == 126)
                pszRecoded = CPLRecode(LAYER.szName, "CP1251", CPL_ENC_UTF8);
            else
                pszRecoded = CPLStrdup(LAYER.szName);

            m_apoLayers.emplace_back(cpl::make_unique<OGRSXFLayer>(
                fpSXF, &hIOMutex, LAYER.nNo, CPLString(pszRecoded),
                oSXFPassport.version, oSXFPassport.stMapDescription));
        }
        else
        {
            if (LAYER.szShortName[0] == 0)
                pszRecoded = CPLStrdup("Unnamed");
            else if (stRSCFileHeader.nFontEnc == 125)
                pszRecoded =
                    CPLRecode(LAYER.szShortName, "KOI8-R", CPL_ENC_UTF8);
            else if (stRSCFileHeader.nFontEnc == 126)
                pszRecoded =
                    CPLRecode(LAYER.szShortName, "CP1251", CPL_ENC_UTF8);
            else
                pszRecoded = CPLStrdup(LAYER.szShortName);

            m_apoLayers.emplace_back(cpl::make_unique<OGRSXFLayer>(
                fpSXF, &hIOMutex, LAYER.nNo, CPLString(pszRecoded),
                oSXFPassport.version, oSXFPassport.stMapDescription));
        }
        CPLFree(pszRecoded);

        nOffset += LAYER.nLength;
        VSIFSeekL(fpRSC, nOffset, SEEK_SET);
    }

    m_apoLayers.emplace_back(cpl::make_unique<OGRSXFLayer>(
        fpSXF, &hIOMutex, static_cast<GByte>(255), CPLString("Not_Classified"),
        oSXFPassport.version, oSXFPassport.stMapDescription));

    char szObjectsID[4];
    struct _object
    {
        unsigned nLength;
        unsigned nClassifyCode;
        // cppcheck-suppress unusedStructMember
        unsigned nObjectNumber;
        // cppcheck-suppress unusedStructMember
        unsigned nObjectCode;
        char szShortName[32];
        char szName[32];
        // cppcheck-suppress unusedStructMember
        char szGeomType;
        char szLayernNo;
        // cppcheck-suppress unusedStructMember
        char szUnimportantSeg[14];
    };

    VSIFSeekL(fpRSC, stRSCFileHeader.Objects.nOffset - sizeof(szObjectsID),
              SEEK_SET);
    VSIFReadL(&szObjectsID, sizeof(szObjectsID), 1, fpRSC);
    nOffset = stRSCFileHeader.Objects.nOffset;
    _object OBJECT;

    for (GUInt32 i = 0; i < stRSCFileHeader.Objects.nRecordCount; ++i)
    {
        VSIFReadL(&OBJECT, sizeof(_object), 1, fpRSC);
        CPL_LSBPTR32(&(OBJECT.nLength));
        CPL_LSBPTR32(&(OBJECT.nClassifyCode));
        CPL_LSBPTR32(&(OBJECT.nObjectNumber));
        CPL_LSBPTR32(&(OBJECT.nObjectCode));

        OGRSXFLayer *pLayer = GetLayerById(OBJECT.szLayernNo);
        if (nullptr != pLayer)
        {
            char *pszRecoded = nullptr;
            if (OBJECT.szName[0] == 0)
                pszRecoded = CPLStrdup("Unnamed");
            else if (stRSCFileHeader.nFontEnc == 125)
                pszRecoded = CPLRecode(OBJECT.szName, "KOI8-R", CPL_ENC_UTF8);
            else if (stRSCFileHeader.nFontEnc == 126)
                pszRecoded = CPLRecode(OBJECT.szName, "CP1251", CPL_ENC_UTF8);
            else
                pszRecoded =
                    CPLStrdup(OBJECT.szName);  // already in  CPL_ENC_UTF8

            pLayer->AddClassifyCode(OBJECT.nClassifyCode, pszRecoded);
            // printf("%d;%s\n", OBJECT.nClassifyCode, OBJECT.szName);
            CPLFree(pszRecoded);
        }

        nOffset += OBJECT.nLength;
        VSIFSeekL(fpRSC, nOffset, SEEK_SET);
    }
}
