/******************************************************************************
 * $Id: ogropenfilegdbdatasource.cpp 28733 2015-03-15 03:06:35Z rouault $
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  Implements Open FileGDB OGR driver.
 * Author:   Even Rouault, <even dot rouault at mines-dash paris dot org>
 *
 ******************************************************************************
 * Copyright (c) 2014, 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 "ogr_openfilegdb.h"
#include "ogr_mem.h"
#include <map>

CPL_CVSID("$Id");

/************************************************************************/
/*                      OGROpenFileGDBDataSource()                      */
/************************************************************************/
OGROpenFileGDBDataSource::OGROpenFileGDBDataSource()
{
    m_pszName = NULL;
    m_papszFiles = NULL;
    bLastSQLUsedOptimizedImplementation = FALSE;
}

/************************************************************************/
/*                     ~OGROpenFileGDBDataSource()                      */
/************************************************************************/
OGROpenFileGDBDataSource::~OGROpenFileGDBDataSource()

{
    size_t i;
    for( i = 0; i < m_apoLayers.size(); i++ )
        delete m_apoLayers[i];
    for( i = 0; i < m_apoHiddenLayers.size(); i++ )
        delete m_apoHiddenLayers[i];
    CPLFree(m_pszName);
    CSLDestroy(m_papszFiles);
}

/************************************************************************/
/*                             FileExists()                             */
/************************************************************************/

int OGROpenFileGDBDataSource::FileExists(const char* pszFilename)
{
    if( m_papszFiles )
        return CSLFindString(m_papszFiles, CPLGetFilename(pszFilename)) >= 0;
    else
    {
        VSIStatBufL sStat;
        return VSIStatExL(pszFilename, &sStat, VSI_STAT_EXISTS_FLAG) == 0;
    }
}

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

int OGROpenFileGDBDataSource::Open( const char* pszFilename )

{
    FileGDBTable oTable;

    m_pszName = CPLStrdup(pszFilename);

    m_osDirName = pszFilename;
    int nInterestTable = -1;
    const char* pszFilenameWithoutPath = CPLGetFilename(pszFilename);
    if( strlen(pszFilenameWithoutPath) == strlen("a00000000.gdbtable") &&
        pszFilenameWithoutPath[0] == 'a' &&
        sscanf(pszFilenameWithoutPath, "a%08x.gdbtable", &nInterestTable) == 1 )
    {
        m_osDirName = CPLGetPath(m_osDirName);
    }
    else
    {
        nInterestTable = -1;
    }

    if( EQUAL(CPLGetExtension(m_osDirName), "zip") &&
        strncmp(m_osDirName, "/vsizip/", strlen("/vsizip/")) != 0 )
    {
        m_osDirName = "/vsizip/" + m_osDirName;
    }
    else  if( EQUAL(CPLGetExtension(m_osDirName), "tar") &&
        strncmp(m_osDirName, "/vsitar/", strlen("/vsitar/")) != 0 )
    {
        m_osDirName = "/vsitar/" + m_osDirName;
    }

    if( strncmp(m_osDirName, "/vsizip/", strlen("/vsizip/")) == 0 ||
        strncmp(m_osDirName, "/vsitar/", strlen("/vsitar/")) == 0)
    {
        /* Look for one subdirectory ending with .gdb extension */
        char** papszDir = CPLReadDir(m_osDirName);
        int iCandidate = -1;
        for( int i=0; papszDir && papszDir[i] != NULL; i++ )
        {
            VSIStatBufL sStat;
            if( EQUAL(CPLGetExtension(papszDir[i]), "gdb") &&
                VSIStatL( CPLSPrintf("%s/%s", m_osDirName.c_str(), papszDir[i]), &sStat ) == 0 &&
                VSI_ISDIR(sStat.st_mode) )
            {
                if( iCandidate < 0 )
                    iCandidate = i;
                else
                {
                    iCandidate = -1;
                    break;
                }
            }
        }
        if( iCandidate >= 0 )
        {
            m_osDirName += "/";
            m_osDirName += papszDir[iCandidate];
        }
        CSLDestroy(papszDir);
    }

    m_papszFiles = VSIReadDir(m_osDirName);

    /* Explore catalog table */
    const char* psza00000001 = CPLFormFilename(m_osDirName, "a00000001", "gdbtable");
    if( !FileExists(psza00000001) || !oTable.Open(psza00000001) )
    {
        if( nInterestTable >= 0 && FileExists(m_pszName) )
        {
            const char* pszLyrName = CPLSPrintf("a%08x", nInterestTable);
            OGROpenFileGDBLayer* poLayer = new OGROpenFileGDBLayer(
                                m_pszName, pszLyrName, "", "");
            const char* pszTablX = CPLResetExtension(m_pszName, "gdbtablx");
            if( !FileExists(pszTablX) &&
                poLayer->GetLayerDefn()->GetFieldCount() == 0 &&
                poLayer->GetFeatureCount() == 0 )
            {
                delete poLayer;
                return FALSE;
            }
            m_apoLayers.push_back(poLayer);
            return TRUE;
        }
        return FALSE;
    }

    if( !(oTable.GetFieldCount() >= 2 &&
          oTable.GetField(0)->GetName() == "Name" &&
          oTable.GetField(0)->GetType() == FGFT_STRING &&
          oTable.GetField(1)->GetName() == "FileFormat" &&
          (oTable.GetField(1)->GetType() == FGFT_INT16 ||
           oTable.GetField(1)->GetType() == FGFT_INT32) ) )
    {
        return FALSE;
    }

    int iGDBItems = -1; /* V10 */
    int iGDBFeatureClasses = -1; /* V9.X */
    int iGDBObjectClasses = -1; /* V9.X */
    int i;

    std::vector<std::string> aosTableNames;
    for(i=0;i<oTable.GetTotalRecordCount();i++)
    {
        if( !oTable.SelectRow(i) )
        {
            if( oTable.HasGotError() )
                break;
            aosTableNames.push_back("");
            continue;
        }

        const OGRField* psField = oTable.GetFieldValue(0);
        if( psField != NULL )
        {
            aosTableNames.push_back(psField->String);

            if( strcmp(psField->String, "GDB_Items") == 0 )
            {
                iGDBItems = i;
            }
            else if( strcmp(psField->String, "GDB_FeatureClasses") == 0 )
            {
                iGDBFeatureClasses = i;
            }
            else if( strcmp(psField->String, "GDB_ObjectClasses") == 0 )
            {
                iGDBObjectClasses = i;
            }
            m_osMapNameToIdx[psField->String] = 1 + i;
        }
        else
        {
            aosTableNames.push_back("");
        }
    }

    oTable.Close();

    if( iGDBItems >= 0 )
    {
        int bRet = OpenFileGDBv10(iGDBItems,
                                  nInterestTable);
        if( !bRet )
            return FALSE;
    }
    else if( iGDBFeatureClasses >= 0 && iGDBObjectClasses >= 0 )
    {
        int bRet = OpenFileGDBv9(iGDBFeatureClasses,
                                 iGDBObjectClasses,
                                 nInterestTable);
        if( !bRet )
            return FALSE;
    }
    else
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "No GDB_Items nor GDB_FeatureClasses table");
        return FALSE;
    }

    if( m_apoLayers.size() == 0 && nInterestTable >= 0 )
    {
        if( FileExists(m_pszName) )
        {
            const char* pszLyrName;
            if( nInterestTable <= (int)aosTableNames.size()  &&
                aosTableNames[nInterestTable-1].size() != 0 )
                pszLyrName = aosTableNames[nInterestTable-1].c_str();
            else
                pszLyrName = CPLSPrintf("a%08x", nInterestTable);
            m_apoLayers.push_back(new OGROpenFileGDBLayer(
                m_pszName, pszLyrName, "", ""));
        }
        else
        {
            return FALSE;
        }
    }

    return TRUE;
}

