/******************************************************************************
 * $Id: ogropenfilegdblayer.cpp 31071 2015-10-19 14:28:09Z 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 "cpl_minixml.h"
#include <algorithm>

CPL_CVSID("$Id");

/************************************************************************/
/*                      OGROpenFileGDBGeomFieldDefn                     */
/************************************************************************/
class OGROpenFileGDBGeomFieldDefn: public OGRGeomFieldDefn
{
        OGROpenFileGDBLayer* m_poLayer;

    public:
        OGROpenFileGDBGeomFieldDefn(OGROpenFileGDBLayer* poLayer,
                                    const char *pszNameIn,
                                    OGRwkbGeometryType eGeomTypeIn) :
                OGRGeomFieldDefn(pszNameIn, eGeomTypeIn), m_poLayer(poLayer)
        {
        };

        ~OGROpenFileGDBGeomFieldDefn() {}

        void UnsetLayer() { m_poLayer = NULL; }

        virtual OGRSpatialReference* GetSpatialRef()
        {
            if( poSRS )
                return poSRS;
            if( m_poLayer != NULL )
                (void) m_poLayer->BuildLayerDefinition();
            return poSRS;
        }
};

/************************************************************************/
/*                      OGROpenFileGDBFeatureDefn                       */
/************************************************************************/
class OGROpenFileGDBFeatureDefn: public OGRFeatureDefn
{
        OGROpenFileGDBLayer* m_poLayer;
        int m_bHasBuildFieldDefn;

    public:
        OGROpenFileGDBFeatureDefn( OGROpenFileGDBLayer* poLayer,
                                   const char * pszName ) :
                        OGRFeatureDefn(pszName), m_poLayer(poLayer)
        {
            m_bHasBuildFieldDefn = FALSE;
        }

        ~OGROpenFileGDBFeatureDefn() {}

        void UnsetLayer()
        {
            if( nGeomFieldCount )
                ((OGROpenFileGDBGeomFieldDefn*)papoGeomFieldDefn[0])->UnsetLayer();
            m_poLayer = NULL;
        }

        virtual int GetFieldCount()
        {
            if( nFieldCount )
                return nFieldCount;
            if( !m_bHasBuildFieldDefn && m_poLayer != NULL )
            {
                m_bHasBuildFieldDefn = TRUE;
                (void) m_poLayer->BuildLayerDefinition();
            }
            return nFieldCount;
        }
};

/************************************************************************/
/*                      OGROpenFileGDBLayer()                           */
/************************************************************************/

OGROpenFileGDBLayer::OGROpenFileGDBLayer(const char* pszGDBFilename,
                                         const char* pszName,
                                         const std::string& osDefinition,
                                         const std::string& osDocumentation,
                                         const char* pszGeomName,
                                         OGRwkbGeometryType eGeomType) :
            m_osGDBFilename(pszGDBFilename),
            m_osName(pszName),
            m_poLyrTable(NULL),
            m_poFeatureDefn(NULL),
            m_iGeomFieldIdx(-1),
            m_iCurFeat(0),
            m_osDefinition(osDefinition),
            m_osDocumentation(osDocumentation),
            m_eGeomType(eGeomType),
            m_bValidLayerDefn(-1),
            m_bEOF(FALSE),
            m_poGeomConverter(NULL),
            m_iFieldToReadAsBinary(-1),
            m_poIterator(NULL),
            m_bIteratorSufficientToEvaluateFilter(FALSE),
            m_poIterMinMax(NULL),
            m_eSpatialIndexState(SPI_IN_BUILDING),
            m_pQuadTree(NULL),
            m_pahFilteredFeatures(NULL),
            m_nFilteredFeatureCount(-1)
{
    m_poFeatureDefn = new OGROpenFileGDBFeatureDefn(this, pszName);
    m_poFeatureDefn->SetGeomType(wkbNone);
    m_poFeatureDefn->Reference();

    if( m_osDefinition.size() && BuildGeometryColumnGDBv10() )
    {
        ;
    }
    else if( eGeomType != wkbNone )
    {
        m_poFeatureDefn->AddGeomFieldDefn(
            new OGROpenFileGDBGeomFieldDefn(this, pszGeomName, eGeomType), FALSE);
    }
}

/***********************************************************************/
/*                      ~OGROpenFileGDBLayer()                         */
/***********************************************************************/

OGROpenFileGDBLayer::~OGROpenFileGDBLayer()
{
    delete m_poLyrTable;
    if( m_poFeatureDefn )
    {
        m_poFeatureDefn->UnsetLayer();
        m_poFeatureDefn->Release();
    }
    delete m_poIterator;
    delete m_poIterMinMax;
    delete m_poGeomConverter;
    if( m_pQuadTree != NULL )
        CPLQuadTreeDestroy(m_pQuadTree);
    CPLFree(m_pahFilteredFeatures);
}

/************************************************************************/
/*                     BuildGeometryColumnGDBv10()                      */
/************************************************************************/

int OGROpenFileGDBLayer::BuildGeometryColumnGDBv10()
{
    CPLXMLNode* psTree = CPLParseXMLString(m_osDefinition.c_str());
    if( psTree == NULL )
    {
        return FALSE;
    }

    CPLStripXMLNamespace( psTree, NULL, TRUE );
    /* CPLSerializeXMLTreeToFile( psTree, "/dev/stderr" ); */
    CPLXMLNode* psInfo = CPLSearchXMLNode( psTree, "=DEFeatureClassInfo" );
    if( psInfo == NULL )
        psInfo = CPLSearchXMLNode( psTree, "=DETableInfo" );
    if( psInfo == NULL )
    {
        CPLDestroyXMLNode(psTree);
        return FALSE;
    }

    /* We cannot trust the XML definition to build the field definitions. */
    /* It sometimes misses a few fields ! */

    int bHasZ = CSLTestBoolean(CPLGetXMLValue( psInfo, "HasZ", "NO" ));
    const char* pszShapeType = CPLGetXMLValue(psInfo, "ShapeType", NULL);
    const char* pszShapeFieldName = CPLGetXMLValue(psInfo, "ShapeFieldName", NULL);
    if( pszShapeType != NULL && pszShapeFieldName != NULL )
    {
        m_eGeomType =
            FileGDBOGRGeometryConverter::GetGeometryTypeFromESRI(pszShapeType);
        if( bHasZ )
            m_eGeomType = (OGRwkbGeometryType)( m_eGeomType | wkb25DBit );

        const char* pszWKT = CPLGetXMLValue( psInfo, "SpatialReference.WKT", NULL );
        int nWKID = atoi(CPLGetXMLValue( psInfo, "SpatialReference.WKID", "0" ));
        /* The concept of LatestWKID is explained in http://resources.arcgis.com/en/help/arcgis-rest-api/index.html#//02r3000000n1000000 */
        int nLatestWKID = atoi(CPLGetXMLValue( psInfo, "SpatialReference.LatestWKID", "0" ));

        OGROpenFileGDBGeomFieldDefn* poGeomFieldDefn =
            new OGROpenFileGDBGeomFieldDefn(NULL, pszShapeFieldName, m_eGeomType);

        OGRSpatialReference* poSRS = NULL;
        if( nWKID > 0 || nLatestWKID > 0 )
        {
            int bSuccess = FALSE;
            poSRS = new OGRSpatialReference();
            CPLPushErrorHandler(CPLQuietErrorHandler);
            /* Try first with nLatestWKID as there's a higher chance it is a EPSG code and not an ESRI one */
            if( nLatestWKID > 0 )
            {
                if( poSRS->importFromEPSG(nLatestWKID) == OGRERR_NONE )
                {
                    bSuccess = TRUE;
                }
                else
                {
                    CPLDebug("OpenFileGDB", "Cannot import SRID %d", nLatestWKID);
                }
            }
            if( !bSuccess && nWKID > 0 )
            {
                if( poSRS->importFromEPSG(nWKID) == OGRERR_NONE )
                {
                    bSuccess = TRUE;
                }
                else
                {
                    CPLDebug("OpenFileGDB", "Cannot import SRID %d", nWKID);
                }
            }
            if( !bSuccess )
            {
                delete poSRS;
                poSRS = NULL;
            }
            CPLPopErrorHandler();
            CPLErrorReset();
        }
        if( poSRS == NULL && pszWKT != NULL && pszWKT[0] != '{' )
        {
            poSRS = new OGRSpatialReference( pszWKT );
            if( poSRS->morphFromESRI() != OGRERR_NONE )
            {
                delete poSRS;
                poSRS = NULL;
            }
        }
        if( poSRS != NULL )
        {
            poGeomFieldDefn->SetSpatialRef(poSRS);
            poSRS->Dereference();
        }
        m_poFeatureDefn->AddGeomFieldDefn(poGeomFieldDefn, FALSE);
    }
    else
    {
        m_eGeomType = wkbNone;
    }
    CPLDestroyXMLNode(psTree);
    return TRUE;
}

