/******************************************************************************
 *
 * Project:  SDTS Translator
 * Purpose:  GDALDataset driver for SDTS Raster translator.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 1999, Frank Warmerdam
 * Copyright (c) 2008-2011, Even Rouault <even dot rouault at spatialys.com>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "gdal_frmts.h"
#include "gdal_pam.h"
#include "ogr_spatialref.h"
#include "sdts_al.h"

/**
 \file sdtsdataset.cpp

 exclude
*/

/************************************************************************/
/* ==================================================================== */
/*                              SDTSDataset                             */
/* ==================================================================== */
/************************************************************************/

class SDTSRasterBand;

class SDTSDataset final : public GDALPamDataset
{
    friend class SDTSRasterBand;

    SDTSTransfer *poTransfer;
    SDTSRasterReader *poRL;

    OGRSpatialReference m_oSRS{};

  public:
    SDTSDataset();
    virtual ~SDTSDataset();

    static GDALDataset *Open(GDALOpenInfo *);

    const OGRSpatialReference *GetSpatialRef() const override
    {
        return &m_oSRS;
    }

    virtual CPLErr GetGeoTransform(double *) override;
};

class SDTSRasterBand final : public GDALPamRasterBand
{
    friend class SDTSDataset;

    SDTSRasterReader *poRL;

  public:
    SDTSRasterBand(SDTSDataset *, int, SDTSRasterReader *);

    virtual CPLErr IReadBlock(int, int, void *) override;

    virtual double GetNoDataValue(int *pbSuccess) override;
    virtual const char *GetUnitType() override;
};

/************************************************************************/
/*                             SDTSDataset()                            */
/************************************************************************/

SDTSDataset::SDTSDataset() : poTransfer(nullptr), poRL(nullptr)
{
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
}

/************************************************************************/
/*                            ~SDTSDataset()                            */
/************************************************************************/

SDTSDataset::~SDTSDataset()

{
    FlushCache(true);

    if (poTransfer != nullptr)
        delete poTransfer;

    if (poRL != nullptr)
        delete poRL;
}

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

GDALDataset *SDTSDataset::Open(GDALOpenInfo *poOpenInfo)