/***********************************************************************/
/*                             AddLayer()                              */
/***********************************************************************/

void OGROpenFileGDBDataSource::AddLayer( const CPLString& osName,
                                         int nInterestTable,
                                         int& nCandidateLayers,
                                         int& nLayersSDC,
                                         const CPLString& osDefinition,
                                         const CPLString& osDocumentation,
                                         const char* pszGeomName,
                                         OGRwkbGeometryType eGeomType )
{
    std::map<std::string, int>::const_iterator oIter =
                                    m_osMapNameToIdx.find(osName);
    int idx = 0;
    if( oIter != m_osMapNameToIdx.end() )
        idx = oIter->second;
    if( idx > 0 && (nInterestTable < 0 || nInterestTable == idx) )
    {
        const char* pszFilename = CPLFormFilename(
            m_osDirName, CPLSPrintf("a%08x", idx), "gdbtable");
        if( FileExists(pszFilename) )
        {
            nCandidateLayers ++;

            if( m_papszFiles != NULL )
            {
                const char* pszSDC = CPLResetExtension(pszFilename, "gdbtable.sdc");
                if( FileExists(pszSDC) )
                {
                    nLayersSDC ++;
                    CPLError(CE_Warning, CPLE_AppDefined,
                            "%s layer has a %s file whose format is unhandled",
                            osName.c_str(), pszSDC);
                    return;
                }
            }

            m_apoLayers.push_back(
                new OGROpenFileGDBLayer(pszFilename,
                                        osName,
                                        osDefinition,
                                        osDocumentation,
                                        pszGeomName, eGeomType));
        }
    }
}

/***********************************************************************/
/*                         OpenFileGDBv10()                            */
/***********************************************************************/