/************************************************************************/
/*                      BuildLayerDefinition()                          */
/************************************************************************/

int OGROpenFileGDBLayer::BuildLayerDefinition()
{
    if( m_bValidLayerDefn >= 0 )
        return m_bValidLayerDefn;

    m_poLyrTable = new FileGDBTable();
    if( !(m_poLyrTable->Open(m_osGDBFilename)) )
    {
        delete m_poLyrTable;
        m_poLyrTable = NULL;
        m_bValidLayerDefn = FALSE;
        return FALSE;
    }

    m_bValidLayerDefn = TRUE;

    m_iGeomFieldIdx = m_poLyrTable->GetGeomFieldIdx();
    if( m_iGeomFieldIdx >= 0 )
    {
        FileGDBGeomField* poGDBGeomField =
            (FileGDBGeomField* )m_poLyrTable->GetField(m_iGeomFieldIdx);
        m_poGeomConverter = FileGDBOGRGeometryConverter::BuildConverter(poGDBGeomField);

        if( CSLTestBoolean(CPLGetConfigOption("OPENFILEGDB_IN_MEMORY_SPI", "YES")) )
        {
            CPLRectObj sGlobalBounds;
            sGlobalBounds.minx = poGDBGeomField->GetXMin();
            sGlobalBounds.miny = poGDBGeomField->GetYMin();
            sGlobalBounds.maxx = poGDBGeomField->GetXMax();
            sGlobalBounds.maxy = poGDBGeomField->GetYMax();
            m_pQuadTree = CPLQuadTreeCreate(&sGlobalBounds,
                                            NULL);
            CPLQuadTreeSetMaxDepth(m_pQuadTree,
                CPLQuadTreeGetAdvisedMaxDepth(m_poLyrTable->GetValidRecordCount()));
        }
        else
        {
            m_eSpatialIndexState = SPI_INVALID;
        }
    }

    if( m_osDefinition.size() == 0 && m_iGeomFieldIdx >= 0 )
    {
        FileGDBGeomField* poGDBGeomField =
            (FileGDBGeomField* )m_poLyrTable->GetField(m_iGeomFieldIdx);
        const char* pszName = poGDBGeomField->GetName().c_str();
        FileGDBTableGeometryType eGDBGeomType = m_poLyrTable->GetGeometryType();

        OGRwkbGeometryType eGeomType = wkbUnknown;
        switch( eGDBGeomType )
        {
            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;
        }
        if( m_eGeomType == wkbUnknown )
            m_eGeomType = eGeomType;
        if( eGeomType != m_eGeomType )
        {
            CPLError(CE_Warning, CPLE_AppDefined,
                     "Inconsistency for layer geometry type");
        }

        OGROpenFileGDBGeomFieldDefn* poGeomFieldDefn;
        if( m_poFeatureDefn->GetGeomFieldCount() == 0 )
        {
            poGeomFieldDefn =
                    new OGROpenFileGDBGeomFieldDefn(NULL, pszName, m_eGeomType);
            m_poFeatureDefn->AddGeomFieldDefn(poGeomFieldDefn, FALSE);
        }
        else
        {
            poGeomFieldDefn = (OGROpenFileGDBGeomFieldDefn*)
                                        m_poFeatureDefn->GetGeomFieldDefn(0);
            poGeomFieldDefn->SetType(m_eGeomType);
        }

        OGRSpatialReference* poSRS = NULL;
        if( poGDBGeomField->GetWKT().size() && 
            poGDBGeomField->GetWKT()[0] != '{' )
        {
            poSRS = new OGRSpatialReference( poGDBGeomField->GetWKT().c_str() );
            if( poSRS->morphFromESRI() != OGRERR_NONE )
            {
                delete poSRS;
                poSRS = NULL;
            }
        }
        if( poSRS != NULL )
        {
            poGeomFieldDefn->SetSpatialRef(poSRS);
            poSRS->Dereference();
        }
    }

    for(int i=0;i<m_poLyrTable->GetFieldCount();i++)
    {
        if( i == m_iGeomFieldIdx )
            continue;

        const FileGDBField* poGDBField = m_poLyrTable->GetField(i);
        OGRFieldType eType = OFTString;
        /* int nWidth = 0; */
        switch( poGDBField->GetType() )
        {
            case FGFT_INT16:
            case FGFT_INT32:
                eType = OFTInteger;
                break;
            case FGFT_FLOAT32:
            case FGFT_FLOAT64:
                eType = OFTReal;
                break;
            case FGFT_STRING:
                /* nWidth = poGDBField->GetMaxWidth(); */
                eType = OFTString;
                break;
            case FGFT_UUID_1:
            case FGFT_UUID_2:
            case FGFT_XML:
                eType = OFTString;
                break;
            case FGFT_DATETIME:
                eType = OFTDateTime;
                break;
            case FGFT_UNDEFINED:
            case FGFT_OBJECTID:
            case FGFT_GEOMETRY:
                CPLAssert(FALSE);
                break;
            case FGFT_BINARY:
            case FGFT_RASTER:
            {
                /* Special case for v9 GDB_UserMetadata table */
                if( m_iFieldToReadAsBinary < 0 &&
                    poGDBField->GetName() == "Xml" &&
                    poGDBField->GetType() == FGFT_BINARY )
                {
                    m_iFieldToReadAsBinary = i;
                    eType = OFTString;
                }
                else
                    eType = OFTBinary;
                break;
            }
        }
        OGRFieldDefn oFieldDefn(poGDBField->GetName().c_str(), eType);
        /* oFieldDefn.SetWidth(nWidth); */
        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
    }

    return TRUE;
}

/************************************************************************/
/*                           GetGeomType()                              */
/************************************************************************/

OGRwkbGeometryType OGROpenFileGDBLayer::GetGeomType()
{
    if( m_eGeomType == wkbUnknown )
    {
        (void) BuildLayerDefinition();
    }

    return m_eGeomType;
}

