/******************************************************************************
 *
 * Name:     ogdidataset.cpp
 * Project:  OGDI Bridge
 * Purpose:  Main driver for OGDI.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 1998, Frank Warmerdam
 * Copyright (c) 2007-2013, Even Rouault <even dot rouault at mines-paris dot org>
 *
 * 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 <cmath>
#include "ecs.h"
#include "gdal_frmts.h"
#include "gdal_priv.h"
#include "cpl_string.h"
#include "ogr_spatialref.h"

#include <algorithm>

CPL_CVSID("$Id: ogdidataset.cpp 36501 2016-11-25 14:09:24Z rouault $");

/************************************************************************/
/* ==================================================================== */
/*                              OGDIDataset                             */
/* ==================================================================== */
/************************************************************************/

class OGDIRasterBand;

class CPL_DLL OGDIDataset : public GDALDataset
{
    friend class OGDIRasterBand;

    int         nClientID;

    ecs_Region  sGlobalBounds;
    ecs_Region  sCurrentBounds;
    int         nCurrentBand;
    int         nCurrentIndex;

    char        *pszProjection;

    static CPLErr CollectLayers(int, char***,char***);
    static CPLErr OverrideGlobalInfo(OGDIDataset*,const char *);

    void        AddSubDataset( const char *pszType, const char *pszLayer );
    char        **papszSubDatasets;

  public:
    OGDIDataset();
    virtual ~OGDIDataset();

    static GDALDataset *Open( GDALOpenInfo * );

    int GetClientID() { return nClientID; }

    virtual const char *GetProjectionRef(void) override;
    virtual CPLErr GetGeoTransform( double * ) override;

    virtual char **GetMetadataDomainList() override;
    virtual char **GetMetadata( const char * pszDomain = "" ) override;
};

/************************************************************************/
/* ==================================================================== */
/*                            OGDIRasterBand                             */
/* ==================================================================== */
/************************************************************************/

class OGDIRasterBand : public GDALRasterBand
{
    friend class OGDIDataset;

    int nOGDIImageType; /* i.e. 1 for RGB */

    char        *pszLayerName;
    ecs_Family  eFamily;

    int         nComponent; /* varies only for RGB layers */

    GDALColorTable *poCT;

    virtual CPLErr IRasterIO( GDALRWFlag, int, int, int, int,
                              void *, int, int, GDALDataType,
                              GSpacing nPixelSpace,
                              GSpacing nLineSpace,
                              GDALRasterIOExtraArg* psExtraArg ) override;

    CPLErr         EstablishAccess( int nXOff, int nYOff,
                                    int nXSize, int nYSize,
                                    int nBufXSize, int nBufYSize );

  public:

                   OGDIRasterBand( OGDIDataset *, int, const char *,
                                   ecs_Family, int );
    virtual ~OGDIRasterBand();

    virtual CPLErr IReadBlock( int, int, void * ) override;
    virtual int    HasArbitraryOverviews() override;
    virtual GDALColorInterp GetColorInterpretation() override;
    virtual GDALColorTable *GetColorTable() override;

    virtual CPLErr AdviseRead( int nXOff, int nYOff, int nXSize, int nYSize,
                               int nBufXSize, int nBufYSize,
                               GDALDataType eDT, char **papszOptions ) override;
};

/************************************************************************/
/*                           OGDIRasterBand()                            */
/************************************************************************/

OGDIRasterBand::OGDIRasterBand( OGDIDataset *poDSIn, int nBandIn,
                                const char * pszName, ecs_Family eFamilyIn,
                                int nComponentIn ) :
    nOGDIImageType(0),
    pszLayerName(CPLStrdup(pszName)),
    eFamily(eFamilyIn),
    nComponent(nComponentIn)
{
    poDS = poDSIn;
    nBand = nBandIn;
    poCT = NULL;

/* -------------------------------------------------------------------- */
/*      Make this layer current.                                        */
/* -------------------------------------------------------------------- */
    EstablishAccess( 0, 0,
                     poDS->GetRasterXSize(), poDS->GetRasterYSize(),
                     poDS->GetRasterXSize(), poDS->GetRasterYSize() );

/* -------------------------------------------------------------------- */
/*      Get the raster info.                                            */
/* -------------------------------------------------------------------- */
    ecs_Result *psResult = cln_GetRasterInfo( poDSIn->nClientID );
    if( ECSERROR(psResult) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "%s", psResult->message );
    }