int OGROpenFileGDBDataSource::OpenFileGDBv10(int iGDBItems,
                                             int nInterestTable)
{
    FileGDBTable oTable;
    int i;

    CPLDebug("OpenFileGDB", "FileGDB v10 or later");

    if( !oTable.Open(CPLFormFilename(m_osDirName,
            CPLSPrintf("a%08x.gdbtable", iGDBItems + 1), NULL)) )
        return FALSE;

    int iName = oTable.GetFieldIdx("Name");
    int iDefinition = oTable.GetFieldIdx("Definition");
    int iDocumentation = oTable.GetFieldIdx("Documentation");
    if( iName < 0 || iDefinition < 0 || iDocumentation < 0 ||
        oTable.GetField(iName)->GetType() != FGFT_STRING ||
        oTable.GetField(iDefinition)->GetType() != FGFT_XML ||
        oTable.GetField(iDocumentation)->GetType() != FGFT_XML )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                    "Wrong structure for GDB_Items table");
        return FALSE;
    }

    int nCandidateLayers = 0, nLayersSDC = 0;
    for(i=0;i<oTable.GetTotalRecordCount();i++)
    {
        if( !oTable.SelectRow(i) )
        {
            if( oTable.HasGotError() )
                break;
            continue;
        }

        const OGRField* psField = oTable.GetFieldValue(iDefinition);
        if( psField != NULL &&
            (strstr(psField->String, "DEFeatureClassInfo") != NULL ||
                strstr(psField->String, "DETableInfo") != NULL) )
        {
            CPLString osDefinition(psField->String);

            psField = oTable.GetFieldValue(iDocumentation);
            CPLString osDocumentation( psField != NULL ? psField->String : "" );

            psField = oTable.GetFieldValue(iName);
            if( psField != NULL )
            {
                AddLayer( psField->String, nInterestTable, nCandidateLayers, nLayersSDC,
                          osDefinition, osDocumentation,
                          NULL, wkbUnknown );
            }
        }
    }

    if( m_apoLayers.size() == 0 && nCandidateLayers > 0 &&
        nCandidateLayers == nLayersSDC )
        return FALSE;

    return TRUE;
}

/***********************************************************************/
/*                         OpenFileGDBv9()                             */
/***********************************************************************/

int OGROpenFileGDBDataSource::OpenFileGDBv9(int iGDBFeatureClasses,
                                            int iGDBObjectClasses,
                                            int nInterestTable)
{
    FileGDBTable oTable;
    int i;

    CPLDebug("OpenFileGDB", "FileGDB v9");

    /* Fetch names of layers */
    if( !oTable.Open(CPLFormFilename(m_osDirName,
            CPLSPrintf("a%08x", iGDBObjectClasses + 1), "gdbtable")) )
        return FALSE;

    int iName = oTable.GetFieldIdx("Name");
    int iCLSID = oTable.GetFieldIdx("CLSID");
    if( iName < 0 || oTable.GetField(iName)->GetType() != FGFT_STRING ||
        iCLSID < 0 || oTable.GetField(iCLSID)->GetType() != FGFT_STRING )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                    "Wrong structure for GDB_ObjectClasses table");
        return FALSE;
    }
    
    std::vector< std::string > aosName;
    int nCandidateLayers = 0, nLayersSDC = 0;
    for(i=0;i<oTable.GetTotalRecordCount();i++)
    {
        if( !oTable.SelectRow(i) )
        {
            if( oTable.HasGotError() )
                break;
            aosName.push_back( "" );
            continue;
        }

        const OGRField* psField = oTable.GetFieldValue(iName);
        if( psField != NULL )
        {
            std::string osName(psField->String);
            psField = oTable.GetFieldValue(iCLSID);
            if( psField != NULL )
            {
                /* Is it a non-spatial table ? */
                if( strcmp(psField->String, "{7A566981-C114-11D2-8A28-006097AFF44E}") == 0 )
                {
                    aosName.push_back( "" );
                    AddLayer( osName, nInterestTable, nCandidateLayers, nLayersSDC,
                              "", "", NULL, wkbNone );
                }
                else
                {
                    /* We should perhaps also check that the CLSID is the one of a spatial table */
                    aosName.push_back( osName );
                }
            }
        }
    }
    oTable.Close();

    /* Find tables that are spatial layers */
    if( !oTable.Open(CPLFormFilename(m_osDirName,
            CPLSPrintf("a%08x", iGDBFeatureClasses + 1), "gdbtable")) )
        return FALSE;

    int iObjectClassID = oTable.GetFieldIdx("ObjectClassID");
    int iGeometryType = oTable.GetFieldIdx("GeometryType");
    int iShapeField = oTable.GetFieldIdx("ShapeField");
    if( iObjectClassID < 0 || iGeometryType < 0 || iShapeField < 0 ||
        oTable.GetField(iObjectClassID)->GetType() != FGFT_INT32 ||
        oTable.GetField(iGeometryType)->GetType() != FGFT_INT32 ||
        oTable.GetField(iShapeField)->GetType() != FGFT_STRING )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                    "Wrong structure for GDB_FeatureClasses table");
        return FALSE;
    }

    for(i=0;i<oTable.GetTotalRecordCount();i++)
    {
        if( !oTable.SelectRow(i) )
        {
            if( oTable.HasGotError() )
                break;
            continue;
        }

        const OGRField* psField;

        psField = oTable.GetFieldValue(iGeometryType);
        if( psField == NULL )
            continue;
        int nGeomType = psField->Integer;
        OGRwkbGeometryType eGeomType = wkbUnknown;
        switch( nGeomType )
        {
            case FGTGT_NONE: /* doesn't make sense ! */ break;
            case FGTGT_POINT: eGeomType = wkbPoint; break;
            case FGTGT_MULTIPOINT: eGeomType = wkbMultiPoint; break;
            case FGTGT_LINE: eGeomType = wkbMultiLineString; break;
            case FGTGT_POLYGON: eGeomType = wkbMultiPolygon; break;
            case FGTGT_MULTIPATCH: eGeomType = wkbMultiPolygon; break;
        }

        psField = oTable.GetFieldValue(iShapeField);
        if( psField == NULL )
            continue;
        CPLString osGeomFieldName(psField->String);

        psField = oTable.GetFieldValue(iObjectClassID);
        if( psField == NULL )
            continue;

        int idx = psField->Integer;
        if( psField != NULL && idx > 0 && idx <= (int)aosName.size() &&
            aosName[idx-1].size() > 0 )
        {
            const std::string osName(aosName[idx-1]);
            AddLayer( osName, nInterestTable, nCandidateLayers, nLayersSDC,
                      "", "", osGeomFieldName.c_str(), eGeomType);
        }
    }

    if( m_apoLayers.size() == 0 && nCandidateLayers > 0 &&
        nCandidateLayers == nLayersSDC )
        return FALSE;

    return TRUE;
}

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