/***********************************************************************/
/*                          GetLayerDefn()                             */
/***********************************************************************/

OGRFeatureDefn* OGROpenFileGDBLayer::GetLayerDefn()
{
    return m_poFeatureDefn;
}

/***********************************************************************/
/*                          GetFIDColumn()                             */
/***********************************************************************/

const char* OGROpenFileGDBLayer::GetFIDColumn()
{
    if( m_osFIDName.size() )
        return m_osFIDName.c_str();

    if( !BuildLayerDefinition() )
        return "";
    return m_poLyrTable->GetObjectIdColName().c_str();
}

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

void OGROpenFileGDBLayer::ResetReading()
{
    if( m_iCurFeat != 0 )
    {
        if( m_eSpatialIndexState == SPI_IN_BUILDING )
            m_eSpatialIndexState = SPI_INVALID;
    }
    m_bEOF = FALSE;
    m_iCurFeat = 0;
    if( m_poIterator )
        m_poIterator->Reset();
}

/***********************************************************************/
/*                         SetSpatialFilter()                          */
/***********************************************************************/

void OGROpenFileGDBLayer::SetSpatialFilter( OGRGeometry *poGeom )
{
    if( !BuildLayerDefinition() )
        return;

    OGRLayer::SetSpatialFilter(poGeom);

    if( m_bFilterIsEnvelope )
    {
        OGREnvelope sLayerEnvelope;
        if( GetExtent(&sLayerEnvelope, FALSE) == OGRERR_NONE )
        {
            if( m_sFilterEnvelope.MinX <= sLayerEnvelope.MinX &&
                m_sFilterEnvelope.MinY <= sLayerEnvelope.MinY &&
                m_sFilterEnvelope.MaxX >= sLayerEnvelope.MaxX &&
                m_sFilterEnvelope.MaxY >= sLayerEnvelope.MaxY )
            {
                CPLDebug("OpenFileGDB", "Disabling spatial filter since it "
                         "contains the layer spatial extent");
                poGeom = NULL;
                OGRLayer::SetSpatialFilter(poGeom);
            }
        }
    }

    if( poGeom != NULL )
    {
        if( m_eSpatialIndexState == SPI_COMPLETED )
        {
            CPLRectObj aoi;
            aoi.minx = m_sFilterEnvelope.MinX;
            aoi.miny = m_sFilterEnvelope.MinY;
            aoi.maxx = m_sFilterEnvelope.MaxX;
            aoi.maxy = m_sFilterEnvelope.MaxY;
            CPLFree(m_pahFilteredFeatures);
            m_nFilteredFeatureCount = -1;
            m_pahFilteredFeatures = CPLQuadTreeSearch(m_pQuadTree,
                                                      &aoi,
                                                      &m_nFilteredFeatureCount);
            if( m_nFilteredFeatureCount >= 0 )
            {
                size_t* panStart = (size_t*)m_pahFilteredFeatures;
                std::sort(panStart, panStart + m_nFilteredFeatureCount);
            }
        }
        m_poLyrTable->InstallFilterEnvelope(&m_sFilterEnvelope);
    }
    else
    {
        CPLFree(m_pahFilteredFeatures);
        m_pahFilteredFeatures = NULL;
        m_nFilteredFeatureCount = -1;
        m_poLyrTable->InstallFilterEnvelope(NULL);
    }
}

/***********************************************************************/
/*                            CompValues()                             */
/***********************************************************************/

static int CompValues(OGRFieldDefn* poFieldDefn,
                      const swq_expr_node* poValue1,
                      const swq_expr_node* poValue2)
{
    switch( poFieldDefn->GetType() )
    {
        case OFTInteger:
        {
            int n1, n2;
            if (poValue1->field_type == SWQ_FLOAT)
                n1 = (int) poValue1->float_value;
            else
                n1 = poValue1->int_value;
            if (poValue2->field_type == SWQ_FLOAT)
                n2 = (int) poValue2->float_value;
            else
                n2 = poValue2->int_value;
            if( n1 < n2 )
                return -1;
            if( n1 == n2 )
                return 0;
            else 
                return 1;
            break;
        }

        case OFTReal:
            if( poValue1->float_value < poValue2->float_value )
                return -1;
            if( poValue1->float_value == poValue2->float_value )
                return 0;
            else
                return 1;
            break;

        case OFTString:
            return strcmp(poValue1->string_value, poValue2->string_value);
            break;

        case OFTDate:
        case OFTTime:
        case OFTDateTime:
        {
            if ((poValue1->field_type == SWQ_TIMESTAMP ||
                 poValue1->field_type == SWQ_DATE ||
                 poValue1->field_type == SWQ_TIME) &&
                (poValue2->field_type == SWQ_TIMESTAMP ||
                 poValue2->field_type == SWQ_DATE ||
                 poValue2->field_type == SWQ_TIME))
            {
                return strcmp(poValue1->string_value, poValue2->string_value);
            }
            return 0;
            break;
        }

        default:
            return 0;
            break;
    }
}

/***********************************************************************/
/*                    OGROpenFileGDBIsComparisonOp()                   */
/***********************************************************************/

int OGROpenFileGDBIsComparisonOp(int op)
{
    return (op == SWQ_EQ || op == SWQ_NE || op == SWQ_LT ||
            op == SWQ_LE || op == SWQ_GT || op == SWQ_GE);
}

/***********************************************************************/
/*                        AreExprExclusive()                           */
/***********************************************************************/

static const struct 
{
    swq_op op1;
    swq_op op2;
    int    expected_comp_1;
    int    expected_comp_2;
}
asPairsOfComparisons[] = 
{
    { SWQ_EQ, SWQ_EQ, -1, 1 },
    { SWQ_LT, SWQ_GT, -1, 0 },
    { SWQ_GT, SWQ_LT, 0, 1 },
    { SWQ_LT, SWQ_GE, -1, 999 },
    { SWQ_LE, SWQ_GE, -1, 999 },
    { SWQ_LE, SWQ_GT, -1, 999 },
    { SWQ_GE, SWQ_LE, 1, 999 },
    { SWQ_GE, SWQ_LT, 1, 999 },
    { SWQ_GT, SWQ_LE, 1, 999 }
};