/* -------------------------------------------------------------------- */
/*      Establish if we have meaningful colortable information.         */
/* -------------------------------------------------------------------- */
    if( eFamily == Matrix )
    {
        int     i;

        poCT = new GDALColorTable();

        for( i = 0; i < (int) ECSRASTERINFO(psResult).cat.cat_len; i++ ) {
            GDALColorEntry   sEntry;

            sEntry.c1 = ECSRASTERINFO(psResult).cat.cat_val[i].r;
            sEntry.c2 = ECSRASTERINFO(psResult).cat.cat_val[i].g;
            sEntry.c3 = ECSRASTERINFO(psResult).cat.cat_val[i].b;
            sEntry.c4 = 255;

            poCT->SetColorEntry( static_cast<int>(ECSRASTERINFO(psResult).cat.cat_val[i].no_cat),
                                 &sEntry );
        }
    }

/* -------------------------------------------------------------------- */
/*      Get the GDAL data type.  Eventually we might use the            */
/*      category info to establish what to do here.                     */
/* -------------------------------------------------------------------- */
    if( eFamily == Matrix )
        eDataType = GDT_Byte;
    else if( ECSRASTERINFO(psResult).width == 1 )
        eDataType = GDT_Byte;
    else if( ECSRASTERINFO(psResult).width == 2 )
        eDataType = GDT_Byte;
    else if( ECSRASTERINFO(psResult).width == 3 )
        eDataType = GDT_UInt16;
    else if( ECSRASTERINFO(psResult).width == 4 )
        eDataType = GDT_Int16;
    else if( ECSRASTERINFO(psResult).width == 5 )
        eDataType = GDT_Int32;
    else
        eDataType = GDT_UInt32;

    nOGDIImageType = ECSRASTERINFO(psResult).width;

/* -------------------------------------------------------------------- */
/*      Currently only works for strips                                 */
/* -------------------------------------------------------------------- */
    nBlockXSize = poDS->GetRasterXSize();
    nBlockYSize = 1;
}

/************************************************************************/
/*                          ~OGDIRasterBand()                           */
/************************************************************************/

OGDIRasterBand::~OGDIRasterBand()

{
    FlushCache();
    CPLFree( pszLayerName );
    delete poCT;
}

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

CPLErr OGDIRasterBand::IReadBlock( int, int nBlockYOff, void * pImage )

{
    GDALRasterIOExtraArg sExtraArg;
    INIT_RASTERIO_EXTRA_ARG(sExtraArg);

    return IRasterIO( GF_Read, 0, nBlockYOff, nBlockXSize, 1,
                      pImage, nBlockXSize, 1, eDataType,
                      GDALGetDataTypeSize(eDataType)/8, 0, &sExtraArg );
}

/************************************************************************/
/*                             IRasterIO()                              */
/************************************************************************/