int OGROpenFileGDBDataSource::TestCapability( const char * pszCap )
{
    (void)pszCap;
    return FALSE;
}

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

OGRLayer* OGROpenFileGDBDataSource::GetLayer( int iIndex )
{
    if( iIndex < 0 || iIndex >= (int) m_apoLayers.size() )
        return NULL;
    return m_apoLayers[iIndex];
}

/***********************************************************************/
/*                          GetLayerByName()                           */
/***********************************************************************/

OGRLayer* OGROpenFileGDBDataSource::GetLayerByName( const char* pszName )
{
    OGRLayer* poLayer;

    poLayer = OGRDataSource::GetLayerByName(pszName);
    if( poLayer != NULL )
        return poLayer;

    for(size_t i=0;i<m_apoHiddenLayers.size();i++)
    {
        if( EQUAL(m_apoHiddenLayers[i]->GetName(), pszName) )
            return m_apoHiddenLayers[i];
    }

    std::map<std::string, int>::const_iterator oIter = m_osMapNameToIdx.find(pszName);
    if( oIter != m_osMapNameToIdx.end() )
    {
        int idx = oIter->second;
        const char* pszFilename = CPLFormFilename(
                            m_osDirName, CPLSPrintf("a%08x", idx), "gdbtable");
        if( FileExists(pszFilename) )
        {
            poLayer = new OGROpenFileGDBLayer(
                                    pszFilename, pszName, "", "");
            m_apoHiddenLayers.push_back(poLayer);
            return poLayer;
        }
    }
    return NULL;
}


/************************************************************************/
/*                   OGROpenFileGDBSingleFeatureLayer                   */
/************************************************************************/

class OGROpenFileGDBSingleFeatureLayer : public OGRLayer
{
  private:
    char               *pszVal;
    OGRFeatureDefn     *poFeatureDefn;
    int                 iNextShapeId;

  public:
                        OGROpenFileGDBSingleFeatureLayer( const char* pszLayerName,
                                                          const char *pszVal );
                        ~OGROpenFileGDBSingleFeatureLayer();

    virtual void        ResetReading() { iNextShapeId = 0; }
    virtual OGRFeature *GetNextFeature();
    virtual OGRFeatureDefn *GetLayerDefn() { return poFeatureDefn; }
    virtual int         TestCapability( const char * ) { return FALSE; }
};

/************************************************************************/
/*                 OGROpenFileGDBSingleFeatureLayer()                   */
/************************************************************************/

OGROpenFileGDBSingleFeatureLayer::OGROpenFileGDBSingleFeatureLayer(const char* pszLayerName,
                                                                   const char *pszVal )
{
    poFeatureDefn = new OGRFeatureDefn( pszLayerName );
    poFeatureDefn->Reference();
    OGRFieldDefn oField( "FIELD_1", OFTString );
    poFeatureDefn->AddFieldDefn( &oField );

    iNextShapeId = 0;
    this->pszVal = pszVal ? CPLStrdup(pszVal) : NULL;
}

/************************************************************************/
/*                 ~OGROpenFileGDBSingleFeatureLayer()                  */
/************************************************************************/

OGROpenFileGDBSingleFeatureLayer::~OGROpenFileGDBSingleFeatureLayer()
{
    if( poFeatureDefn != NULL )
        poFeatureDefn->Release();
    CPLFree(pszVal);
}


