/******************************************************************************
 *
 * Project:  OGDI Bridge
 * Purpose:  Implements OGROGDIDataSource class.
 * Author:   Daniel Morissette, danmo@videotron.ca
 *           (Based on some code contributed by Frank Warmerdam :)
 *
 ******************************************************************************
 * Copyright (c) 2000, Daniel Morissette
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "ogrogdi.h"

#include "cpl_conv.h"
#include "cpl_string.h"

/************************************************************************/
/*                         OGROGDIDataSource()                          */
/************************************************************************/

OGROGDIDataSource::OGROGDIDataSource()
    : m_papoLayers(nullptr), m_nLayers(0), m_nClientID(-1),
      m_poSpatialRef(nullptr), m_poCurrentLayer(nullptr),
      m_bLaunderLayerNames(
          CPLTestBool(CPLGetConfigOption("OGR_OGDI_LAUNDER_LAYER_NAMES", "NO")))
{
    m_sGlobalBounds.north = 0.0;
    m_sGlobalBounds.south = 0.0;
    m_sGlobalBounds.east = 0.0;
    m_sGlobalBounds.west = 0.0;
    m_sGlobalBounds.ns_res = 0.0;
    m_sGlobalBounds.ew_res = 0.0;
}

/************************************************************************/
/*                         ~OGROGDIDataSource()                         */
/************************************************************************/

OGROGDIDataSource::~OGROGDIDataSource()

{
    for (int i = 0; i < m_nLayers; i++)
        delete m_papoLayers[i];
    CPLFree(m_papoLayers);

    if (m_nClientID != -1)
    {
        ecs_Result *psResult = cln_DestroyClient(m_nClientID);
        ecs_CleanUp(psResult);
    }

    if (m_poSpatialRef)
        m_poSpatialRef->Release();
}

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

int OGROGDIDataSource::Open(const char *pszNewName)