static int AreExprExclusive(OGRFeatureDefn* poFeatureDefn,
                            const swq_expr_node* poNode1,
                            const swq_expr_node* poNode2)
{
    if( poNode1->eNodeType != SNT_OPERATION )
        return FALSE;
    if( poNode2->eNodeType != SNT_OPERATION )
        return FALSE;

    const size_t nParis = sizeof(asPairsOfComparisons) / sizeof(asPairsOfComparisons[0]);
    for(size_t i = 0; i < nParis; i++ )
    {
        if( poNode1->nOperation == asPairsOfComparisons[i].op1 &&
            poNode2->nOperation == asPairsOfComparisons[i].op2 &&
            poNode1->nSubExprCount == 2 &&
            poNode2->nSubExprCount == 2 )
        {
            swq_expr_node *poColumn1 = poNode1->papoSubExpr[0];
            swq_expr_node *poValue1 = poNode1->papoSubExpr[1];
            swq_expr_node *poColumn2 = poNode2->papoSubExpr[0];
            swq_expr_node *poValue2 = poNode2->papoSubExpr[1];
            if( poColumn1->eNodeType == SNT_COLUMN &&
                poValue1->eNodeType == SNT_CONSTANT &&
                poColumn2->eNodeType == SNT_COLUMN &&
                poValue2->eNodeType == SNT_CONSTANT &&
                poColumn1->field_index == poColumn2->field_index &&
                poColumn1->field_index < poFeatureDefn->GetFieldCount() )
            {
                OGRFieldDefn *poFieldDefn;
                poFieldDefn = poFeatureDefn->GetFieldDefn(poColumn1->field_index);

                int nComp = CompValues(poFieldDefn, poValue1, poValue2);
                return nComp == asPairsOfComparisons[i].expected_comp_1 ||
                       nComp == asPairsOfComparisons[i].expected_comp_2;
            }
            return FALSE;
        }
    }

    if( (poNode2->nOperation == SWQ_ISNULL &&
         OGROpenFileGDBIsComparisonOp(poNode1->nOperation) &&
         poNode1->nSubExprCount == 2 &&
         poNode2->nSubExprCount == 1) ||
        (poNode1->nOperation == SWQ_ISNULL &&
         OGROpenFileGDBIsComparisonOp(poNode2->nOperation) &&
         poNode2->nSubExprCount == 2 &&
         poNode1->nSubExprCount == 1))
    {
        swq_expr_node *poColumn1 = poNode1->papoSubExpr[0];
        swq_expr_node *poColumn2 = poNode2->papoSubExpr[0];
        if( poColumn1->eNodeType == SNT_COLUMN &&
            poColumn2->eNodeType == SNT_COLUMN &&
            poColumn1->field_index == poColumn2->field_index &&
            poColumn1->field_index < poFeatureDefn->GetFieldCount() )
        {
            return TRUE;
        }
    }

    /* In doubt: return FALSE */
    return FALSE;
}


/***********************************************************************/
/*                     FillTargetValueFromSrcExpr()                    */
/***********************************************************************/

static
int FillTargetValueFromSrcExpr( OGRFieldDefn* poFieldDefn,
                                OGRField* poTargetValue,
                                const swq_expr_node* poSrcValue )
{
    switch( poFieldDefn->GetType() )
    {
        case OFTInteger:
            if (poSrcValue->field_type == SWQ_FLOAT)
                poTargetValue->Integer = (int) poSrcValue->float_value;
            else
                poTargetValue->Integer = poSrcValue->int_value;
            break;

        case OFTReal:
            poTargetValue->Real = poSrcValue->float_value;
            break;

        case OFTString:
            poTargetValue->String = poSrcValue->string_value;
            break;

        case OFTDate:
        case OFTTime:
        case OFTDateTime:
            if (poSrcValue->field_type == SWQ_TIMESTAMP ||
                poSrcValue->field_type == SWQ_DATE ||
                poSrcValue->field_type == SWQ_TIME)
            {
                int nYear = 0, nMonth = 0, nDay = 0, nHour = 0, nMin = 0, nSec = 0;
                if( sscanf(poSrcValue->string_value, "%04d/%02d/%02d %02d:%02d:%02d",
                           &nYear, &nMonth, &nDay, &nHour, &nMin, &nSec) == 6 ||
                    sscanf(poSrcValue->string_value, "%04d/%02d/%02d",
                           &nYear, &nMonth, &nDay) == 3 ||
                    sscanf(poSrcValue->string_value, "%02d:%02d:%02d",
                           &nHour, &nMin, &nSec) == 3 )
                {
                    poTargetValue->Date.Year = (GInt16)nYear;
                    poTargetValue->Date.Month = (GByte)nMonth;
                    poTargetValue->Date.Day = (GByte)nDay;
                    poTargetValue->Date.Hour = (GByte)nHour;
                    poTargetValue->Date.Minute = (GByte)nMin;
                    poTargetValue->Date.Second = (GByte)nSec;
                    poTargetValue->Date.TZFlag = 0;
                }
                else
                    return FALSE;
            }
            else
                return FALSE;
            break;

        default:
            return FALSE;
    }
    return TRUE;
}

/***********************************************************************/
/*                        GetColumnSubNode()                           */
/***********************************************************************/

static swq_expr_node* GetColumnSubNode(swq_expr_node* poNode)
{
    if( poNode->eNodeType == SNT_OPERATION &&
        poNode->nSubExprCount == 2 )
    {
        if( poNode->papoSubExpr[0]->eNodeType == SNT_COLUMN )
            return poNode->papoSubExpr[0];
        if( poNode->papoSubExpr[1]->eNodeType == SNT_COLUMN )
            return poNode->papoSubExpr[1];
    }
    return NULL;
}

/***********************************************************************/
/*                        GetConstantSubNode()                         */
/***********************************************************************/

static swq_expr_node* GetConstantSubNode(swq_expr_node* poNode)
{
    if( poNode->eNodeType == SNT_OPERATION &&
        poNode->nSubExprCount == 2 )
    {
        if( poNode->papoSubExpr[1]->eNodeType == SNT_CONSTANT )
            return poNode->papoSubExpr[1];
        if( poNode->papoSubExpr[0]->eNodeType == SNT_CONSTANT )
            return poNode->papoSubExpr[0];
    }
    return NULL;
}
/***********************************************************************/
/*                     BuildIteratorFromExprNode()                     */
/***********************************************************************/