/************************************************************************/
/*                           GetNextFeature()                           */
/************************************************************************/

OGRFeature * OGROpenFileGDBSingleFeatureLayer::GetNextFeature()
{
    if (iNextShapeId != 0)
        return NULL;

    OGRFeature* poFeature = new OGRFeature(poFeatureDefn);
    if (pszVal)
        poFeature->SetField(0, pszVal);
    poFeature->SetFID(iNextShapeId ++);
    return poFeature;
}

/***********************************************************************/
/*                     OGROpenFileGDBSimpleSQLLayer                    */
/***********************************************************************/

class OGROpenFileGDBSimpleSQLLayer: public OGRLayer
{
        OGRLayer        *poBaseLayer;
        FileGDBIterator *poIter;
        OGRFeatureDefn  *poFeatureDefn;

    public:
        OGROpenFileGDBSimpleSQLLayer(OGRLayer* poBaseLayer,
                                     FileGDBIterator* poIter,
                                     int nColumns,
                                     swq_col_def* pasColDefs);
       ~OGROpenFileGDBSimpleSQLLayer();

       virtual void        ResetReading();
       virtual OGRFeature* GetNextFeature();
       virtual OGRFeature* GetFeature( long nFeatureId );
       virtual OGRFeatureDefn* GetLayerDefn() { return poFeatureDefn; }
       virtual int         TestCapability( const char * );
       virtual const char* GetFIDColumn() { return poBaseLayer->GetFIDColumn(); }
       virtual OGRErr      GetExtent( OGREnvelope *psExtent, int bForce )
                            { return poBaseLayer->GetExtent(psExtent, bForce); }
       virtual int         GetFeatureCount(int bForce);
};

/***********************************************************************/
/*                    OGROpenFileGDBSimpleSQLLayer()                   */
/***********************************************************************/

OGROpenFileGDBSimpleSQLLayer::OGROpenFileGDBSimpleSQLLayer(
                                            OGRLayer* poBaseLayer,
                                            FileGDBIterator* poIter,
                                            int nColumns,
                                            swq_col_def* pasColDefs) :
        poBaseLayer(poBaseLayer), poIter(poIter)
{
    if( nColumns == 1 && strcmp(pasColDefs[0].field_name, "*") == 0 )
    {
        poFeatureDefn = poBaseLayer->GetLayerDefn();
        poFeatureDefn->Reference();
    }
    else
    {
        poFeatureDefn = new OGRFeatureDefn(poBaseLayer->GetName());
        poFeatureDefn->SetGeomType(poBaseLayer->GetGeomType());
        poFeatureDefn->Reference();
        if( poBaseLayer->GetGeomType() != wkbNone )
        {
            poFeatureDefn->GetGeomFieldDefn(0)->SetName(poBaseLayer->GetGeometryColumn());
            poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poBaseLayer->GetSpatialRef());
        }
        for(int i=0;i<nColumns;i++)
        {
            if( strcmp(pasColDefs[i].field_name, "*") == 0 )
            {
                for(int j=0;j<poBaseLayer->GetLayerDefn()->GetFieldCount();j++)
                    poFeatureDefn->AddFieldDefn(poBaseLayer->GetLayerDefn()->GetFieldDefn(j));
            }
            else
            {
                OGRFieldDefn* poFieldDefn = poBaseLayer->GetLayerDefn()->GetFieldDefn(
                    poBaseLayer->GetLayerDefn()->GetFieldIndex(pasColDefs[i].field_name));
                CPLAssert(poFieldDefn != NULL ); /* already checked before */
                poFeatureDefn->AddFieldDefn(poFieldDefn);
            }
        }
    }
    ResetReading();
}

/***********************************************************************/
/*                   ~OGROpenFileGDBSimpleSQLLayer()                   */
/***********************************************************************/

OGROpenFileGDBSimpleSQLLayer::~OGROpenFileGDBSimpleSQLLayer()
{
    if( poFeatureDefn )
    {
        poFeatureDefn->Release();
    }
    delete poIter;
}

/***********************************************************************/
/*                          ResetReading()                             */
/***********************************************************************/

void OGROpenFileGDBSimpleSQLLayer::ResetReading()
{
    poIter->Reset();
}

/***********************************************************************/
/*                          GetFeature()                               */
/***********************************************************************/

OGRFeature* OGROpenFileGDBSimpleSQLLayer::GetFeature( long nFeatureId )
{
    OGRFeature* poSrcFeature = poBaseLayer->GetFeature(nFeatureId);
    if( poSrcFeature == NULL )
        return NULL;

    if( poFeatureDefn == poBaseLayer->GetLayerDefn() )
        return poSrcFeature;
    else
    {
        OGRFeature* poFeature = new OGRFeature(poFeatureDefn);
        poFeature->SetFrom(poSrcFeature);
        poFeature->SetFID(poSrcFeature->GetFID());
        delete poSrcFeature;
        return poFeature;
    }
}