{
    /* -------------------------------------------------------------------- */
    /*      Before trying SDTSOpen() we first verify that the first         */
    /*      record is in fact a SDTS file descriptor record.                */
    /* -------------------------------------------------------------------- */
    if (poOpenInfo->nHeaderBytes < 24)
        return nullptr;

    char *pachLeader = reinterpret_cast<char *>(poOpenInfo->pabyHeader);
    if (pachLeader[5] != '1' && pachLeader[5] != '2' && pachLeader[5] != '3')
        return nullptr;

    if (pachLeader[6] != 'L')
        return nullptr;

    if (pachLeader[8] != '1' && pachLeader[8] != ' ')
        return nullptr;

    /* -------------------------------------------------------------------- */
    /*      Try opening the dataset.                                        */
    /* -------------------------------------------------------------------- */
    SDTSTransfer *poTransfer = new SDTSTransfer;

    if (!poTransfer->Open(poOpenInfo->pszFilename))
    {
        delete poTransfer;
        return nullptr;
    }

    /* -------------------------------------------------------------------- */
    /*      Confirm the requested access is supported.                      */
    /* -------------------------------------------------------------------- */
    if (poOpenInfo->eAccess == GA_Update)
    {
        delete poTransfer;
        CPLError(CE_Failure, CPLE_NotSupported,
                 "The SDTS driver does not support update access to existing"
                 " datasets.\n");
        return nullptr;
    }

    /* -------------------------------------------------------------------- */
    /*      Find the first raster layer.  If there are none, abort          */
    /*      returning an error.                                             */
    /* -------------------------------------------------------------------- */
    SDTSRasterReader *poRL = nullptr;

    for (int i = 0; i < poTransfer->GetLayerCount(); i++)
    {
        if (poTransfer->GetLayerType(i) == SLTRaster)
        {
            poRL = poTransfer->GetLayerRasterReader(i);
            break;
        }
    }

    if (poRL == nullptr)
    {
        delete poTransfer;

        CPLError(CE_Warning, CPLE_AppDefined,
                 "%s is an SDTS transfer, but has no raster cell layers.\n"
                 "Perhaps it is a vector transfer?\n",
                 poOpenInfo->pszFilename);
        return nullptr;
    }

    /* -------------------------------------------------------------------- */
    /*      Initialize a corresponding GDALDataset.                         */
    /* -------------------------------------------------------------------- */
    SDTSDataset *poDS = new SDTSDataset();

    poDS->poTransfer = poTransfer;
    poDS->poRL = poRL;

    /* -------------------------------------------------------------------- */
    /*      Capture some information from the file that is of interest.     */
    /* -------------------------------------------------------------------- */
    poDS->nRasterXSize = poRL->GetXSize();
    poDS->nRasterYSize = poRL->GetYSize();

    /* -------------------------------------------------------------------- */
    /*      Create band information objects.                                */
    /* -------------------------------------------------------------------- */
    poDS->nBands = 1;
    poDS->papoBands = reinterpret_cast<GDALRasterBand **>(
        VSICalloc(sizeof(GDALRasterBand *), poDS->nBands));

    for (int i = 0; i < poDS->nBands; i++)
        poDS->SetBand(i + 1, new SDTSRasterBand(poDS, i + 1, poRL));

    /* -------------------------------------------------------------------- */
    /*      Try to establish the projection string.  For now we only        */
    /*      support UTM and GEO.                                            */
    /* -------------------------------------------------------------------- */
    SDTS_XREF *poXREF = poTransfer->GetXREF();

    if (EQUAL(poXREF->pszSystemName, "UTM"))
    {
        poDS->m_oSRS.SetUTM(poXREF->nZone);
    }
    else if (EQUAL(poXREF->pszSystemName, "GEO"))
    {
        /* we set datum later */
    }
    else
        poDS->m_oSRS.SetLocalCS(poXREF->pszSystemName);

    if (poDS->m_oSRS.IsLocal())
        /* don't try to set datum. */;
    else if (EQUAL(poXREF->pszDatum, "NAS"))
        poDS->m_oSRS.SetWellKnownGeogCS("NAD27");
    else if (EQUAL(poXREF->pszDatum, "NAX"))
        poDS->m_oSRS.SetWellKnownGeogCS("NAD83");
    else if (EQUAL(poXREF->pszDatum, "WGC"))
        poDS->m_oSRS.SetWellKnownGeogCS("WGS72");
    else /* if( EQUAL(poXREF->pszDatum, "WGE") ) or default */
        poDS->m_oSRS.SetWellKnownGeogCS("WGS84");

    /* -------------------------------------------------------------------- */
    /*      Get metadata from the IDEN file.                                */
    /* -------------------------------------------------------------------- */
    const char *pszIDENFilePath =
        poTransfer->GetCATD()->GetModuleFilePath("IDEN");
    if (pszIDENFilePath)
    {
        DDFModule oIDENFile;
        if (oIDENFile.Open(pszIDENFilePath))
        {
            DDFRecord *poRecord = nullptr;

            while ((poRecord = oIDENFile.ReadRecord()) != nullptr)
            {

                if (poRecord->GetStringSubfield("IDEN", 0, "MODN", 0) ==
                    nullptr)
                    continue;

                static const char *const fields[][2] = {
                    {"TITL", "TITLE"},
                    {"DAID", "DATASET_ID"},
                    {"DAST", "DATA_STRUCTURE"},
                    {"MPDT", "MAP_DATE"},
                    {"DCDT", "DATASET_CREATION_DATE"}};

                for (int i = 0; i < static_cast<int>(sizeof(fields)) /
                                        static_cast<int>(sizeof(fields[0]));
                     i++)
                {
                    const char *pszFieldValue =
                        poRecord->GetStringSubfield("IDEN", 0, fields[i][0], 0);
                    if (pszFieldValue)
                        poDS->SetMetadataItem(fields[i][1], pszFieldValue);
                }

                break;
            }
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Initialize any PAM information.                                 */
    /* -------------------------------------------------------------------- */
    poDS->SetDescription(poOpenInfo->pszFilename);
    poDS->TryLoadXML();

    /* -------------------------------------------------------------------- */
    /*      Check for external overviews.                                   */
    /* -------------------------------------------------------------------- */
    poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename,
                                poOpenInfo->GetSiblingFiles());

    return poDS;
}

/************************************************************************/
/*                          GetGeoTransform()                           */
/************************************************************************/

CPLErr SDTSDataset::GetGeoTransform(double *padfTransform)

{
    if (poRL->GetTransform(padfTransform))
        return CE_None;

    return CE_Failure;
}

/************************************************************************/
/* ==================================================================== */
/*                            SDTSRasterBand                             */
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                           SDTSRasterBand()                            */
/************************************************************************/

SDTSRasterBand::SDTSRasterBand(SDTSDataset *poDSIn, int nBandIn,
                               SDTSRasterReader *poRLIn)
    : poRL(poRLIn)
{
    poDS = poDSIn;
    nBand = nBandIn;

    if (poRL->GetRasterType() == SDTS_RT_INT16)
        eDataType = GDT_Int16;
    else
        eDataType = GDT_Float32;

    nBlockXSize = poRL->GetBlockXSize();
    nBlockYSize = poRL->GetBlockYSize();
}

/************************************************************************/
/*                             IReadBlock()                             */
/************************************************************************/

CPLErr SDTSRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)

{
    if (poRL->GetBlock(nBlockXOff, nBlockYOff, pImage))
        return CE_None;

    return CE_Failure;
}

/************************************************************************/
/*                           GetNoDataValue()                           */
/************************************************************************/

double SDTSRasterBand::GetNoDataValue(int *pbSuccess)

{
    if (pbSuccess != nullptr)
        *pbSuccess = TRUE;

    return -32766.0;
}

/************************************************************************/
/*                            GetUnitType()                             */
/************************************************************************/

const char *SDTSRasterBand::GetUnitType()

{
    if (EQUAL(poRL->szUNITS, "FEET"))
        return "ft";
    else if (STARTS_WITH_CI(poRL->szUNITS, "MET"))
        return "m";

    return poRL->szUNITS;
}

/************************************************************************/
/*                         GDALRegister_SDTS()                          */
/************************************************************************/

void GDALRegister_SDTS()

{
    if (GDALGetDriverByName("SDTS") != nullptr)
        return;

    GDALDriver *poDriver = new GDALDriver();

    poDriver->SetDescription("SDTS");
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "SDTS Raster");
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/sdts.html");
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "ddf");
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");

    poDriver->pfnOpen = SDTSDataset::Open;

    GetGDALDriverManager()->RegisterDriver(poDriver);
}