FileGDBIterator* OGROpenFileGDBLayer::BuildIteratorFromExprNode(swq_expr_node* poNode)
{
    if( m_bIteratorSufficientToEvaluateFilter == FALSE )
        return NULL;

    if( poNode->eNodeType == SNT_OPERATION &&
        poNode->nOperation == SWQ_AND && poNode->nSubExprCount == 2 )
    {
        /* Even if there's only one branch of the 2 that results to an iterator, */
        /* it is usefull. Of course, the iterator will not be sufficient to evaluate */
        /* the filter, but it will be a super-set of the features */
        FileGDBIterator* poIter1 = BuildIteratorFromExprNode(poNode->papoSubExpr[0]);

        /* In case the first branch didn't result to an iterator, temporarily */
        /* restore the flag */
        int bSaveIteratorSufficientToEvaluateFilter = m_bIteratorSufficientToEvaluateFilter;
        m_bIteratorSufficientToEvaluateFilter = -1;
        FileGDBIterator* poIter2 = BuildIteratorFromExprNode(poNode->papoSubExpr[1]);
        m_bIteratorSufficientToEvaluateFilter = bSaveIteratorSufficientToEvaluateFilter;

        if( poIter1 != NULL && poIter2 != NULL )
            return FileGDBIterator::BuildAnd(poIter1, poIter2);
        m_bIteratorSufficientToEvaluateFilter = FALSE;
        if( poIter1 != NULL )
            return poIter1;
        if( poIter2 != NULL )
            return poIter2;
    }

    else if( poNode->eNodeType == SNT_OPERATION &&
        poNode->nOperation == SWQ_OR && poNode->nSubExprCount == 2 )
    {
        /* For a OR, we need an iterator for the 2 branches */
        FileGDBIterator* poIter1 = BuildIteratorFromExprNode(poNode->papoSubExpr[0]);
        if( poIter1 != NULL )
        {
            FileGDBIterator* poIter2 = BuildIteratorFromExprNode(poNode->papoSubExpr[1]);
            if( poIter2 == NULL )
            {
                delete poIter1;
            }
            else
            {
                return FileGDBIterator::BuildOr(poIter1, poIter2,
                                        AreExprExclusive(GetLayerDefn(),
                                                        poNode->papoSubExpr[0],
                                                        poNode->papoSubExpr[1]));
            }
        }
    }

    else if( poNode->eNodeType == SNT_OPERATION &&
             OGROpenFileGDBIsComparisonOp(poNode->nOperation) &&
             poNode->nSubExprCount == 2 )
    {
        swq_expr_node *poColumn = GetColumnSubNode(poNode);
        swq_expr_node *poValue = GetConstantSubNode(poNode);
        if( poColumn != NULL && poValue != NULL &&
            poColumn->field_index < GetLayerDefn()->GetFieldCount())
        {
            OGRFieldDefn *poFieldDefn;
            poFieldDefn = GetLayerDefn()->GetFieldDefn(poColumn->field_index);

            int nTableColIdx = m_poLyrTable->GetFieldIdx(poFieldDefn->GetNameRef());
            if( nTableColIdx >= 0 && m_poLyrTable->GetField(nTableColIdx)->HasIndex() )
            {
                OGRField sValue;

                if( FillTargetValueFromSrcExpr(poFieldDefn, &sValue, poValue) )
                {
                    FileGDBSQLOp eOp;
                    if( poColumn == poNode->papoSubExpr[0] )
                    {
                        switch( poNode->nOperation )
                        {
                            case SWQ_LE: eOp = FGSO_LE; break;
                            case SWQ_LT: eOp = FGSO_LT; break;
                            case SWQ_NE: eOp = FGSO_EQ; /* yes : EQ */ break;
                            case SWQ_EQ: eOp = FGSO_EQ; break;
                            case SWQ_GE: eOp = FGSO_GE; break;
                            case SWQ_GT: eOp = FGSO_GT; break;
                            default: eOp = FGSO_EQ; CPLAssert(FALSE); break;
                        }
                    }
                    else
                    {
                        /* If "constant op column", then we must reverse */
                        /* the operator */
                        switch( poNode->nOperation )
                        {
                            case SWQ_LE: eOp = FGSO_GE; break;
                            case SWQ_LT: eOp = FGSO_GT; break;
                            case SWQ_NE: eOp = FGSO_EQ; /* yes : EQ */ break;
                            case SWQ_EQ: eOp = FGSO_EQ; break;
                            case SWQ_GE: eOp = FGSO_LE; break;
                            case SWQ_GT: eOp = FGSO_LT; break;
                            default: eOp = FGSO_EQ; CPLAssert(FALSE); break;
                        }
                    }

                    FileGDBIterator* poIter = FileGDBIterator::Build(
                        m_poLyrTable, nTableColIdx, TRUE,
                        eOp, poFieldDefn->GetType(), &sValue);
                    if( poIter != NULL )
                        m_bIteratorSufficientToEvaluateFilter = TRUE;
                    if( poIter && poNode->nOperation == SWQ_NE )
                        return FileGDBIterator::BuildNot(poIter);
                    else
                        return poIter;
                }
            }
        }
    }
    else if( poNode->eNodeType == SNT_OPERATION &&
             poNode->nOperation == SWQ_ISNULL && poNode->nSubExprCount == 1 )
    {
        swq_expr_node *poColumn = poNode->papoSubExpr[0];
        if( poColumn->eNodeType == SNT_COLUMN &&
            poColumn->field_index < GetLayerDefn()->GetFieldCount() )
        {
            OGRFieldDefn *poFieldDefn;
            poFieldDefn = GetLayerDefn()->GetFieldDefn(poColumn->field_index);

            int nTableColIdx = m_poLyrTable->GetFieldIdx(poFieldDefn->GetNameRef());
            if( nTableColIdx >= 0 && m_poLyrTable->GetField(nTableColIdx)->HasIndex() )
            {
                FileGDBIterator* poIter = FileGDBIterator::BuildIsNotNull(
                                                    m_poLyrTable, nTableColIdx, TRUE);
                if( poIter )
                {
                    m_bIteratorSufficientToEvaluateFilter = TRUE;
                    poIter = FileGDBIterator::BuildNot(poIter);
                }
                return poIter;
            }
        }
    }
    else if( poNode->eNodeType == SNT_OPERATION &&
                poNode->nOperation == SWQ_NOT && poNode->nSubExprCount == 1 &&
                poNode->papoSubExpr[0]->eNodeType == SNT_OPERATION &&
                poNode->papoSubExpr[0]->nOperation == SWQ_ISNULL &&
                poNode->papoSubExpr[0]->nSubExprCount == 1 )
    {
        swq_expr_node *poColumn = poNode->papoSubExpr[0]->papoSubExpr[0];
        if( poColumn->eNodeType == SNT_COLUMN &&
            poColumn->field_index < GetLayerDefn()->GetFieldCount() )
        {
            OGRFieldDefn *poFieldDefn;
            poFieldDefn = GetLayerDefn()->GetFieldDefn(poColumn->field_index);

            int nTableColIdx = m_poLyrTable->GetFieldIdx(poFieldDefn->GetNameRef());
            if( nTableColIdx >= 0 && m_poLyrTable->GetField(nTableColIdx)->HasIndex() )
            {
                FileGDBIterator* poIter = FileGDBIterator::BuildIsNotNull(
                                                m_poLyrTable, nTableColIdx, TRUE);
                if( poIter )
                    m_bIteratorSufficientToEvaluateFilter = TRUE;
                return poIter;
            }
        }
    }
    else if( poNode->eNodeType == SNT_OPERATION &&
        poNode->nOperation == SWQ_BETWEEN && poNode->nSubExprCount == 3 )
    {
        swq_expr_node *poColumn = poNode->papoSubExpr[0];
        swq_expr_node *poValue1 = poNode->papoSubExpr[1];
        swq_expr_node *poValue2 = poNode->papoSubExpr[2];
        if( poColumn->eNodeType == SNT_COLUMN &&
            poColumn->field_index < GetLayerDefn()->GetFieldCount() &&
            poValue1->eNodeType == SNT_CONSTANT &&
            poValue2->eNodeType == SNT_CONSTANT )
        {
            OGRFieldDefn *poFieldDefn;
            poFieldDefn = GetLayerDefn()->GetFieldDefn(poColumn->field_index);

            int nTableColIdx = m_poLyrTable->GetFieldIdx(poFieldDefn->GetNameRef());
            if( nTableColIdx >= 0 && m_poLyrTable->GetField(nTableColIdx)->HasIndex() )
            {
                OGRField sValue1, sValue2;

                if( FillTargetValueFromSrcExpr(poFieldDefn, &sValue1, poValue1) &&
                    FillTargetValueFromSrcExpr(poFieldDefn, &sValue2, poValue2) )
                {
                    FileGDBIterator* poIter1 = FileGDBIterator::Build(
                                            m_poLyrTable, nTableColIdx, TRUE,
                                            FGSO_GE,
                                            poFieldDefn->GetType(), &sValue1);
                    FileGDBIterator* poIter2 = FileGDBIterator::Build(
                                            m_poLyrTable, nTableColIdx, TRUE,
                                            FGSO_LE,
                                            poFieldDefn->GetType(), &sValue2);
                    if( poIter1 != NULL && poIter2 != NULL )
                    {
                        m_bIteratorSufficientToEvaluateFilter = TRUE;
                        return FileGDBIterator::BuildAnd(poIter1, poIter2);
                    }
                    delete poIter1;
                    delete poIter2;
                }
            }
        }
    }
    else if( poNode->eNodeType == SNT_OPERATION &&
             poNode->nOperation == SWQ_IN && poNode->nSubExprCount >= 2 )
    {
        swq_expr_node *poColumn = poNode->papoSubExpr[0];
        if( poColumn->eNodeType == SNT_COLUMN &&
            poColumn->field_index < GetLayerDefn()->GetFieldCount() )
        {
            int bAllConstants = TRUE;
            int i;
            for(i=1;i<poNode->nSubExprCount;i++)
            {
                if( poNode->papoSubExpr[i]->eNodeType != SNT_CONSTANT )
                    bAllConstants = FALSE;
            }
            OGRFieldDefn *poFieldDefn;
            poFieldDefn = GetLayerDefn()->GetFieldDefn(poColumn->field_index);

            int nTableColIdx = m_poLyrTable->GetFieldIdx(poFieldDefn->GetNameRef());
            if( bAllConstants && nTableColIdx >= 0 &&
                m_poLyrTable->GetField(nTableColIdx)->HasIndex() )
            {
                FileGDBIterator* poRet = NULL;
                for(i=1;i<poNode->nSubExprCount;i++)
                {
                    OGRField sValue;
                    if( !FillTargetValueFromSrcExpr(poFieldDefn, &sValue,
                                                    poNode->papoSubExpr[i]) )
                    {
                        delete poRet;
                        poRet = NULL;
                        break;
                    }
                    FileGDBIterator* poIter = FileGDBIterator::Build(
                                        m_poLyrTable, nTableColIdx, TRUE, FGSO_EQ,
                                        poFieldDefn->GetType(), &sValue);
                    if( poIter == NULL )
                    {
                        delete poRet;
                        poRet = NULL;
                        break;
                    }
                    if( poRet == NULL )
                        poRet = poIter;
                    else
                        poRet = FileGDBIterator::BuildOr(poRet, poIter);
                }
                if( poRet != NULL )
                {
                    m_bIteratorSufficientToEvaluateFilter = TRUE;
                    return poRet;
                }
            }
        }
    }
    else if( poNode->eNodeType == SNT_OPERATION &&
             poNode->nOperation == SWQ_NOT && poNode->nSubExprCount == 1 )
    {
        FileGDBIterator* poIter = BuildIteratorFromExprNode(poNode->papoSubExpr[0]);
        /* If we have an iterator that is only partial w.r.t the full clause */
        /* then we cannot do anything with it unfortunately */
        if( m_bIteratorSufficientToEvaluateFilter == FALSE )
        {
            if( poIter != NULL )
                CPLDebug("OpenFileGDB", "Disabling use of indexes");
            delete poIter;
        }
        else if( poIter != NULL )
        {
            return FileGDBIterator::BuildNot(poIter);
        }
    }


    if( m_bIteratorSufficientToEvaluateFilter == TRUE )
        CPLDebug("OpenFileGDB", "Disabling use of indexes");
    m_bIteratorSufficientToEvaluateFilter = FALSE;
    return NULL;
}