/***********************************************************************/
/*                         GetNextFeature()                            */
/***********************************************************************/

OGRFeature* OGROpenFileGDBSimpleSQLLayer::GetNextFeature()
{
    while(TRUE)
    {
        int nRow = poIter->GetNextRowSortedByValue();
        if( nRow < 0 )
            return NULL;
        OGRFeature* poFeature = GetFeature(nRow + 1);
        if( poFeature == NULL )
            return NULL;

        if( (m_poFilterGeom == NULL
             || FilterGeometry( poFeature->GetGeometryRef() ) )
            && (m_poAttrQuery == NULL ||
                m_poAttrQuery->Evaluate( poFeature ) ) )
        {
            return poFeature;
        }

        delete poFeature;
    }
}

/***********************************************************************/
/*                         GetFeatureCount()                           */
/***********************************************************************/

int OGROpenFileGDBSimpleSQLLayer::GetFeatureCount( int bForce )
{

    /* No filter */
    if( m_poFilterGeom == NULL && m_poAttrQuery == NULL )
    {
        return poIter->GetRowCount();
    }

    return OGRLayer::GetFeatureCount(bForce);
}

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

int OGROpenFileGDBSimpleSQLLayer::TestCapability( const char * pszCap )
{

    if( EQUAL(pszCap,OLCFastFeatureCount) )
    {
        return( m_poFilterGeom == NULL && m_poAttrQuery == NULL );
    }
    else if( EQUAL(pszCap,OLCFastGetExtent) )
    {
        return TRUE;
    }
    else if( EQUAL(pszCap,OLCRandomRead) )
    {
        return TRUE;
    }
    else if( EQUAL(pszCap,OLCStringsAsUTF8) )
    {
        return TRUE; /* ? */
    }

    return FALSE;
}

/***********************************************************************/
/*                            ExecuteSQL()                             */
/***********************************************************************/