CPLErr OGDIRasterBand::IRasterIO( CPL_UNUSED GDALRWFlag eRWFlag,
                                  int nXOff, int nYOff, int nXSize, int nYSize,
                                  void * pData, int nBufXSize, int nBufYSize,
                                  GDALDataType eBufType,
                                  GSpacing nPixelSpace,
                                  GSpacing nLineSpace,
                                  CPL_UNUSED GDALRasterIOExtraArg* psExtraArg )
{
    OGDIDataset *poODS = (OGDIDataset *) poDS;
    CPLErr    eErr;
#ifdef notdef
    CPLDebug( "OGDIRasterBand",
              "RasterIO(%d,%d,%d,%d -> %dx%d)",
              nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize );
#endif

/* -------------------------------------------------------------------- */
/*      Establish access at the desired resolution.                     */
/* -------------------------------------------------------------------- */
    eErr = EstablishAccess( nXOff, nYOff, nXSize, nYSize,
                            nBufXSize, nBufYSize );
    if( eErr != CE_None )
        return eErr;

/* -------------------------------------------------------------------- */
/*      Read back one scanline at a time, till request is satisfied.    */
/* -------------------------------------------------------------------- */
    int      iScanline;

    for( iScanline = 0; iScanline < nBufYSize; iScanline++ )
    {
        void *pLineData = ((unsigned char *) pData) + iScanline * nLineSpace;

        poODS->nCurrentIndex++;
        ecs_Result *psResult = cln_GetNextObject( poODS->nClientID );

        if( ECSERROR(psResult) )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "%s", psResult->message );
            return CE_Failure;
        }

        if( eFamily == Matrix )
        {
            GDALCopyWords( ECSRASTER(psResult), GDT_UInt32, 4,
                           pLineData, eBufType, static_cast<int>(nPixelSpace),
                           nBufXSize );
        }
        else if( nOGDIImageType == 1 )
        {
            GDALCopyWords( ((GByte *) ECSRASTER(psResult)) + nComponent,
                           GDT_Byte, 4,
                           pLineData, eBufType, static_cast<int>(nPixelSpace), nBufXSize );

            if( nComponent == 3 )
            {
                int i;

                for( i = 0; i < nBufXSize; i++ )
                {
                    if( ((GByte *) pLineData)[i] != 0 )
                        ((GByte *) pLineData)[i] = 255;
                    else
                        ((GByte *) pLineData)[i] = 0;
                }
            }
        }
        else if( nOGDIImageType == 2 )
        {
            GDALCopyWords( ECSRASTER(psResult), GDT_Byte, 1,
                           pLineData, eBufType, static_cast<int>(nPixelSpace),
                           nBufXSize );
        }
        else if( nOGDIImageType == 3 )
        {
            GDALCopyWords( ECSRASTER(psResult), GDT_UInt16, 2,
                           pLineData, eBufType, static_cast<int>(nPixelSpace),
                           nBufXSize );
        }
        else if( nOGDIImageType == 4 )
        {
            GDALCopyWords( ECSRASTER(psResult), GDT_Int16, 2,
                           pLineData, eBufType, static_cast<int>(nPixelSpace),
                           nBufXSize );
        }
        else if( nOGDIImageType == 5 )
        {
            GDALCopyWords( ECSRASTER(psResult), GDT_Int32, 4,
                           pLineData, eBufType, static_cast<int>(nPixelSpace),
                           nBufXSize );
        }
    }

    return CE_None;
}

/************************************************************************/
/*                       HasArbitraryOverviews()                        */
/************************************************************************/

int OGDIRasterBand::HasArbitraryOverviews()

{
    return TRUE;
}

/************************************************************************/
/*                          EstablishAccess()                           */
/************************************************************************/

CPLErr OGDIRasterBand::EstablishAccess( int nXOff, int nYOff,
                                        int nWinXSize, int nWinYSize,
                                        int nBufXSize, int nBufYSize )

{
    ecs_Result   *psResult = NULL;
    OGDIDataset  *poODS = (OGDIDataset *) poDS;

/* -------------------------------------------------------------------- */
/*      Is this already the current band?  If not, make it so now.      */
/* -------------------------------------------------------------------- */
    if( poODS->nCurrentBand != nBand )
    {
        ecs_LayerSelection sSelection;

        sSelection.Select = pszLayerName;
        sSelection.F = eFamily;

        CPLDebug( "OGDIRasterBand", "<EstablishAccess: SelectLayer(%s)>",
                  pszLayerName );
        psResult = cln_SelectLayer( poODS->nClientID, &sSelection );
        if( ECSERROR(psResult) )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "%s", psResult->message );
            return CE_Failure;
        }

        poODS->nCurrentBand = nBand;
        poODS->nCurrentIndex = -1;
    }