/***********************************************************************/
/*                         SetAttributeFilter()                        */
/***********************************************************************/

OGRErr OGROpenFileGDBLayer::SetAttributeFilter( const char* pszFilter )
{
    if( !BuildLayerDefinition() )
        return OGRERR_FAILURE;

    delete m_poIterator;
    m_poIterator = NULL;
    m_bIteratorSufficientToEvaluateFilter = FALSE;

    OGRErr eErr = OGRLayer::SetAttributeFilter(pszFilter);
    if( eErr != OGRERR_NONE ||
        !CSLTestBoolean(CPLGetConfigOption("OPENFILEGDB_USE_INDEX", "YES")) )
        return eErr;

    if( m_poAttrQuery != NULL && m_nFilteredFeatureCount < 0 )
    {
        swq_expr_node* poNode = (swq_expr_node*) m_poAttrQuery->GetSWGExpr();
        m_bIteratorSufficientToEvaluateFilter = -1;
        m_poIterator = BuildIteratorFromExprNode(poNode);
        if( m_poIterator != NULL && m_eSpatialIndexState == SPI_IN_BUILDING )
            m_eSpatialIndexState = SPI_INVALID;
        if( m_bIteratorSufficientToEvaluateFilter < 0 )
            m_bIteratorSufficientToEvaluateFilter = FALSE;
    }
    return eErr;
}

/***********************************************************************/
/*                         GetCurrentFeature()                         */
/***********************************************************************/

OGRFeature* OGROpenFileGDBLayer::GetCurrentFeature()
{
    OGRFeature *poFeature = NULL;
    int iOGRIdx = 0;
    int iRow = m_poLyrTable->GetCurRow();
    for(int iGDBIdx=0;iGDBIdx<m_poLyrTable->GetFieldCount();iGDBIdx++)
    {
        if( iGDBIdx == m_iGeomFieldIdx )
        {
            if( m_poFeatureDefn->GetGeomFieldDefn(0)->IsIgnored() )
            {
                if( m_eSpatialIndexState == SPI_IN_BUILDING )
                    m_eSpatialIndexState = SPI_INVALID;
                continue;
            }

            const OGRField* psField = m_poLyrTable->GetFieldValue(iGDBIdx);
            if( psField != NULL )
            {
                if( m_eSpatialIndexState == SPI_IN_BUILDING )
                {
                    OGREnvelope sFeatureEnvelope;
                    if( m_poLyrTable->GetFeatureExtent(psField,
                                                       &sFeatureEnvelope) )
                    {
                        CPLRectObj sBounds;
                        sBounds.minx = sFeatureEnvelope.MinX;
                        sBounds.miny = sFeatureEnvelope.MinY;
                        sBounds.maxx = sFeatureEnvelope.MaxX;
                        sBounds.maxy = sFeatureEnvelope.MaxY;
                        CPLQuadTreeInsertWithBounds(m_pQuadTree,
                                                    (void*)(size_t)iRow,
                                                    &sBounds);
                    }
                }

                if( m_poFilterGeom != NULL &&
                    m_eSpatialIndexState != SPI_COMPLETED &&
                    !m_poLyrTable->DoesGeometryIntersectsFilterEnvelope(psField) )
                {
                    delete poFeature;
                    return NULL;
                }

                OGRGeometry* poGeom = m_poGeomConverter->GetAsGeometry(psField);
                if( poGeom != NULL )
                {
                    OGRwkbGeometryType eFlattenType = wkbFlatten(poGeom->getGeometryType());
                    if( eFlattenType == wkbPolygon )
                        poGeom = OGRGeometryFactory::forceToMultiPolygon(poGeom);
                    else if( eFlattenType == wkbLineString )
                        poGeom = OGRGeometryFactory::forceToMultiLineString(poGeom);
                    poGeom->assignSpatialReference(
                        m_poFeatureDefn->GetGeomFieldDefn(0)->GetSpatialRef() );

                    if( poFeature == NULL )
                        poFeature = new OGRFeature(m_poFeatureDefn);
                    poFeature->SetGeometryDirectly( poGeom );
                }
            }
        }
        else
        {
            if( !m_poFeatureDefn->GetFieldDefn(iOGRIdx)->IsIgnored() )
            {
                const OGRField* psField = m_poLyrTable->GetFieldValue(iGDBIdx);
                if( psField != NULL )
                {
                    if( poFeature == NULL )
                        poFeature = new OGRFeature(m_poFeatureDefn);

                    if( iGDBIdx == m_iFieldToReadAsBinary )
                        poFeature->SetField(iOGRIdx, (const char*) psField->Binary.paData);
                    else
                        poFeature->SetField(iOGRIdx, (OGRField*) psField);
                }
            }
            iOGRIdx ++;
        }
    }

    if( poFeature == NULL )
        poFeature = new OGRFeature(m_poFeatureDefn);
    poFeature->SetFID(iRow + 1);
    return poFeature;
}

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