OGRLayer* OGROpenFileGDBDataSource::ExecuteSQL( const char *pszSQLCommand,
                                                OGRGeometry *poSpatialFilter,
                                                const char *pszDialect )
{

/* -------------------------------------------------------------------- */
/*      Special case GetLayerDefinition                                 */
/* -------------------------------------------------------------------- */
    if (EQUALN(pszSQLCommand, "GetLayerDefinition ", strlen("GetLayerDefinition ")))
    {
        OGROpenFileGDBLayer* poLayer = (OGROpenFileGDBLayer*)
            GetLayerByName(pszSQLCommand + strlen("GetLayerDefinition "));
        if (poLayer)
        {
            OGRLayer* poRet = new OGROpenFileGDBSingleFeatureLayer(
                "LayerDefinition", poLayer->GetXMLDefinition().c_str() );
            return poRet;
        }
        else
            return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Special case GetLayerMetadata                                   */
/* -------------------------------------------------------------------- */
    if (EQUALN(pszSQLCommand, "GetLayerMetadata ", strlen("GetLayerMetadata ")))
    {
        OGROpenFileGDBLayer* poLayer = (OGROpenFileGDBLayer*)
            GetLayerByName(pszSQLCommand + strlen("GetLayerMetadata "));
        if (poLayer)
        {
            OGRLayer* poRet = new OGROpenFileGDBSingleFeatureLayer(
                "LayerMetadata", poLayer->GetXMLDocumentation().c_str() );
            return poRet;
        }
        else
            return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Special case GetLayerAttrIndexUse (only for debugging purposes) */
/* -------------------------------------------------------------------- */
    if (EQUALN(pszSQLCommand, "GetLayerAttrIndexUse ", strlen("GetLayerAttrIndexUse ")))
    {
        OGROpenFileGDBLayer* poLayer = (OGROpenFileGDBLayer*)
            GetLayerByName(pszSQLCommand + strlen("GetLayerAttrIndexUse "));
        if (poLayer)
        {
            OGRLayer* poRet = new OGROpenFileGDBSingleFeatureLayer(
                "LayerAttrIndexUse", CPLSPrintf("%d", poLayer->GetAttrIndexUse()) );
            return poRet;
        }
        else
            return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Special case GetLayerSpatialIndexState (only for debugging purposes) */
/* -------------------------------------------------------------------- */
    if (EQUALN(pszSQLCommand, "GetLayerSpatialIndexState ", strlen("GetLayerSpatialIndexState ")))
    {
        OGROpenFileGDBLayer* poLayer = (OGROpenFileGDBLayer*)
            GetLayerByName(pszSQLCommand + strlen("GetLayerSpatialIndexState "));
        if (poLayer)
        {
            OGRLayer* poRet = new OGROpenFileGDBSingleFeatureLayer(
                "LayerSpatialIndexState", CPLSPrintf("%d", poLayer->GetSpatialIndexState()) );
            return poRet;
        }
        else
            return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Special case GetLastSQLUsedOptimizedImplementation (only for debugging purposes) */
/* -------------------------------------------------------------------- */
    if (EQUAL(pszSQLCommand, "GetLastSQLUsedOptimizedImplementation"))
    {
        OGRLayer* poRet = new OGROpenFileGDBSingleFeatureLayer(
            "GetLastSQLUsedOptimizedImplementation",
                    CPLSPrintf("%d", bLastSQLUsedOptimizedImplementation) );
        return poRet;
    }

    bLastSQLUsedOptimizedImplementation = FALSE;

/* -------------------------------------------------------------------- */
/*      Special cases for SQL optimizations                             */
/* -------------------------------------------------------------------- */
    if( EQUALN(pszSQLCommand, "SELECT ", strlen("SELECT ")) &&
        (pszDialect == NULL || EQUAL(pszDialect, "") || EQUAL(pszDialect, "OGRSQL")) &&
        CSLTestBoolean(CPLGetConfigOption("OPENFILEGDB_USE_INDEX", "YES")) )
    {
        swq_select oSelect;
        if( oSelect.preparse(pszSQLCommand) != OGRERR_NONE )
            return NULL;

/* -------------------------------------------------------------------- */
/*      MIN/MAX/SUM/AVG/COUNT optimization                              */
/* -------------------------------------------------------------------- */
        if( oSelect.join_count == 0 && oSelect.poOtherSelect == NULL &&
            oSelect.table_count == 1 && oSelect.order_specs == 0 )
        {
            OGROpenFileGDBLayer* poLayer = 
                (OGROpenFileGDBLayer*)GetLayerByName( oSelect.table_defs[0].table_name);
            if( poLayer )
            {
                OGRMemLayer* poMemLayer = NULL;

                int i;
                for(i = 0; i < oSelect.result_columns; i ++ )
                {
                    swq_col_func col_func = oSelect.column_defs[i].col_func;
                    if( !(col_func == SWQCF_MIN || col_func == SWQCF_MAX ||
                          col_func == SWQCF_COUNT || col_func == SWQCF_AVG ||
                          col_func == SWQCF_SUM) )
                        break;

                    if( oSelect.column_defs[i].field_name == NULL )
                        break;
                    if( oSelect.column_defs[i].distinct_flag )
                        break;
                    if( oSelect.column_defs[i].target_type != SWQ_OTHER )
                        break;

                    int idx = poLayer->GetLayerDefn()->GetFieldIndex(
                                            oSelect.column_defs[i].field_name);
                    if( idx < 0 )
                        break;

                    OGRFieldDefn* poFieldDefn = poLayer->GetLayerDefn()->GetFieldDefn(idx);

                    if( col_func == SWQCF_SUM && poFieldDefn->GetType() == OFTDateTime )
                        break;

                    int eOutOGRType = -1;
                    int nCount = 0;
                    double dfSum = 0.0;
                    const OGRField* psField = NULL;
                    OGRField sField;
                    if( col_func == SWQCF_MIN || col_func == SWQCF_MAX )
                    {
                        psField = poLayer->GetMinMaxValue(
                                 poFieldDefn, col_func == SWQCF_MIN, eOutOGRType);
                        if( eOutOGRType < 0 )
                            break;
                    }
                    else
                    {
                        double dfMin = 0.0, dfMax = 0.0;
                        if( !poLayer->GetMinMaxSumCount(poFieldDefn, dfMin, dfMax,
                                                        dfSum, nCount) )
                            break;
                        psField = &sField;
                        if( col_func == SWQCF_AVG )
                        {
                            if( nCount == 0 )
                            {
                                eOutOGRType = OFTReal;
                                psField = NULL;
                            }
                            else
                            {
                                if( poFieldDefn->GetType() == OFTDateTime )
                                {
                                    eOutOGRType = OFTDateTime;
                                    FileGDBDoubleDateToOGRDate(dfSum / nCount, &sField);
                                }
                                else
                                {
                                    eOutOGRType = OFTReal;
                                    sField.Real = dfSum / nCount;
                                }
                            }
                        }
                        else if( col_func == SWQCF_COUNT )
                        {
                            sField.Integer = nCount;
                            eOutOGRType = OFTInteger;
                        }
                        else
                        {
                            sField.Real = dfSum;
                            eOutOGRType = OFTReal;
                        }
                    }

                    if( poMemLayer == NULL )
                    {
                        poMemLayer = new OGRMemLayer("SELECT", NULL, wkbNone);
                        OGRFeature* poFeature = new OGRFeature(poMemLayer->GetLayerDefn());
                        poMemLayer->CreateFeature(poFeature);
                        delete poFeature;
                    }

                    const char* pszMinMaxFieldName =
                        CPLSPrintf( "%s_%s", (col_func == SWQCF_MIN) ? "MIN" :
                                             (col_func == SWQCF_MAX) ? "MAX" :
                                             (col_func == SWQCF_AVG) ? "AVG" :
                                             (col_func == SWQCF_SUM) ? "SUM" :
                                                                       "COUNT",
                                            oSelect.column_defs[i].field_name);
                    OGRFieldDefn oFieldDefn(pszMinMaxFieldName,
                                            (OGRFieldType) eOutOGRType);
                    poMemLayer->CreateField(&oFieldDefn);
                    if( psField != NULL )
                    {
                        OGRFeature* poFeature = poMemLayer->GetFeature(0);
                        poFeature->SetField(oFieldDefn.GetNameRef(), (OGRField*) psField);
                        poMemLayer->SetFeature(poFeature);
                        delete poFeature;
                    }
                }
                if( i != oSelect.result_columns )
                {
                    delete poMemLayer;
                }
                else
                {
                    CPLDebug("OpenFileGDB",
                        "Using optimized MIN/MAX/SUM/AVG/COUNT implementation");
                    bLastSQLUsedOptimizedImplementation = TRUE;
                    return poMemLayer;
                }
            }
        }

/* -------------------------------------------------------------------- */
/*      ORDER BY optimization                                           */
/* -------------------------------------------------------------------- */
        if( oSelect.join_count == 0 && oSelect.poOtherSelect == NULL &&
            oSelect.table_count == 1 && oSelect.order_specs == 1 )
        {
            OGROpenFileGDBLayer* poLayer = 
                (OGROpenFileGDBLayer*)GetLayerByName( oSelect.table_defs[0].table_name);
            if( poLayer != NULL &&
                poLayer->HasIndexForField(oSelect.order_defs[0].field_name) )
            {
                OGRErr eErr = OGRERR_NONE;
                if( oSelect.where_expr != NULL )
                {
                    /* The where must be a simple comparison on the column */
                    /* that is used for ordering */
                    if( oSelect.where_expr->eNodeType == SNT_OPERATION &&
                        OGROpenFileGDBIsComparisonOp(oSelect.where_expr->nOperation) &&
                        oSelect.where_expr->nOperation != SWQ_NE &&
                        oSelect.where_expr->nSubExprCount == 2 &&
                        (oSelect.where_expr->papoSubExpr[0]->eNodeType == SNT_COLUMN ||
                         oSelect.where_expr->papoSubExpr[0]->eNodeType == SNT_CONSTANT) &&
                        oSelect.where_expr->papoSubExpr[0]->field_type == SWQ_STRING &&
                        EQUAL(oSelect.where_expr->papoSubExpr[0]->string_value,
                              oSelect.order_defs[0].field_name) &&
                        oSelect.where_expr->papoSubExpr[1]->eNodeType == SNT_CONSTANT )
                    {
                        /* ok */
                    }
                    else
                        eErr = OGRERR_FAILURE;
                }
                if( eErr == OGRERR_NONE )
                {
                    int i;
                    for(i = 0; i < oSelect.result_columns; i ++ )
                    {
                        if( oSelect.column_defs[i].col_func != SWQCF_NONE )
                            break;
                        if( oSelect.column_defs[i].field_name == NULL )
                            break;
                        if( oSelect.column_defs[i].distinct_flag )
                            break;
                        if( oSelect.column_defs[i].target_type != SWQ_OTHER )
                            break;
                        if( strcmp(oSelect.column_defs[i].field_name, "*") != 0 &&
                            poLayer->GetLayerDefn()->GetFieldIndex(
                                        oSelect.column_defs[i].field_name) < 0 )
                            break;
                    }
                    if( i != oSelect.result_columns )
                        eErr = OGRERR_FAILURE;
                }
                if( eErr == OGRERR_NONE )
                {
                    swq_op op = SWQ_UNKNOWN;
                    swq_expr_node* poValue = NULL;
                    if( oSelect.where_expr != NULL )
                    {
                        op = (swq_op)oSelect.where_expr->nOperation;
                        poValue = oSelect.where_expr->papoSubExpr[1];
                    }

                    FileGDBIterator *poIter = poLayer->BuildIndex(
                                    oSelect.order_defs[0].field_name,
                                    oSelect.order_defs[0].ascending_flag,
                                    op, poValue);

                    /* Check that they are no NULL values */
                    if( oSelect.where_expr == NULL &&
                        poIter->GetRowCount() != poLayer->GetFeatureCount(FALSE) )
                    {
                        delete poIter;
                        poIter = NULL;
                    }

                    if( poIter != NULL )
                    {
                        CPLDebug("OpenFileGDB", "Using OGROpenFileGDBSimpleSQLLayer");
                        bLastSQLUsedOptimizedImplementation = TRUE;
                        return new OGROpenFileGDBSimpleSQLLayer(poLayer,
                                                                poIter,
                                                                oSelect.result_columns,
                                                                oSelect.column_defs);
                    }
                }
            }
        }
    }

    return OGRDataSource::ExecuteSQL(pszSQLCommand, poSpatialFilter, pszDialect);
}

/***********************************************************************/
/*                           ReleaseResultSet()                        */
/***********************************************************************/

void OGROpenFileGDBDataSource::ReleaseResultSet( OGRLayer * poResultsSet )
{
    delete poResultsSet;
}