/* -------------------------------------------------------------------- */
/*      What region would represent this resolution and window?         */
/* -------------------------------------------------------------------- */
    ecs_Region   sWin;
    double       dfNSTolerance = 0.0000001;

    sWin.west = nXOff * poODS->sGlobalBounds.ew_res
        + poODS->sGlobalBounds.west;
    sWin.east = (nXOff+nWinXSize) * poODS->sGlobalBounds.ew_res
        + poODS->sGlobalBounds.west;
    sWin.ew_res = poODS->sGlobalBounds.ew_res*(nWinXSize/(double)nBufXSize);

    sWin.north = poODS->sGlobalBounds.north
        - nYOff*poODS->sGlobalBounds.ns_res;
    if( nBufYSize == 1 && nWinYSize == 1 )
    {
        sWin.ns_res = sWin.ew_res
            * (poODS->sGlobalBounds.ns_res / poODS->sGlobalBounds.ew_res);
        nWinYSize = (int) ((sWin.north - poODS->sGlobalBounds.south + sWin.ns_res*0.9)
                                / sWin.ns_res);

        sWin.south = sWin.north - nWinYSize * sWin.ns_res;
        dfNSTolerance = std::max(poODS->sCurrentBounds.ns_res, sWin.ns_res);
    }
    else if( nBufYSize == 1 )
    {
        sWin.ns_res = poODS->sGlobalBounds.ns_res
            *(nWinYSize/(double)nBufYSize);
        nWinYSize = (int) ((sWin.north - poODS->sGlobalBounds.south + sWin.ns_res*0.9)
                                / sWin.ns_res);

        sWin.south = sWin.north - nWinYSize * sWin.ns_res;
        dfNSTolerance = std::max(poODS->sCurrentBounds.ns_res, sWin.ns_res);
    }
    else
    {
        sWin.ns_res = poODS->sGlobalBounds.ns_res
            *(nWinYSize/(double)nBufYSize);
        sWin.south = sWin.north - nWinYSize * sWin.ns_res;
        dfNSTolerance = sWin.ns_res * 0.001;
    }

    if( poODS->nCurrentIndex != 0
        || std::abs(sWin.west - poODS->sCurrentBounds.west) > 0.0001
        || std::abs(sWin.east - poODS->sCurrentBounds.east) > 0.0001
        || std::abs(sWin.north - (poODS->sCurrentBounds.north - poODS->nCurrentIndex * poODS->sCurrentBounds.ns_res)) > dfNSTolerance
        || std::abs(sWin.ew_res/poODS->sCurrentBounds.ew_res - 1.0) > 0.0001
        || std::abs(sWin.ns_res - poODS->sCurrentBounds.ns_res) > dfNSTolerance )
    {
        CPLDebug( "OGDIRasterBand",
                  "<EstablishAccess: Set Region(%d,%d,%d,%d,%d,%d>",
                  nXOff, nYOff, nWinXSize, nWinYSize, nBufXSize, nBufYSize );

        psResult = cln_SelectRegion( poODS->nClientID, &sWin );
        if( ECSERROR(psResult) )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "%s", psResult->message );
            return CE_Failure;
        }

        poODS->sCurrentBounds = sWin;
        poODS->nCurrentIndex = 0;
    }

    return CE_None;
}

/************************************************************************/
/*                       GetColorInterpretation()                       */
/************************************************************************/

GDALColorInterp OGDIRasterBand::GetColorInterpretation()

{
    if( poCT != NULL )
        return GCI_PaletteIndex;
    else if( nOGDIImageType == 1 && eFamily == Image && nComponent == 0 )
        return GCI_RedBand;
    else if( nOGDIImageType == 1 && eFamily == Image && nComponent == 1 )
        return GCI_GreenBand;
    else if( nOGDIImageType == 1 && eFamily == Image && nComponent == 2 )
        return GCI_BlueBand;
    else if( nOGDIImageType == 1 && eFamily == Image && nComponent == 3 )
        return GCI_AlphaBand;
    else
        return GCI_Undefined;
}