{
    CPLAssert(m_nLayers == 0);

    /* -------------------------------------------------------------------- */
    /*      Parse the dataset name.                                         */
    /*      i.e.                                                            */
    /* gltp://<hostname>/<format>/<path_to_dataset>[:<layer_name>:<Family>] */
    /*                                                                      */
    /*      Where <Family> is one of: Line, Area, Point, and Text           */
    /* -------------------------------------------------------------------- */
    if (!STARTS_WITH_CI(pszNewName, "gltp:"))
        return FALSE;

    char *pszWorkingName = CPLStrdup(pszNewName);

    char *pszFamily = strrchr(pszWorkingName, ':');
    CPLAssert(pszFamily);

    // Don't treat drive name colon as family separator.  It is assumed
    // that drive names are on character long, and preceded by a
    // forward or backward slash.
    if (pszFamily < pszWorkingName + 2 || pszFamily[-2] == '/' ||
        pszFamily[-2] == '\\')
        pszFamily = nullptr;

    char *pszLyrName = nullptr;
    if (pszFamily && pszFamily != pszWorkingName + 4)
    {
        *pszFamily = '\0';
        pszFamily++;

        pszLyrName = strrchr(pszWorkingName, ':');
        if (pszLyrName == pszWorkingName + 4)
            pszLyrName = nullptr;

        if (pszLyrName != nullptr)
        {
            *pszLyrName = '\0';
            pszLyrName++;
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Open the client interface.                                      */
    /* -------------------------------------------------------------------- */
    ecs_Result *psResult = cln_CreateClient(&m_nClientID, pszWorkingName);

    if (ECSERROR(psResult))
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "OGDI DataSource Open Failed: %s\n",
                 psResult->message ? psResult->message : "(no message string)");
        CPLFree(pszWorkingName);
        return FALSE;
    }

    /* -------------------------------------------------------------------- */
    /*      Capture some information from the file.                         */
    /* -------------------------------------------------------------------- */
    psResult = cln_GetGlobalBound(m_nClientID);
    if (ECSERROR(psResult))
    {
        CPLError(CE_Failure, CPLE_AppDefined, "GetGlobalBound failed: %s",
                 psResult->message ? psResult->message : "(no message string)");
        CPLFree(pszWorkingName);
        return FALSE;
    }

    m_sGlobalBounds = ECSREGION(psResult);

    psResult = cln_GetServerProjection(m_nClientID);
    if (ECSERROR(psResult))
    {
        CPLError(CE_Failure, CPLE_AppDefined, "GetServerProjection failed: %s",
                 psResult->message ? psResult->message : "(no message string)");
        CPLFree(pszWorkingName);
        return FALSE;
    }

    m_poSpatialRef = new OGRSpatialReference;
    m_poSpatialRef->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);

    CPLString osProjString(ECSTEXT(psResult));
    osProjString.replaceAll("datum=wgs84", "datum=WGS84");
    if (m_poSpatialRef->importFromProj4(osProjString) != OGRERR_NONE)
    {
        CPLError(CE_Warning, CPLE_NotSupported,
                 "untranslatable PROJ.4 projection: %s\n",
                 ECSTEXT(psResult) ? ECSTEXT(psResult) : "(no message string)");
        delete m_poSpatialRef;
        m_poSpatialRef = nullptr;
    }

    /* -------------------------------------------------------------------- */
    /*      Select the global region.                                       */
    /* -------------------------------------------------------------------- */
    psResult = cln_SelectRegion(m_nClientID, &m_sGlobalBounds);
    if (ECSERROR(psResult))
    {
        CPLError(CE_Failure, CPLE_AppDefined, "SelectRegion failed: %s",
                 psResult->message ? psResult->message : "(no message string)");
        CPLFree(pszWorkingName);
        return FALSE;
    }

    /* -------------------------------------------------------------------- */
    /*      If an explicit layer was selected, just create that layer.      */
    /* -------------------------------------------------------------------- */
    m_poCurrentLayer = nullptr;

    if (pszLyrName != nullptr)
    {
        ecs_Family eFamily;

        if (EQUAL(pszFamily, "Line"))
            eFamily = Line;
        else if (EQUAL(pszFamily, "Area"))
            eFamily = Area;
        else if (EQUAL(pszFamily, "Point"))
            eFamily = Point;
        else if (EQUAL(pszFamily, "Text"))
            eFamily = Text;
        else
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Invalid or unsupported family name (%s) in URL %s\n",
                     pszFamily, pszNewName);
            CPLFree(pszWorkingName);
            return FALSE;
        }

        IAddLayer(pszLyrName, eFamily);
    }

    /* -------------------------------------------------------------------- */
    /*      Otherwise create a layer for every layer in the capabilities.   */
    /* -------------------------------------------------------------------- */
    else
    {
        // Call cln_UpdateDictionary so as to be able to report errors
        // since cln_GetLayerCapabilities() cannot do that
        // Help in the case of DNC17/COA17A that has a missing env/fcs file
        char *szEmpty = CPLStrdup("");
        psResult = cln_UpdateDictionary(m_nClientID, szEmpty);
        CPLFree(szEmpty);
        if (ECSERROR(psResult))
        {
            CPLError(CE_Failure, CPLE_AppDefined, "UpdateDictionary failed: %s",
                     psResult->message ? psResult->message
                                       : "(no message string)");
            CPLFree(pszWorkingName);
            return FALSE;
        }

        const ecs_LayerCapabilities *psLayerCap = nullptr;
        for (int i = 0;
             (psLayerCap = cln_GetLayerCapabilities(m_nClientID, i)) != nullptr;
             i++)
        {
            if (psLayerCap->families[Point])
                IAddLayer(psLayerCap->name, Point);
            if (psLayerCap->families[Line])
                IAddLayer(psLayerCap->name, Line);
            if (psLayerCap->families[Area])
                IAddLayer(psLayerCap->name, Area);
            if (psLayerCap->families[Text])
                IAddLayer(psLayerCap->name, Text);
        }
    }

    CPLFree(pszWorkingName);

    return TRUE;
}

/************************************************************************/
/*                             IAddLayer()                              */
/*                                                                      */
/*      Internal helper function for adding one existing layer to       */
/*      the datasource.                                                 */
/************************************************************************/

void OGROGDIDataSource::IAddLayer(const char *pszLayerName, ecs_Family eFamily)

{
    m_papoLayers = (OGROGDILayer **)CPLRealloc(
        m_papoLayers, (m_nLayers + 1) * sizeof(OGROGDILayer *));

    m_papoLayers[m_nLayers++] = new OGROGDILayer(this, pszLayerName, eFamily);
}

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

OGRLayer *OGROGDIDataSource::GetLayer(int iLayer)

{
    if (iLayer < 0 || iLayer >= m_nLayers)
        return nullptr;
    else
        return m_papoLayers[iLayer];
}