OGRFeature* OGROpenFileGDBLayer::GetNextFeature()
{
    if( !BuildLayerDefinition() || m_bEOF )
        return NULL;

    while( TRUE )
    {
        OGRFeature *poFeature = NULL;

        if( m_nFilteredFeatureCount >= 0 )
        {
            while( TRUE )
            {
                if( m_iCurFeat >= m_nFilteredFeatureCount )
                {
                    return NULL;
                }
                int iRow = (int)(size_t)m_pahFilteredFeatures[m_iCurFeat++];
                if( m_poLyrTable->SelectRow(iRow) )
                {
                    poFeature = GetCurrentFeature();
                    if( poFeature )
                        break;
                }
                else if( m_poLyrTable->HasGotError() )
                {
                    m_bEOF = TRUE;
                    return NULL;
                }
            }
        }
        else if( m_poIterator != NULL )
        {
            while( TRUE )
            {
                int iRow = m_poIterator->GetNextRowSortedByFID();
                if( iRow < 0 )
                    return NULL;
                if( m_poLyrTable->SelectRow(iRow) )
                {
                    poFeature = GetCurrentFeature();
                    if( poFeature )
                        break;
                }
                else if( m_poLyrTable->HasGotError() )
                {
                    m_bEOF = TRUE;
                    return NULL;
                }
            }
        }
        else
        {
            while( TRUE )
            {
                if( m_iCurFeat == m_poLyrTable->GetTotalRecordCount() )
                {
                    return NULL;
                }
                if( m_poLyrTable->SelectRow(m_iCurFeat++) )
                {
                    poFeature = GetCurrentFeature();
                    if( m_eSpatialIndexState == SPI_IN_BUILDING &&
                        m_iCurFeat == m_poLyrTable->GetTotalRecordCount() )
                    {
                        CPLDebug("OpenFileGDB", "SPI_COMPLETED");
                        m_eSpatialIndexState = SPI_COMPLETED;
                    }
                    if( poFeature )
                        break;
                }
                else if( m_poLyrTable->HasGotError() )
                {
                    m_bEOF = TRUE;
                    return NULL;
                }
            }
        }

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

        delete poFeature;
    }
}

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

OGRFeature* OGROpenFileGDBLayer::GetFeature( long nFeatureId )
{
    if( !BuildLayerDefinition() )
        return NULL;

    if( nFeatureId < 1 || nFeatureId > m_poLyrTable->GetTotalRecordCount() )
        return NULL;
    if( !m_poLyrTable->SelectRow((int)nFeatureId - 1) )
        return NULL;

    /* Temporarily disable spatial filter */
    OGRGeometry* poOldSpatialFilter = m_poFilterGeom;
    m_poFilterGeom = NULL;
    /* and also spatial index state to avoid features to be inserted */
    /* multiple times in spatial index */
    SPIState eOldState = m_eSpatialIndexState;
    m_eSpatialIndexState = SPI_INVALID;

    OGRFeature* poFeature = GetCurrentFeature();

    /* Set it back */
    m_poFilterGeom = poOldSpatialFilter;
    m_eSpatialIndexState = eOldState;

    return poFeature;
}

/***********************************************************************/
/*                         SetNextByIndex()                            */
/***********************************************************************/

OGRErr OGROpenFileGDBLayer::SetNextByIndex( long nIndex )
{
    if( m_poIterator != NULL )
        return OGRLayer::SetNextByIndex(nIndex);

    if( !BuildLayerDefinition() )
        return OGRERR_FAILURE;

    if( m_eSpatialIndexState == SPI_IN_BUILDING )
        m_eSpatialIndexState = SPI_INVALID;

    if( m_nFilteredFeatureCount >= 0 )
    {
        if( nIndex < 0 || nIndex >= m_nFilteredFeatureCount )
            return OGRERR_FAILURE;
        m_iCurFeat = nIndex;
        return OGRERR_NONE;
    }
    else if( m_poLyrTable->GetValidRecordCount() == m_poLyrTable->GetTotalRecordCount() )
    {
        if( nIndex < 0 || nIndex >= m_poLyrTable->GetValidRecordCount() )
            return OGRERR_FAILURE;
        m_iCurFeat = nIndex;
        return OGRERR_NONE;
    }
    else
        return OGRLayer::SetNextByIndex(nIndex);
}

/***********************************************************************/
/*                           GetExtent()                               */
/***********************************************************************/

OGRErr OGROpenFileGDBLayer::GetExtent( OGREnvelope *psExtent, int bForce )
{
    (void)bForce;

    if( !BuildLayerDefinition() )
        return OGRERR_FAILURE;

    if( m_iGeomFieldIdx >= 0 && m_poLyrTable->GetValidRecordCount() > 0 )
    {
        FileGDBGeomField* poGDBGeomField =
            (FileGDBGeomField* )m_poLyrTable->GetField(m_iGeomFieldIdx);
        psExtent->MinX = poGDBGeomField->GetXMin();
        psExtent->MinY = poGDBGeomField->GetYMin();
        psExtent->MaxX = poGDBGeomField->GetXMax();
        psExtent->MaxY = poGDBGeomField->GetYMax();
        return OGRERR_NONE;
    }
    else
        return OGRERR_FAILURE;
}

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