/************************************************************************/
/*                           GetColorTable()                            */
/************************************************************************/

GDALColorTable *OGDIRasterBand::GetColorTable()

{
    return poCT;
}

/************************************************************************/
/*                             AdviseRead()                             */
/*                                                                      */
/*      Allow the application to give us a hint in advance how they     */
/*      want the data.                                                  */
/************************************************************************/

CPLErr OGDIRasterBand::AdviseRead( int nXOff, int nYOff,
                                   int nXSize, int nYSize,
                                   int nBufXSize, int nBufYSize,
                                   CPL_UNUSED GDALDataType eDT,
                                   CPL_UNUSED char **papszOptions )

{
    return EstablishAccess( nXOff, nYOff, nXSize, nYSize,
                            nBufXSize, nBufYSize );
}

/************************************************************************/
/* ==================================================================== */
/*                            OGDIDataset                               */
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                            OGDIDataset()                            */
/************************************************************************/

OGDIDataset::OGDIDataset() :
    nClientID(-1),
    nCurrentBand(-1),
    nCurrentIndex(-1),
    pszProjection(NULL),
    papszSubDatasets(NULL)
{
    sGlobalBounds.north = 0.0;
    sGlobalBounds.south = 0.0;
    sGlobalBounds.east = 0.0;
    sGlobalBounds.west = 0.0;
    sGlobalBounds.ns_res = 0.0;
    sGlobalBounds.ew_res = 0.0;

    sCurrentBounds.north = 0.0;
    sCurrentBounds.south = 0.0;
    sCurrentBounds.east = 0.0;
    sCurrentBounds.west = 0.0;
    sCurrentBounds.ns_res = 0.0;
    sCurrentBounds.ew_res = 0.0;
}

/************************************************************************/
/*                           ~OGDIDataset()                            */
/************************************************************************/

OGDIDataset::~OGDIDataset()

{
    cln_DestroyClient( nClientID );
    CSLDestroy( papszSubDatasets );
    CPLFree( pszProjection );
}

/************************************************************************/
/*                      GetMetadataDomainList()                         */
/************************************************************************/

char **OGDIDataset::GetMetadataDomainList()
{
    return BuildMetadataDomainList(GDALDataset::GetMetadataDomainList(),
                                   TRUE,
                                   "SUBDATASETS", NULL);
}

/************************************************************************/
/*                            GetMetadata()                             */
/************************************************************************/

char **OGDIDataset::GetMetadata( const char *pszDomain )

{
    if( pszDomain != NULL && EQUAL(pszDomain,"SUBDATASETS") )
        return papszSubDatasets;
    else
        return GDALDataset::GetMetadata( pszDomain );
}

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

GDALDataset *OGDIDataset::Open( GDALOpenInfo * poOpenInfo )