int OGROpenFileGDBLayer::GetFeatureCount( int bForce )
{
    if( !BuildLayerDefinition() )
        return 0;

    /* No filter */
    if( (m_poFilterGeom == NULL || m_iGeomFieldIdx < 0 ) &&
        m_poAttrQuery == NULL )
    {
        return m_poLyrTable->GetValidRecordCount();
    }
    else if( m_nFilteredFeatureCount >= 0 && m_poAttrQuery == NULL )
    {
        return m_nFilteredFeatureCount;
    }

    /* Only geometry filter ? */
    if( m_poAttrQuery == NULL && m_bFilterIsEnvelope )
    {
        int nCount = 0;
        if( m_eSpatialIndexState == SPI_IN_BUILDING && m_iCurFeat != 0 )
            m_eSpatialIndexState = SPI_INVALID;
        
        int nFilteredFeatureCountAlloc = 0;
        if( m_eSpatialIndexState == SPI_IN_BUILDING )
        {
            CPLFree(m_pahFilteredFeatures);
            m_pahFilteredFeatures = NULL;
            m_nFilteredFeatureCount = 0;
        }

        for(int i=0;i<m_poLyrTable->GetTotalRecordCount();i++)
        {
            if( !m_poLyrTable->SelectRow(i) )
            {
                if( m_poLyrTable->HasGotError() )
                    break;
                else
                    continue;
            }

            const OGRField* psField = m_poLyrTable->GetFieldValue(m_iGeomFieldIdx);
            if( psField != NULL )
            {
                if( m_eSpatialIndexState == SPI_IN_BUILDING )
                {
                    OGREnvelope sFeatureEnvelope;
                    if( m_poLyrTable->GetFeatureExtent(psField,
                                                       &sFeatureEnvelope) )
                    {
                        CPLRectObj sBounds;
                        sBounds.minx = sFeatureEnvelope.MinX;
                        sBounds.miny = sFeatureEnvelope.MinY;
                        sBounds.maxx = sFeatureEnvelope.MaxX;
                        sBounds.maxy = sFeatureEnvelope.MaxY;
                        CPLQuadTreeInsertWithBounds(m_pQuadTree,
                                                    (void*)(size_t)i,
                                                    &sBounds);
                    }
                }

                if( m_poLyrTable->DoesGeometryIntersectsFilterEnvelope(psField) )
                {
                    OGRGeometry* poGeom = m_poGeomConverter->GetAsGeometry(psField);
                    if( poGeom != NULL && FilterGeometry( poGeom ))
                    {
                        if( m_eSpatialIndexState == SPI_IN_BUILDING )
                        {
                            if( nCount == nFilteredFeatureCountAlloc )
                            {
                                nFilteredFeatureCountAlloc = 4 * nFilteredFeatureCountAlloc / 3 + 1024;
                                m_pahFilteredFeatures = (void**)CPLRealloc(
                                    m_pahFilteredFeatures, sizeof(void*) * nFilteredFeatureCountAlloc);
                            }
                            m_pahFilteredFeatures[nCount] = (void*)(size_t)i;
                        }
                        nCount ++;
                    }
                    delete poGeom;
                }
            }
        }
        if( m_eSpatialIndexState == SPI_IN_BUILDING )
        {
            m_nFilteredFeatureCount = nCount;
            m_eSpatialIndexState = SPI_COMPLETED;
        }

        return nCount;
    }
    /* Only simple attribute filter ? */
    else if( m_poFilterGeom == NULL &&
             m_poIterator != NULL && m_bIteratorSufficientToEvaluateFilter )
    {
        return m_poIterator->GetRowCount();
    }

    return OGRLayer::GetFeatureCount(bForce);
}

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

int OGROpenFileGDBLayer::TestCapability( const char * pszCap )
{
    if( !BuildLayerDefinition() )
        return FALSE;

    if( EQUAL(pszCap,OLCFastFeatureCount) )
    {
        return( (m_poFilterGeom == NULL || m_iGeomFieldIdx < 0 ) &&
                m_poAttrQuery == NULL );
    }
    else if( EQUAL(pszCap,OLCFastSetNextByIndex) )
    {
        return ( m_poLyrTable->GetValidRecordCount() ==
                 m_poLyrTable->GetTotalRecordCount() &&
                 m_poIterator == NULL );
    }
    else if( EQUAL(pszCap,OLCRandomRead) )
    {
        return TRUE;
    }
    else if( EQUAL(pszCap,OLCFastGetExtent) )
    {
        return TRUE;
    }
    else if( EQUAL(pszCap,OLCIgnoreFields) )
    {
        return TRUE;
    }
    else if( EQUAL(pszCap,OLCStringsAsUTF8) )
    {
        return TRUE; /* ? */
    }

    return FALSE;
}

/***********************************************************************/
/*                         HasIndexForField()                          */
/***********************************************************************/

int OGROpenFileGDBLayer::HasIndexForField(const char* pszFieldName)
{
    if( !BuildLayerDefinition() )
        return FALSE;
    
    int nTableColIdx = m_poLyrTable->GetFieldIdx(pszFieldName);
    return ( nTableColIdx >= 0 &&
             m_poLyrTable->GetField(nTableColIdx)->HasIndex() );
}

/***********************************************************************/
/*                             BuildIndex()                            */
/***********************************************************************/

FileGDBIterator* OGROpenFileGDBLayer::BuildIndex(const char* pszFieldName,
                                                 int bAscending,
                                                 swq_op op,
                                                 swq_expr_node* poValue)
{
    if( !BuildLayerDefinition() )
        return NULL;

    int idx = GetLayerDefn()->GetFieldIndex(pszFieldName);
    if( idx < 0 )
        return NULL;
    OGRFieldDefn* poFieldDefn = GetLayerDefn()->GetFieldDefn(idx);

    int nTableColIdx = m_poLyrTable->GetFieldIdx(pszFieldName);
    if( nTableColIdx >= 0 && m_poLyrTable->GetField(nTableColIdx)->HasIndex() )
    {
        if( op == SWQ_UNKNOWN )
            return FileGDBIterator::BuildIsNotNull(m_poLyrTable, nTableColIdx, bAscending);
        else
        {
            OGRField sValue;
            if( FillTargetValueFromSrcExpr(poFieldDefn, &sValue, poValue) )
            {
                FileGDBSQLOp eOp;
                switch( op )
                {
                    case SWQ_LE: eOp = FGSO_LE; break;
                    case SWQ_LT: eOp = FGSO_LT; break;
                    case SWQ_EQ: eOp = FGSO_EQ; break;
                    case SWQ_GE: eOp = FGSO_GE; break;
                    case SWQ_GT: eOp = FGSO_GT; break;
                    default: return NULL;
                }

                return FileGDBIterator::Build(
                                m_poLyrTable, nTableColIdx, bAscending,
                                eOp, poFieldDefn->GetType(), &sValue);
            }
        }
    }
    return NULL;
}

/***********************************************************************/
/*                          GetMinMaxValue()                           */
/***********************************************************************/

const OGRField* OGROpenFileGDBLayer::GetMinMaxValue(OGRFieldDefn* poFieldDefn,
                                                    int bIsMin,
                                                    int& eOutType)
{
    eOutType = -1;
    if( !BuildLayerDefinition() )
        return NULL;

    int nTableColIdx = m_poLyrTable->GetFieldIdx(poFieldDefn->GetNameRef());
    if( nTableColIdx >= 0 && m_poLyrTable->GetField(nTableColIdx)->HasIndex() )
    {
        delete m_poIterMinMax;
        m_poIterMinMax = FileGDBIterator::BuildIsNotNull(
                                                m_poLyrTable, nTableColIdx, TRUE);
        if( m_poIterMinMax != NULL )
        {
            const OGRField* poRet = (bIsMin ) ?
                m_poIterMinMax->GetMinValue(eOutType) :
                m_poIterMinMax->GetMaxValue(eOutType);
            if( poRet == NULL )
                eOutType = poFieldDefn->GetType();
            return poRet;
        }
    }
    return NULL;
}

/***********************************************************************/
/*                        GetMinMaxSumCount()                          */
/***********************************************************************/

int OGROpenFileGDBLayer::GetMinMaxSumCount(OGRFieldDefn* poFieldDefn,
                                           double& dfMin, double& dfMax,
                                           double& dfSum, int& nCount)
{
    dfMin = 0.0;
    dfMax = 0.0;
    dfSum = 0.0;
    nCount = 0;
    if( !BuildLayerDefinition() )
        return FALSE;

    int nTableColIdx = m_poLyrTable->GetFieldIdx(poFieldDefn->GetNameRef());
    if( nTableColIdx >= 0 && m_poLyrTable->GetField(nTableColIdx)->HasIndex() )
    {
        FileGDBIterator* poIter = FileGDBIterator::BuildIsNotNull(
                                                m_poLyrTable, nTableColIdx, TRUE);
        if( poIter != NULL )
        {
            int nRet = poIter->GetMinMaxSumCount(dfMin, dfMax, dfSum, nCount);
            delete poIter;
            return nRet;
        }
    }
    return FALSE;
}