{
    int nClientID;
    char **papszImages = NULL;
    char **papszMatrices = NULL;

    if( !STARTS_WITH_CI(poOpenInfo->pszFilename, "gltp:") )
        return NULL ;

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

/* -------------------------------------------------------------------- */
/*      Has the user hardcoded a layer and family in the URL?           */
/*      Honour quoted strings for the layer name, since some layers     */
/*      (i.e. RPF/CADRG) have embedded colons.                           */
/* -------------------------------------------------------------------- */
    int nC1 = -1;
    int nC2 = -1;
    int bInQuotes = FALSE;
    char *pszURL = CPLStrdup(poOpenInfo->pszFilename);

    for( int i = static_cast<int>(strlen(pszURL))-1; i > 0; i-- )
    {
        if( pszURL[i] == '/' )
            break;

        if( pszURL[i] == '"' && pszURL[i-1] != '\\' )
            bInQuotes = !bInQuotes;

        else if( pszURL[i] == ':' && !bInQuotes )
        {
            if( nC1 == -1 )
            {
                nC1 = i;
                pszURL[nC1] = '\0';
            }
            else if( nC2 == -1 )
            {
                nC2 = i;
                pszURL[nC2] = '\0';
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      If we got a "family", and it is a vector family then return     */
/*      quietly.                                                        */
/* -------------------------------------------------------------------- */
    if( nC2 != -1
        && !EQUAL(pszURL+nC1+1,"Matrix")
        && !EQUAL(pszURL+nC1+1,"Image") )
    {
        CPLFree( pszURL );
        return NULL;
    }

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

    if( ECSERROR(psResult) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "%s", psResult->message );
        CPLFree(pszURL);
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      collect the list of images and matrices available.              */
/* -------------------------------------------------------------------- */
    if( nC2 == -1 )
    {
        CollectLayers( nClientID, &papszImages, &papszMatrices );
    }
    else
    {
        char *pszLayerName = CPLStrdup( pszURL+nC2+1 );

        if( pszLayerName[0] == '"' )
        {
            int nOut = 0;

            for( int i = 1; pszLayerName[i] != '\0'; i++ )
            {
                if( pszLayerName[i+1] == '"' && pszLayerName[i] == '\\' )
                    pszLayerName[nOut++] = pszLayerName[++i];
                else if( pszLayerName[i] != '"' )
                    pszLayerName[nOut++] = pszLayerName[i];
                else
                    break;
            }
            pszLayerName[nOut] = '\0';
        }

        if( EQUAL(pszURL+nC1+1,"Image") )
            papszImages = CSLAddString( papszImages, pszLayerName );
        else
            papszMatrices = CSLAddString( papszMatrices, pszLayerName );

        CPLFree( pszLayerName );
    }

    CPLFree( pszURL );

/* -------------------------------------------------------------------- */
/*      If this is a 3.1 server (i.e, it support                         */
/*      cln_GetLayerCapabilities()) and it has no raster layers then    */
/*      we can assume it must be a vector datastore.  End without an    */
/*      error in case the application wants to try this through         */
/*      OGR.                                                            */
/* -------------------------------------------------------------------- */
    psResult = cln_GetVersion(nClientID);

    if( (ECSERROR(psResult) || CPLAtof(ECSTEXT(psResult)) >= 3.1)
        && CSLCount(papszMatrices) == 0
        && CSLCount(papszImages) == 0 )
    {
        CPLDebug( "OGDIDataset",
                  "While this is an OGDI datastore, it does not appear to\n"
                  "have any identifiable raster layers.  Perhaps it is a\n"
                  "vector datastore?" );
        cln_DestroyClient( nClientID );
        CSLDestroy( papszImages );
        CSLDestroy( papszMatrices );
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Create a corresponding GDALDataset.                             */
/* -------------------------------------------------------------------- */
    OGDIDataset *poDS = new OGDIDataset();

    poDS->nClientID = nClientID;
    poDS->SetDescription( poOpenInfo->pszFilename );

/* -------------------------------------------------------------------- */
/*      Capture some information from the file that is of interest.     */
/* -------------------------------------------------------------------- */
    psResult = cln_GetGlobalBound( nClientID );
    if( ECSERROR(psResult) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "%s", psResult->message );
        CSLDestroy( papszImages );
        CSLDestroy( papszMatrices );
        delete poDS;
        return NULL;
    }

    poDS->sGlobalBounds = ECSREGION(psResult);

    psResult = cln_GetServerProjection(nClientID);
    if( ECSERROR(psResult) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "%s", psResult->message );
        CSLDestroy( papszImages );
        CSLDestroy( papszMatrices );
        delete poDS;
        return NULL;
    }

    OGRSpatialReference  oSRS;

    if( oSRS.importFromProj4( ECSTEXT(psResult) ) == OGRERR_NONE )
    {
        poDS->pszProjection = NULL;
        oSRS.exportToWkt( &(poDS->pszProjection) );
    }
    else
    {
        CPLError( CE_Warning, CPLE_NotSupported,
                  "untranslatable PROJ.4 projection: %s\n",
                  ECSTEXT(psResult) );
        poDS->pszProjection = CPLStrdup("");
    }

/* -------------------------------------------------------------------- */
/*      Select the global region.                                       */
/* -------------------------------------------------------------------- */
    psResult = cln_SelectRegion( nClientID, &(poDS->sGlobalBounds) );
    if( ECSERROR(psResult) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "%s", psResult->message );
        CSLDestroy( papszImages );
        CSLDestroy( papszMatrices );
        delete poDS;
        return NULL;
    }

    poDS->sCurrentBounds = poDS->sGlobalBounds;

/* -------------------------------------------------------------------- */
/*      If we have only one layer try to find the corresponding         */
/*      capabilities, and override the global bounds and resolution     */
/*      based on it.                                                    */
/* -------------------------------------------------------------------- */
    if( CSLCount(papszMatrices) + CSLCount(papszImages) == 1 )
    {
        if( papszMatrices != NULL && *papszMatrices != NULL )
            OverrideGlobalInfo( poDS, papszMatrices[0] );
        else if( papszImages != NULL && *papszImages != NULL )
            OverrideGlobalInfo( poDS, papszImages[0] );
    }

/* -------------------------------------------------------------------- */
/*      Otherwise setup a subdataset list.                              */
/* -------------------------------------------------------------------- */
    else
    {
        int i;

        for( i = 0; papszMatrices != NULL && papszMatrices[i] != NULL; i++ )
            poDS->AddSubDataset( "Matrix", papszMatrices[i] );

        for( i = 0; papszImages != NULL && papszImages[i] != NULL; i++ )
            poDS->AddSubDataset( "Image", papszImages[i] );
    }

/* -------------------------------------------------------------------- */
/*      Establish raster info.                                          */
/* -------------------------------------------------------------------- */
    poDS->nRasterXSize = (int)
        (((poDS->sGlobalBounds.east - poDS->sGlobalBounds.west)
          / poDS->sGlobalBounds.ew_res) + 0.5);

    poDS->nRasterYSize = (int)
        (((poDS->sGlobalBounds.north - poDS->sGlobalBounds.south)
          / poDS->sGlobalBounds.ns_res) + 0.5);

/* -------------------------------------------------------------------- */
/*      Create band information objects.                                */
/* -------------------------------------------------------------------- */
    for( int i=0; papszMatrices != NULL && papszMatrices[i] != NULL; i++)
    {
        if( CSLFindString( papszImages, papszMatrices[i] ) == -1 )
            poDS->SetBand( poDS->GetRasterCount()+1,
                           new OGDIRasterBand( poDS, poDS->GetRasterCount()+1,
                                               papszMatrices[i], Matrix, 0 ) );
    }

    for( int i=0; papszImages != NULL && papszImages[i] != NULL; i++)
    {
        OGDIRasterBand *poBand =
            new OGDIRasterBand( poDS, poDS->GetRasterCount() + 1,
                                papszImages[i], Image, 0 );

        poDS->SetBand( poDS->GetRasterCount()+1, poBand );

        /* special case for RGBt Layers */
        if( poBand->nOGDIImageType == 1 )
        {
            poDS->SetBand( poDS->GetRasterCount()+1,
                           new OGDIRasterBand( poDS, poDS->GetRasterCount()+1,
                                               papszImages[i], Image, 1 ));
            poDS->SetBand( poDS->GetRasterCount()+1,
                           new OGDIRasterBand( poDS, poDS->GetRasterCount()+1,
                                               papszImages[i], Image, 2 ));
            poDS->SetBand( poDS->GetRasterCount()+1,
                           new OGDIRasterBand( poDS, poDS->GetRasterCount()+1,
                                               papszImages[i], Image, 3 ));
        }
    }

    CSLDestroy( papszMatrices );
    CSLDestroy( papszImages );

    return poDS;
}

/************************************************************************/
/*                           AddSubDataset()                            */
/************************************************************************/

void OGDIDataset::AddSubDataset( const char *pszType, const char *pszLayer )

{
    char szName[80];
    int nCount = CSLCount( papszSubDatasets ) / 2;

    snprintf( szName, sizeof(szName), "SUBDATASET_%d_NAME", nCount+1 );
    papszSubDatasets =
        CSLSetNameValue( papszSubDatasets, szName,
              CPLSPrintf( "%s:\"%s\":%s", GetDescription(), pszLayer, pszType ) );

    snprintf( szName, sizeof(szName), "SUBDATASET_%d_DESC", nCount+1 );
    papszSubDatasets =
        CSLSetNameValue( papszSubDatasets, szName,
              CPLSPrintf( "%s as %s", pszLayer, pszType ) );
}

/************************************************************************/
/*                           CollectLayers()                            */
/************************************************************************/

CPLErr OGDIDataset::CollectLayers( int nClientID,
                                   char ***ppapszImages,
                                   char ***ppapszMatrices )

{
    const ecs_LayerCapabilities *psLayer = NULL;

    for( int iLayer = 0;
         (psLayer = cln_GetLayerCapabilities(nClientID,iLayer)) != NULL;
         iLayer++ )
    {
        if( psLayer->families[Image] )
        {
            *ppapszImages = CSLAddString( *ppapszImages, psLayer->name );
        }
        if( psLayer->families[Matrix] )
        {
            *ppapszMatrices = CSLAddString( *ppapszMatrices, psLayer->name );
        }
    }

    return CE_None;
}

/************************************************************************/
/*                         OverrideGlobalInfo()                         */
/*                                                                      */
/*      Override the global bounds and resolution based on a layers     */
/*      capabilities, if possible.                                      */
/************************************************************************/

CPLErr OGDIDataset::OverrideGlobalInfo( OGDIDataset *poDS,
                                        const char *pszLayer )

{
    const ecs_LayerCapabilities *psLayer = NULL;

    for( int iLayer = 0;
         (psLayer = cln_GetLayerCapabilities(poDS->nClientID,iLayer)) != NULL;
         iLayer++ )
    {
        if( EQUAL(psLayer->name, pszLayer) )
        {
            poDS->sGlobalBounds.north = psLayer->srs_north;
            poDS->sGlobalBounds.south = psLayer->srs_south;
            poDS->sGlobalBounds.east = psLayer->srs_east;
            poDS->sGlobalBounds.west = psLayer->srs_west;
            poDS->sGlobalBounds.ew_res = psLayer->srs_ewres;
            poDS->sGlobalBounds.ns_res = psLayer->srs_nsres;
        }
    }

    return CE_None;
}

/************************************************************************/
/*                          GetProjectionRef()                          */
/************************************************************************/

const char *OGDIDataset::GetProjectionRef()

{
    return pszProjection;
}

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

CPLErr OGDIDataset::GetGeoTransform( double * padfTransform )

{
    padfTransform[0] = sGlobalBounds.west;
    padfTransform[1] = sGlobalBounds.ew_res;
    padfTransform[2] = 0.0;

    padfTransform[3] = sGlobalBounds.north;
    padfTransform[4] = 0.0;
    padfTransform[5] = -sGlobalBounds.ns_res;

    return CE_None;
}

/************************************************************************/
/*                          GDALRegister_OGDI()                         */
/************************************************************************/

void GDALRegister_OGDI()

{
    if( !GDAL_CHECK_VERSION( "GDAL/OGDI driver" ) )
        return;

    if( GDALGetDriverByName( "OGDI" ) != NULL )
        return;

    GDALDriver *poDriver = new GDALDriver();

    poDriver->SetDescription( "OGDI" );
    poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
    poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, "OGDI Bridge" );
    poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "frmt_ogdi.html" );
    poDriver->SetMetadataItem( GDAL_DMD_SUBDATASETS, "YES" );

    poDriver->pfnOpen = OGDIDataset::Open;

    GetGDALDriverManager()->RegisterDriver( poDriver );
}
