/******************************************************************************
 * $Id: filegdbtable.cpp 29330 2015-06-14 12:11:11Z rouault $
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  Implements reading of FileGDB tables
 * 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 "filegdbtable_priv.h"
#include "cpl_string.h"
#include "cpl_time.h"
#include "ogrpgeogeometry.h" /* SHPT_ constants and OGRCreateFromMultiPatchPart() */

CPL_CVSID("$Id");

#define TEST_BIT(ar, bit)                       (ar[(bit) / 8] & (1 << ((bit) % 8)))
#define BIT_ARRAY_SIZE_IN_BYTES(bitsize)        (((bitsize)+7)/8)

#define UUID_SIZE_IN_BYTES              16

#define IS_VALID_LAYER_GEOM_TYPE(byVal)         ((byVal) <= FGTGT_POLYGON || (byVal) == FGTGT_MULTIPATCH)

/* Reserve one extra byte in case the last field is a string */
/*       or 2 for 2 ReadVarIntAndAddNoCheck() in a row */
/*       or 4 for SkipVarUInt() with nIter = 4 */
/*       or for 4 ReadVarUInt64NoCheck */
#define ZEROES_AFTER_END_OF_BUFFER      4

namespace OpenFileGDB
{

/************************************************************************/
/*                      FileGDBTablePrintError()                        */
/************************************************************************/

void FileGDBTablePrintError(const char* pszFile, int nLineNumber)
{
    CPLError(CE_Failure, CPLE_AppDefined, "Error occured in %s at line %d",
             pszFile, nLineNumber);
}

/************************************************************************/
/*                            FileGDBTable()                            */
/************************************************************************/

FileGDBTable::FileGDBTable()
{
    Init();
}

/************************************************************************/
/*                           ~FileGDBTable()                            */
/************************************************************************/

FileGDBTable::~FileGDBTable()
{
    Close();
}

/************************************************************************/
/*                                Init()                                */
/************************************************************************/

void FileGDBTable::Init()
{
    osFilename = "";
    fpTable = NULL;
    fpTableX = NULL;
    nFileSize = 0;
    memset(&sCurField, 0, sizeof(sCurField));
    bError = FALSE;
    nCurRow = -1;
    nLastCol = -1;
    pabyIterVals = NULL;
    iAccNullable = 0;
    nRowBlobLength = 0;
    /* eCurFieldType = OFTInteger; */
    eTableGeomType = FGTGT_NONE;
    nValidRecordCount = 0;
    nTotalRecordCount = 0;
    iGeomField = -1;
    nCountNullableFields = 0;
    nNullableFieldsSizeInBytes = 0;
    nBufferMaxSize = 0;
    pabyBuffer = NULL;
    nFilterXMin = 0;
    nFilterXMax = 0;
    nFilterYMin = 0;
    nFilterYMax = 0;
    osObjectIdColName = "";
    nChSaved = -1;
    pabyTablXBlockMap = NULL;
    nCountBlocksBeforeIBlockIdx = 0;
    nCountBlocksBeforeIBlockValue = 0;
    bHasReadGDBIndexes = FALSE;
    nOffsetFieldDesc = 0;
    nFieldDescLength = 0;
    nTablxOffsetSize = 0;
    anFeatureOffsets.resize(0);
    nOffsetHeaderEnd = 0;
    bHasDeletedFeaturesListed = FALSE;
    bIsDeleted = FALSE;
}

/************************************************************************/
/*                                Close()                               */
/************************************************************************/

void FileGDBTable::Close()
{
    if( fpTable )
        VSIFCloseL(fpTable);
    fpTable = NULL;

    if( fpTableX )
        VSIFCloseL(fpTableX);
    fpTableX = NULL;

    CPLFree(pabyBuffer);
    pabyBuffer = NULL;

    for(size_t i=0;i<apoFields.size();i++)
        delete apoFields[i];
    apoFields.resize(0);

    CPLFree(pabyTablXBlockMap);
    pabyTablXBlockMap = NULL;

    for(size_t i=0;i<apoIndexes.size();i++)
        delete apoIndexes[i];
    apoIndexes.resize(0);

    Init();
}

/************************************************************************/
/*                              GetFieldIdx()                           */
/************************************************************************/

int FileGDBTable::GetFieldIdx(const std::string& osName) const
{
    for(size_t i=0;i<apoFields.size();i++)
    {
        if( apoFields[i]->GetName() == osName )
            return (int)i;
    }
    return -1;
}

/************************************************************************/
/*                          ReadVarUInt()                               */
/************************************************************************/

template < class OutType, class ControleType >
static int ReadVarUInt(GByte*& pabyIter, GByte* pabyEnd, OutType& nOutVal)
{
    const int errorRetValue = FALSE;
    if( !(ControleType::check_bounds) )
    {
        /* nothing */
    }
    else if( ControleType::verbose_error )
    {
        returnErrorIf(pabyIter >= pabyEnd);
    }
    else
    {
        if( pabyIter >= pabyEnd )
            return FALSE;
    }
    OutType b = *pabyIter;
    if( (b & 0x80) == 0 )
    {
        pabyIter ++;
        nOutVal = b;
        return TRUE;
    }
    GByte* pabyLocalIter = pabyIter + 1;
    int nShift = 7;
    OutType nVal = ( b & 0x7F );
    while(TRUE)
    {
        if( !(ControleType::check_bounds) )
        {
            /* nothing */
        }
        else if( ControleType::verbose_error )
        {
            returnErrorIf(pabyLocalIter >= pabyEnd);
        }
        else
        {
            if( pabyLocalIter >= pabyEnd )
                return FALSE;
        }
        b = *pabyLocalIter;
        pabyLocalIter ++;
        nVal |= ( b & 0x7F ) << nShift;
        if( (b & 0x80) == 0 )
        {
            pabyIter = pabyLocalIter;
            nOutVal = nVal;
            return TRUE;
        }
        nShift += 7;
    }
}

struct ControleTypeVerboseErrorTrue
{
    static const bool check_bounds = true;
    static const bool verbose_error = true;
};

struct ControleTypeVerboseErrorFalse
{
    static const bool check_bounds = true;
    static const bool verbose_error = false;
};

struct ControleTypeNone
{
    static const bool check_bounds = false;
    static const bool verbose_error = false;
};

static int ReadVarUInt32(GByte*& pabyIter, GByte* pabyEnd, GUInt32& nOutVal)
{
    return ReadVarUInt<GUInt32, ControleTypeVerboseErrorTrue>(pabyIter, pabyEnd, nOutVal);
}

static void ReadVarUInt32NoCheck(GByte*& pabyIter, GUInt32& nOutVal)
{
    GByte* pabyEnd = NULL;
    ReadVarUInt<GUInt32, ControleTypeNone>(pabyIter, pabyEnd, nOutVal);
}

static int ReadVarUInt32Silent(GByte*& pabyIter, GByte* pabyEnd, GUInt32& nOutVal)
{
    return ReadVarUInt<GUInt32, ControleTypeVerboseErrorFalse>(pabyIter, pabyEnd, nOutVal);
}

static void ReadVarUInt64NoCheck(GByte*& pabyIter, GUIntBig& nOutVal)
{
    GByte* pabyEnd = NULL;
    ReadVarUInt<GUIntBig, ControleTypeNone>(pabyIter, pabyEnd, nOutVal);
}

/************************************************************************/
/*                      IsLikelyFeatureAtOffset()                       */
/************************************************************************/

int FileGDBTable::IsLikelyFeatureAtOffset(vsi_l_offset nOffset,
                                          GUInt32* pnSize,
                                          int* pbDeletedRecord)
{
    VSIFSeekL(fpTable, nOffset, SEEK_SET);
    GByte abyBuffer[4];
    if( VSIFReadL(abyBuffer, 4, 1, fpTable) != 1 )
        return FALSE;

    nRowBlobLength = GetUInt32(abyBuffer, 0);
    if( nRowBlobLength < (GUInt32)nNullableFieldsSizeInBytes ||
        nRowBlobLength > nFileSize - nOffset ||
        nRowBlobLength > INT_MAX - ZEROES_AFTER_END_OF_BUFFER ||
        nRowBlobLength > 10 * (nFileSize / nValidRecordCount) )
    {
        /* Is it a deleted record ? */
        if( (int)nRowBlobLength < 0 && nRowBlobLength != 0x80000000U )
        {
            nRowBlobLength = (GUInt32) (-(int)nRowBlobLength);
            if( nRowBlobLength < (GUInt32)nNullableFieldsSizeInBytes ||
                nRowBlobLength > nFileSize - nOffset ||
                nRowBlobLength > INT_MAX - ZEROES_AFTER_END_OF_BUFFER ||
                nRowBlobLength > 10 * (nFileSize / nValidRecordCount) )
                return FALSE;
            else
                *pbDeletedRecord = TRUE;
        }
        else
            return FALSE;
    }
    else
        *pbDeletedRecord = FALSE;

    if( nRowBlobLength > nBufferMaxSize )
    {
        GByte* pabyNewBuffer = (GByte*) VSIRealloc( pabyBuffer,
                                nRowBlobLength + ZEROES_AFTER_END_OF_BUFFER );
        if( pabyNewBuffer == NULL )
            return FALSE;

        pabyBuffer = pabyNewBuffer;
        nBufferMaxSize = nRowBlobLength;
    }
    if( pabyBuffer == NULL ) return FALSE; /* to please Coverity. Not needed */
    if( nCountNullableFields > 0 )
    {
        if( VSIFReadL(pabyBuffer, nNullableFieldsSizeInBytes, 1, fpTable) != 1 )
            return FALSE;
    }
    size_t i;
    iAccNullable = 0;
    int bExactSizeKnown = TRUE;
    GUInt32 nRequiredLength = nNullableFieldsSizeInBytes;
    for(i=0;i<apoFields.size();i++)
    {
        if( apoFields[i]->bNullable )
        {
            int bIsNull = TEST_BIT(pabyBuffer, iAccNullable);
            iAccNullable ++;
            if( bIsNull )
                continue;
        }

        switch( apoFields[i]->eType )
        {
            case FGFT_STRING:
            case FGFT_XML:
            case FGFT_GEOMETRY:
            case FGFT_BINARY:
            {
                nRequiredLength += 1; /* varuint32 so at least one byte */
                bExactSizeKnown = FALSE;
                break;
            }

            /* Only 4 bytes ? */
            case FGFT_RASTER: nRequiredLength += sizeof(GInt32); break;

            case FGFT_INT16: nRequiredLength += sizeof(GInt16); break;
            case FGFT_INT32: nRequiredLength += sizeof(GInt32); break;
            case FGFT_FLOAT32: nRequiredLength += sizeof(float); break;
            case FGFT_FLOAT64: nRequiredLength += sizeof(double); break;
            case FGFT_DATETIME: nRequiredLength += sizeof(double); break;
            case FGFT_UUID_1:
            case FGFT_UUID_2: nRequiredLength += UUID_SIZE_IN_BYTES; break;

            default:
                CPLAssert(FALSE);
                break;
        }
    }
    if( !bExactSizeKnown )
    {
        if( nRowBlobLength < nRequiredLength )
            return FALSE;
        if( VSIFReadL(pabyBuffer + nNullableFieldsSizeInBytes,
                nRowBlobLength - nNullableFieldsSizeInBytes, 1, fpTable) != 1 )
            return FALSE;

        iAccNullable = 0;
        nRequiredLength = nNullableFieldsSizeInBytes;
        for(i=0;i<apoFields.size();i++)
        {
            if( apoFields[i]->bNullable )
            {
                int bIsNull = TEST_BIT(pabyBuffer, iAccNullable);
                iAccNullable ++;
                if( bIsNull )
                    continue;
            }

            switch( apoFields[i]->eType )
            {
                case FGFT_STRING:
                case FGFT_XML:
                {
                    GByte* pabyIter = pabyBuffer + nRequiredLength;
                    GUInt32 nLength;
                    if( !ReadVarUInt32Silent(pabyIter, pabyBuffer + nRowBlobLength, nLength) ||
                        pabyIter - (pabyBuffer + nRequiredLength) > 5 )
                        return FALSE;
                    nRequiredLength = pabyIter - pabyBuffer;
                    if( nLength > nRowBlobLength - nRequiredLength )
                        return FALSE;
                    for( GUInt32 j=0;j<nLength;j++ )
                    {
                        if( pabyIter[j] == 0 )
                            return FALSE;
                    }
                    if( !CPLIsUTF8((const char*)pabyIter, nLength) )
                        return FALSE;
                    nRequiredLength += nLength;
                    break;
                }

                case FGFT_GEOMETRY:
                case FGFT_BINARY:
                {
                    GByte* pabyIter = pabyBuffer + nRequiredLength;
                    GUInt32 nLength;
                    if( !ReadVarUInt32Silent(pabyIter, pabyBuffer + nRowBlobLength, nLength) ||
                        pabyIter - (pabyBuffer + nRequiredLength) > 5 )
                        return FALSE;
                    nRequiredLength = pabyIter - pabyBuffer;
                    if( nLength > nRowBlobLength - nRequiredLength )
                        return FALSE;
                    nRequiredLength += nLength;
                    break;
                }

                /* Only 4 bytes ? */
                case FGFT_RASTER: nRequiredLength += sizeof(GInt32); break;

                case FGFT_INT16: nRequiredLength += sizeof(GInt16); break;
                case FGFT_INT32: nRequiredLength += sizeof(GInt32); break;
                case FGFT_FLOAT32: nRequiredLength += sizeof(float); break;
                case FGFT_FLOAT64: nRequiredLength += sizeof(double); break;
                case FGFT_DATETIME: nRequiredLength += sizeof(double); break;
                case FGFT_UUID_1:
                case FGFT_UUID_2: nRequiredLength += UUID_SIZE_IN_BYTES; break;

                default:
                    CPLAssert(FALSE);
                    break;
            }
            if( nRequiredLength > nRowBlobLength )
                return FALSE;
        }
    }

    *pnSize = 4 + nRequiredLength;
    return nRequiredLength == nRowBlobLength;
}

/************************************************************************/
/*                      GuessFeatureLocations()                         */
/************************************************************************/

#define MARK_DELETED(x)  ((x) | (((GIntBig)1) << 63))
#define IS_DELETED(x)    (((x) & (((GIntBig)1) << 63)) != 0)
#define GET_OFFSET(x)    ((x) & ~(((GIntBig)1) << 63))

int FileGDBTable::GuessFeatureLocations()
{
    VSIFSeekL(fpTable, 0, SEEK_END);
    nFileSize = VSIFTellL(fpTable);
    
    int bReportDeletedFeatures =
        CSLTestBoolean(CPLGetConfigOption("OPENFILEGDB_REPORT_DELETED_FEATURES", "NO"));

    vsi_l_offset nOffset = 40 + nFieldDescLength;
    
    if( nOffsetFieldDesc != 40 )
    {
        /* Check if there is a deleted field description at offset 40 */
        GByte abyBuffer[14];
        VSIFSeekL(fpTable, 40, SEEK_SET);
        if( VSIFReadL(abyBuffer, 14, 1, fpTable) != 1 )
            return FALSE;
        int nSize = GetInt32(abyBuffer, 0);
        int nVersion = GetInt32(abyBuffer + 4, 0);
        if( nSize < 0 && -nSize < 1024 * 1024 &&
            (nVersion == 3 || nVersion == 4) &&
            IS_VALID_LAYER_GEOM_TYPE(abyBuffer[8]) &&
            abyBuffer[9] == 3 && abyBuffer[10] == 0 && abyBuffer[11] == 0 )
        {
            nOffset = 40 + (-nSize);
        }
        else
        {
            nOffset = 40;
        }
    }

    int nInvalidRecords = 0;
    while(nOffset < nFileSize)
    {
        GUInt32 nSize;
        int bDeletedRecord;
        if( !IsLikelyFeatureAtOffset(nOffset, &nSize, &bDeletedRecord) )
        {
            nOffset ++;
        }
        else
        {
            /*CPLDebug("OpenFileGDB", "Feature found at offset %d (size = %d)",
                     nOffset, nSize);*/
            if( bDeletedRecord )
            {
                if( bReportDeletedFeatures )
                {
                    bHasDeletedFeaturesListed = TRUE;
                    anFeatureOffsets.push_back(MARK_DELETED(nOffset));
                }
                else
                {
                    nInvalidRecords ++;
                    anFeatureOffsets.push_back(0);
                }
            }
            else
                anFeatureOffsets.push_back(nOffset);
            nOffset += nSize;
        }
    }
    nTotalRecordCount = (int) anFeatureOffsets.size();
    if( nTotalRecordCount - nInvalidRecords > nValidRecordCount )
    {
        if( !bHasDeletedFeaturesListed )
        {
            CPLError(CE_Warning, CPLE_AppDefined,
                    "More features found (%d) than declared number of valid features (%d). "
                    "So deleted features will likely be reported.",
                    nTotalRecordCount - nInvalidRecords, nValidRecordCount);
        }
        nValidRecordCount = nTotalRecordCount - nInvalidRecords;
    }

    return nTotalRecordCount > 0;
}

/************************************************************************/
/*                            ReadTableXHeader()                        */
/************************************************************************/

int FileGDBTable::ReadTableXHeader()
{
    const int errorRetValue = FALSE;
    GByte abyHeader[16];

    // Read .gdbtablx file header
    returnErrorIf(VSIFReadL( abyHeader, 16, 1, fpTableX ) != 1 );
    GUInt32 n1024Blocks = GetUInt32(abyHeader + 4, 0);

    nTotalRecordCount = GetInt32(abyHeader + 8, 0);
    if( n1024Blocks == 0 )
        returnErrorIf(nTotalRecordCount != 0 );
    else
        returnErrorIf(nTotalRecordCount < 0 );

    nTablxOffsetSize = GetUInt32(abyHeader + 12, 0);
    returnErrorIf(nTablxOffsetSize < 4 || nTablxOffsetSize > 6);

    if( n1024Blocks != 0 )
    {
        GByte abyTrailer[16];

        VSIFSeekL( fpTableX, nTablxOffsetSize * 1024 * (vsi_l_offset)n1024Blocks + 16, SEEK_SET );
        returnErrorIf(VSIFReadL( abyTrailer, 16, 1, fpTableX ) != 1 );

        GUInt32 nMagic = GetUInt32(abyTrailer, 0);

        GUInt32 nBitsForBlockMap = GetUInt32(abyTrailer + 4, 0);
        returnErrorIf(nBitsForBlockMap > INT_MAX / 1024);

        GUInt32 n1024BlocksBis = GetUInt32(abyTrailer + 8, 0);
        returnErrorIf(n1024BlocksBis != n1024Blocks );

        /* GUInt32 nMagic2 = GetUInt32(abyTrailer + 12, 0); */

        if( nMagic == 0 )
        {
            returnErrorIf(nBitsForBlockMap != n1024Blocks );
            /* returnErrorIf(nMagic2 != 0 ); */
        }
        else
        {
            returnErrorIf((GUInt32)nTotalRecordCount > nBitsForBlockMap * 1024 );
#ifdef DEBUG_VERBOSE
            CPLDebug("OpenFileGDB", "%s .gdbtablx has block map array",
                     osFilename.c_str());
#endif

            // Allocate a bit mask array for blocks of 1024 features.
            int nSizeInBytes = BIT_ARRAY_SIZE_IN_BYTES(nBitsForBlockMap);
            pabyTablXBlockMap = (GByte*) VSIMalloc( nSizeInBytes );
            returnErrorIf(pabyTablXBlockMap == NULL );
            returnErrorIf(VSIFReadL( pabyTablXBlockMap, nSizeInBytes, 1, fpTableX ) != 1 );
            /* returnErrorIf(nMagic2 == 0 ); */

            // Check that the map is consistent with n1024Blocks
            GUInt32 nCountBlocks = 0;
            for(GUInt32 i=0;i<nBitsForBlockMap;i++)
                nCountBlocks += TEST_BIT(pabyTablXBlockMap, i) != 0;
            returnErrorIf(nCountBlocks != n1024Blocks );
        }
    }
    return TRUE;
}

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

int FileGDBTable::Open(const char* pszFilename,
                       const char* pszLayerName)
{
    const int errorRetValue = FALSE;
    CPLAssert(fpTable == NULL);

    osFilename = pszFilename;
    CPLString osFilenameWithLayerName(osFilename);
    if( pszLayerName )
        osFilenameWithLayerName += CPLSPrintf(" (layer %s)", pszLayerName);

    fpTable = VSIFOpenL( pszFilename, "rb" );
    if( fpTable == NULL )
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                 "Cannot open %s: %s", osFilenameWithLayerName.c_str(),
                 VSIStrerror(errno));
        return FALSE;
    }

    // Read .gdtable file header
    GByte abyHeader[40];
    returnErrorIf(VSIFReadL( abyHeader, 40, 1, fpTable ) != 1 );
    nValidRecordCount = GetInt32(abyHeader + 4, 0);
    returnErrorIf(nValidRecordCount < 0 );

    CPLString osTableXName;
    if( nValidRecordCount > 0 &&
        !CSLTestBoolean(CPLGetConfigOption("OPENFILEGDB_IGNORE_GDBTABLX", "FALSE")) )
    {
        osTableXName = CPLFormFilename(CPLGetPath(pszFilename),
                                        CPLGetBasename(pszFilename), "gdbtablx");
        fpTableX = VSIFOpenL( osTableXName, "rb" );
        if( fpTableX == NULL )
        {
            const char* pszIgnoreGDBTablXAbsence =
                CPLGetConfigOption("OPENFILEGDB_IGNORE_GDBTABLX_ABSENCE", NULL);
            if( pszIgnoreGDBTablXAbsence == NULL )
            {
                CPLError(CE_Warning, CPLE_AppDefined, "%s could not be found. "
                        "Trying to guess feature locations, but this might fail or "
                        "return incorrect results", osTableXName.c_str());
            }
            else if( !CSLTestBoolean(pszIgnoreGDBTablXAbsence) )
            {
                returnErrorIf(fpTableX == NULL );
            }
        }
        else if( !ReadTableXHeader() )
            return FALSE;
    }

    if( fpTableX != NULL )
    {
        if(nValidRecordCount > nTotalRecordCount )
        {
            if( CSLTestBoolean(CPLGetConfigOption("OPENFILEGDB_USE_GDBTABLE_RECORD_COUNT", "FALSE")) )
            {
                /* Potentially unsafe. See #5842 */
                CPLDebug("OpenFileGDB", "%s: nTotalRecordCount (was %d) forced to nValidRecordCount=%d",
                        osFilenameWithLayerName.c_str(),
                        nTotalRecordCount, nValidRecordCount);
                nTotalRecordCount = nValidRecordCount;
            }
            else
            {
                /* By default err on the safe side */
                CPLError(CE_Warning, CPLE_AppDefined,
                        "File %s declares %d valid records, but %s declares "
                        "only %d total records. Using that later value for safety "
                        "(this possibly ignoring features). "
                        "You can also try setting OPENFILEGDB_IGNORE_GDBTABLX=YES to "
                        "completely ignore the .gdbtablx file (but possibly retrieving "
                        "deleted features), or set OPENFILEGDB_USE_GDBTABLE_RECORD_COUNT=YES "
                        "(but that setting can potentially cause crashes)",
                        osFilenameWithLayerName.c_str(), nValidRecordCount,
                        osTableXName.c_str(), nTotalRecordCount);
                nValidRecordCount = nTotalRecordCount;
            }
        }

#ifdef DEBUG_VERBOSE
        else if( nTotalRecordCount != nValidRecordCount )
        {
            CPLDebug("OpenFileGDB", "%s: nTotalRecordCount=%d nValidRecordCount=%d",
                    pszFilename,
                    nTotalRecordCount, nValidRecordCount);
        }
#endif
    }

    nOffsetFieldDesc = GetUInt32(abyHeader + 32, 0);

#ifdef DEBUG_VERBOSE
    if( nOffsetFieldDesc != 40 )
    {
        CPLDebug("OpenFileGDB", "%s: nOffsetFieldDesc=%d",
                 pszFilename, nOffsetFieldDesc);
    }
#endif

    // Skip to field description section
    VSIFSeekL( fpTable, nOffsetFieldDesc, SEEK_SET );
    returnErrorIf(VSIFReadL( abyHeader, 14, 1, fpTable ) != 1 );
    nFieldDescLength = GetUInt32(abyHeader, 0);

    nOffsetHeaderEnd = nOffsetFieldDesc + nFieldDescLength;

    returnErrorIf(nFieldDescLength > 10 * 1024 * 1024 || nFieldDescLength < 10 );
    GByte byTableGeomType = abyHeader[8];
    if( IS_VALID_LAYER_GEOM_TYPE(byTableGeomType) )
        eTableGeomType = (FileGDBTableGeometryType) byTableGeomType;
    else
        CPLDebug("OpenFileGDB", "Unknown table geometry type: %d", byTableGeomType);
    GUInt16 iField, nFields;
    nFields = GetUInt16(abyHeader + 12, 0);

    /* No interest in guessing a trivial file */
    returnErrorIf( fpTableX == NULL && nFields == 0) ;

    GUInt32 nRemaining = nFieldDescLength - 10;
    nBufferMaxSize = nRemaining;
    pabyBuffer = (GByte*)VSIMalloc(nBufferMaxSize + ZEROES_AFTER_END_OF_BUFFER);
    returnErrorIf(pabyBuffer == NULL );
    returnErrorIf(VSIFReadL(pabyBuffer, nRemaining, 1, fpTable) != 1 );

    GByte* pabyIter = pabyBuffer;
    for(iField = 0; iField < nFields; iField ++)
    {
        returnErrorIf(nRemaining < 1 );
        GByte nCarCount = pabyIter[0];

        pabyIter ++;
        nRemaining --;
        returnErrorIf(nRemaining < (GUInt32)(2 * nCarCount + 1) );
        std::string osName;
        for(int j=0;j<nCarCount;j++) //FIXME? UTF16
            osName += pabyIter[2 * j];
        pabyIter += 2 * nCarCount;
        nRemaining -= 2 * nCarCount;

        returnErrorIf(nRemaining < 1 );
        nCarCount = pabyIter[0];
        pabyIter ++;
        nRemaining --;
        returnErrorIf(nRemaining < (GUInt32)(2 * nCarCount + 1) );
        std::string osAlias;
        for(int j=0;j<nCarCount;j++) //FIXME? UTF16
            osAlias += pabyIter[2 * j];
        pabyIter += 2 * nCarCount;
        nRemaining -= 2 * nCarCount;

        returnErrorIf(nRemaining < 1 );
        GByte byFieldType = pabyIter[0];
        pabyIter ++;
        nRemaining --;

        if( byFieldType > FGFT_XML)
        {
            CPLDebug("OpenFileGDB", "Unhandled field type : %d", byFieldType);
            returnError();
        }

        FileGDBFieldType eType = (FileGDBFieldType) byFieldType;
        if( eType != FGFT_GEOMETRY && eType != FGFT_RASTER )
        {
            GByte flags = 0;
            int nMaxWidth = 0;
            GUInt32 defaultValueLength = 0;

            switch( eType )
            {
                case FGFT_STRING:
                {
                    returnErrorIf(nRemaining < 6 );
                    nMaxWidth = GetInt32(pabyIter, 0);
                    returnErrorIf(nMaxWidth < 0);
                    flags = pabyIter[4];
                    pabyIter += 5;
                    nRemaining -= 5;
                    GByte* pabyIterBefore = pabyIter;
                    returnErrorIf(!ReadVarUInt32(pabyIter, pabyIter + nRemaining, defaultValueLength));
                    nRemaining -= (pabyIter - pabyIterBefore);
                    break;
                }

                case FGFT_OBJECTID:
                case FGFT_BINARY:
                case FGFT_UUID_1:
                case FGFT_UUID_2:
                case FGFT_XML:
                    returnErrorIf(nRemaining < 2 );
                    flags = pabyIter[1];
                    pabyIter += 2;
                    nRemaining -= 2;
                    break;

                default:
                    returnErrorIf(nRemaining < 3 );
                    flags = pabyIter[1];
                    defaultValueLength = pabyIter[2];
                    pabyIter += 3;
                    nRemaining -= 3;
                    break;
            }

            OGRField sDefault;
            sDefault.Set.nMarker1 = OGRUnsetMarker;
            sDefault.Set.nMarker2 = OGRUnsetMarker;
            if( (flags & 4) != 0 )
            {
                /* Default value */
                /* Found on PreNIS.gdb/a0000000d.gdbtable */
                returnErrorIf(nRemaining < defaultValueLength );
                if( defaultValueLength )
                {
                    if( eType == FGFT_STRING )
                    {
                        sDefault.String = (char*)CPLMalloc(defaultValueLength+1);
                        memcpy(sDefault.String, pabyIter, defaultValueLength);
                        sDefault.String[defaultValueLength] = 0;
                    }
                    else if( eType == FGFT_INT16 && defaultValueLength == 2 )
                    {
                        sDefault.Integer = GetInt16(pabyIter, 0);
                        sDefault.Set.nMarker2 = 0;
                    }
                    else if( eType == FGFT_INT32 && defaultValueLength == 4 )
                    {
                        sDefault.Integer = GetInt32(pabyIter, 0);
                        sDefault.Set.nMarker2 = 0;
                    }
                    else if( eType == FGFT_FLOAT32 && defaultValueLength == 4 )
                    {
                        sDefault.Real = GetFloat32(pabyIter, 0);
                    }
                    else if( eType == FGFT_FLOAT64 && defaultValueLength == 8 )
                    {
                        sDefault.Real = GetFloat64(pabyIter, 0);
                    }
                    else if( eType == FGFT_DATETIME && defaultValueLength == 8 )
                    {
                        double dfVal = GetFloat64(pabyIter, 0);
                        FileGDBDoubleDateToOGRDate(dfVal, &sDefault);
                    }
                }

                pabyIter += defaultValueLength;
                nRemaining -= defaultValueLength;
            }

            if( eType == FGFT_OBJECTID )
            {
                returnErrorIf(osObjectIdColName.size() > 0 );
                osObjectIdColName = osName;
                continue;
            }

            FileGDBField* poField = new FileGDBField(this);
            poField->osName = osName;
            poField->osAlias = osAlias;
            poField->eType = eType;
            poField->bNullable = (flags & 1);
            poField->nMaxWidth = nMaxWidth;
            poField->sDefault = sDefault;
            apoFields.push_back(poField);
        }
        else
        {

            FileGDBRasterField* poRasterField = NULL;
            FileGDBGeomField* poField;
            if( eType == FGFT_GEOMETRY )
            {
                returnErrorIf(iGeomField >= 0 );
                poField = new FileGDBGeomField(this);
            }
            else
            {
                poRasterField = new FileGDBRasterField(this);
                poField = poRasterField;
            }

            poField->osName = osName;
            poField->osAlias = osAlias;
            poField->eType = eType;
            if( eType == FGFT_GEOMETRY )
                iGeomField = (int)apoFields.size();
            apoFields.push_back(poField);

            returnErrorIf(nRemaining < 2 );
            GByte flags = pabyIter[1];
            poField->bNullable = (flags & 1);
            pabyIter += 2;
            nRemaining -= 2;

            if( eType == FGFT_RASTER )
            {
                returnErrorIf(nRemaining < 1 );
                nCarCount = pabyIter[0];
                pabyIter ++;
                nRemaining --;
                returnErrorIf(nRemaining < (GUInt32)(2 * nCarCount + 1) );
                std::string osRasterColumn;
                for(int j=0;j<nCarCount;j++) //FIXME? UTF16
                    osRasterColumn += pabyIter[2 * j];
                pabyIter += 2 * nCarCount;
                nRemaining -= 2 * nCarCount;
                poRasterField->osRasterColumnName = osRasterColumn;
            }

            returnErrorIf(nRemaining < 2 );
            GUInt16 nLengthWKT = GetUInt16(pabyIter, 0);
            pabyIter += sizeof(nLengthWKT);
            nRemaining -= sizeof(nLengthWKT);

            returnErrorIf(nRemaining < (GUInt32)(1 + nLengthWKT) );
            for(int j=0;j<nLengthWKT/2;j++) //FIXME? UTF16
                poField->osWKT += pabyIter[2 * j];
            pabyIter += nLengthWKT;
            nRemaining -= nLengthWKT;

            GByte abyGeomFlags = pabyIter[0];
            pabyIter ++;
            nRemaining --;
            poField->bHasM = (abyGeomFlags & 2) != 0;
            poField->bHasZ = (abyGeomFlags & 4) != 0;

            if( eType == FGFT_GEOMETRY || abyGeomFlags > 0 )
            {
                returnErrorIf(
                        nRemaining < (GUInt32)(sizeof(double) * ( 4 + (( eType == FGFT_GEOMETRY ) ? 4 : 0) + (poField->bHasM + poField->bHasZ) * 3 )) );

    #define READ_DOUBLE(field) do { \
        field = GetFloat64(pabyIter, 0); \
        pabyIter += sizeof(double); \
        nRemaining -= sizeof(double); } while(0)

                READ_DOUBLE(poField->dfXOrigin);
                READ_DOUBLE(poField->dfYOrigin);
                READ_DOUBLE(poField->dfXYScale);

                if( poField->bHasM )
                {
                    READ_DOUBLE(poField->dfMOrigin);
                    READ_DOUBLE(poField->dfMScale);
                }

                if( poField->bHasZ )
                {
                    READ_DOUBLE(poField->dfZOrigin);
                    READ_DOUBLE(poField->dfZScale);
                }

                READ_DOUBLE(poField->dfXYTolerance);

                if( poField->bHasM )
                {
                    READ_DOUBLE(poField->dfMTolerance);
                }

                if( poField->bHasZ )
                {
                    READ_DOUBLE(poField->dfZTolerance);
                }
            }

            if( eType == FGFT_RASTER )
            {
                /* Always one byte at end ? */
                returnErrorIf(nRemaining < 1 );
                pabyIter += 1;
                nRemaining -= 1;
            }
            else
            {
                READ_DOUBLE(poField->dfXMin);
                READ_DOUBLE(poField->dfYMin);
                READ_DOUBLE(poField->dfXMax);
                READ_DOUBLE(poField->dfYMax);

                /* Purely empiric logic ! */
                /* Well, it seems that in practice there are 1 or 3 doubles */
                /* here. When there are 3, the first one is zmin and the second */
                /* one is zmax */
                int nCountDoubles = 0;
                while( TRUE )
                {
                    returnErrorIf(nRemaining < 5 );

                    if( pabyIter[0] == 0x00 && pabyIter[1] >= 1 && pabyIter[1] <= 3 &&
                        pabyIter[2] == 0x00 && pabyIter[3] == 0x00 && pabyIter[4] == 0x00 )
                    {
                        GByte nToSkip = pabyIter[1];
                        pabyIter += 5;
                        nRemaining -= 5;
                        returnErrorIf(nRemaining < (GUInt32)(nToSkip * 8) );
                        nCountDoubles += nToSkip;
                        pabyIter += nToSkip * 8;
                        nRemaining -= nToSkip * 8;
                        break;
                    }
                    else
                    {
                        returnErrorIf(nRemaining < 8 );
                        pabyIter += 8;
                        nRemaining -= 8;
                        nCountDoubles ++;
                    }
                }
                if( nCountDoubles == 3 )
                    poField->bHas3D = TRUE;
            }
        }

        nCountNullableFields += apoFields[apoFields.size()-1]->bNullable;
    }
    nNullableFieldsSizeInBytes = BIT_ARRAY_SIZE_IN_BYTES(nCountNullableFields);

#ifdef DEBUG_VERBOSE
    if( nRemaining > 0 )
    {
        CPLDebug("OpenFileGDB", "%u remaining (ignored) bytes in field header section",
                 nRemaining);
    }
#endif

    if( nValidRecordCount > 0 && fpTableX == NULL )
        return GuessFeatureLocations();

    return TRUE;
}

/************************************************************************/
/*                          SkipVarUInt()                               */
/************************************************************************/

/* Bound check only valid if nIter <= 4 */
static int SkipVarUInt(GByte*& pabyIter, GByte* pabyEnd, int nIter = 1)
{
    const int errorRetValue = FALSE;
    GByte* pabyLocalIter = pabyIter;
    returnErrorIf(pabyLocalIter /*+ nIter - 1*/ >= pabyEnd);
    while( nIter -- > 0 )
    {
        while(TRUE)
        {
            GByte b = *pabyLocalIter;
            pabyLocalIter ++;
            if( (b & 0x80) == 0 )
                break;
        }
    }
    pabyIter = pabyLocalIter;
    return TRUE;
}

/************************************************************************/
/*                      ReadVarIntAndAddNoCheck()                       */
/************************************************************************/

static void ReadVarIntAndAddNoCheck(GByte*& pabyIter, GIntBig& nOutVal)
{
    GUInt32 b;

    b = *pabyIter;
    GUIntBig nVal = (b & 0x3F);
    int nSign = 1;
    if( (b & 0x40) != 0 )
        nSign = -1;
    if( (b & 0x80) == 0 )
    {
        pabyIter ++;
        nOutVal += nVal * nSign;
        return;
    }

    GByte* pabyLocalIter = pabyIter + 1;
    int nShift = 6;
    while(TRUE)
    {
        GUIntBig b = *pabyLocalIter;
        pabyLocalIter ++;
        nVal |= ( b & 0x7F ) << nShift;
        if( (b & 0x80) == 0 )
        {
            pabyIter = pabyLocalIter;
            nOutVal += nVal * nSign;
            return;
        }
        nShift += 7;
    }
}

/************************************************************************/
/*                       GetOffsetInTableForRow()                       */
/************************************************************************/

vsi_l_offset FileGDBTable::GetOffsetInTableForRow(int iRow)
{
    const int errorRetValue = 0;
    returnErrorIf(iRow < 0 || iRow >= nTotalRecordCount );

    bIsDeleted = FALSE;
    if( fpTableX == NULL )
    {
        bIsDeleted = IS_DELETED(anFeatureOffsets[iRow]);
        return GET_OFFSET(anFeatureOffsets[iRow]);
    }

    if( pabyTablXBlockMap != NULL )
    {
        GUInt32 nCountBlocksBefore = 0;
        int iBlock = iRow / 1024;

        // Check if the block is not empty
        if( TEST_BIT(pabyTablXBlockMap, iBlock) == 0 )
            return 0;

        // In case of sequential reading, optimization to avoid recomputing
        // the number of blocks since the beginning of the map
        if( iBlock >= nCountBlocksBeforeIBlockIdx )
        {
            nCountBlocksBefore = nCountBlocksBeforeIBlockValue;
            for(int i=nCountBlocksBeforeIBlockIdx;i<iBlock;i++)
                nCountBlocksBefore += TEST_BIT(pabyTablXBlockMap, i) != 0;
        }
        else
        {
            nCountBlocksBefore = 0;
            for(int i=0;i<iBlock;i++)
                nCountBlocksBefore += TEST_BIT(pabyTablXBlockMap, i) != 0;
        }
        nCountBlocksBeforeIBlockIdx = iBlock;
        nCountBlocksBeforeIBlockValue = nCountBlocksBefore;
        int iCorrectedRow = nCountBlocksBefore * 1024 + (iRow % 1024);
        VSIFSeekL(fpTableX, 16 + nTablxOffsetSize * iCorrectedRow, SEEK_SET);
    }
    else
    {
        VSIFSeekL(fpTableX, 16 + nTablxOffsetSize * iRow, SEEK_SET);
    }

    GByte abyBuffer[6];
    bError = VSIFReadL(abyBuffer, nTablxOffsetSize, 1, fpTableX) != 1;
    returnErrorIf(bError );
    vsi_l_offset nOffset;

    if( nTablxOffsetSize == 4 )
        nOffset = GetUInt32(abyBuffer, 0);
    else if( nTablxOffsetSize == 5 )
        nOffset = GetUInt32(abyBuffer, 0) | (((vsi_l_offset)abyBuffer[4]) << 32);
    else
        nOffset = GetUInt32(abyBuffer, 0) | (((vsi_l_offset)abyBuffer[4]) << 32) | (((vsi_l_offset)abyBuffer[5]) << 40);

#ifdef DEBUG_VERBOSE
    if( iRow == 0 && nOffset != 0 &&
        nOffset != nOffsetHeaderEnd && nOffset != nOffsetHeaderEnd + 4 )
        CPLDebug("OpenFileGDB", "%s: first feature offset = " CPL_FRMT_GUIB ". Expected %d",
                 osFilename.c_str(), nOffset, nOffsetHeaderEnd);
#endif

    return nOffset;
}

/************************************************************************/
/*                      GetAndSelectNextNonEmptyRow()                   */
/************************************************************************/

int FileGDBTable::GetAndSelectNextNonEmptyRow(int iRow)
{
    const int errorRetValue = -1;
    returnErrorAndCleanupIf(iRow < 0 || iRow >= nTotalRecordCount, nCurRow = -1 );

    while( iRow < nTotalRecordCount )
    {
        if( pabyTablXBlockMap != NULL && (iRow % 1024) == 0 )
        {
            int iBlock = iRow / 1024;
            if( TEST_BIT(pabyTablXBlockMap, iBlock) == 0 )
            {
                int nBlocks = (nTotalRecordCount+1023)/1024;
                do
                {
                    iBlock ++;
                }
                while( iBlock < nBlocks &&
                    TEST_BIT(pabyTablXBlockMap, iBlock) == 0 );

                iRow = iBlock * 1024;
                if( iRow >= nTotalRecordCount )
                    return -1;
            }
        }

        if( SelectRow(iRow) )
            return iRow;
        if( HasGotError() )
            return -1;
        iRow ++;
    }

    return -1;
}

/************************************************************************/
/*                            SelectRow()                               */
/************************************************************************/

int FileGDBTable::SelectRow(int iRow)
{
    const int errorRetValue = FALSE;
    returnErrorAndCleanupIf(iRow < 0 || iRow >= nTotalRecordCount, nCurRow = -1 );

    if( nCurRow != iRow )
    {
        vsi_l_offset nOffsetTable = GetOffsetInTableForRow(iRow);
        if( nOffsetTable == 0 )
        {
            nCurRow = -1;
            return FALSE;
        }

        VSIFSeekL(fpTable, nOffsetTable, SEEK_SET);
        GByte abyBuffer[4];
        returnErrorAndCleanupIf(
                VSIFReadL(abyBuffer, 4, 1, fpTable) != 1, nCurRow = -1 );

        nRowBlobLength = GetUInt32(abyBuffer, 0);
        if( bIsDeleted )
        {
            nRowBlobLength = (GUInt32)(-(int)nRowBlobLength);
        }

        if( !(apoFields.size() == 0 && nRowBlobLength == 0) )
        {
            /* CPLDebug("OpenFileGDB", "nRowBlobLength = %u", nRowBlobLength); */
            returnErrorAndCleanupIf(
                    nRowBlobLength < (GUInt32)nNullableFieldsSizeInBytes ||
                    nRowBlobLength > INT_MAX - ZEROES_AFTER_END_OF_BUFFER, nCurRow = -1 );

            if( nRowBlobLength > nBufferMaxSize )
            {
                /* For suspicious row blob length, check if we don't go beyond file size */
                if( nRowBlobLength > 100 * 1024 * 1024 )
                {
                    if( nFileSize == 0 )
                    {
                        VSIFSeekL(fpTable, 0, SEEK_END);
                        nFileSize = VSIFTellL(fpTable);
                        VSIFSeekL(fpTable, nOffsetTable + 4, SEEK_SET);
                    }
                    returnErrorAndCleanupIf( nOffsetTable + 4 + nRowBlobLength > nFileSize, nCurRow = -1 );
                }

                GByte* pabyNewBuffer = (GByte*) VSIRealloc( pabyBuffer,
                                nRowBlobLength + ZEROES_AFTER_END_OF_BUFFER );
                returnErrorAndCleanupIf(pabyNewBuffer == NULL, nCurRow = -1 );

                pabyBuffer = pabyNewBuffer;
                nBufferMaxSize = nRowBlobLength;
            }
            returnErrorAndCleanupIf(
                VSIFReadL(pabyBuffer, nRowBlobLength, 1, fpTable) != 1, nCurRow = -1 );
            /* Protection for 4 ReadVarUInt64NoCheck */
            CPLAssert(ZEROES_AFTER_END_OF_BUFFER == 4);
            pabyBuffer[nRowBlobLength] = 0;
            pabyBuffer[nRowBlobLength+1] = 0;
            pabyBuffer[nRowBlobLength+2] = 0;
            pabyBuffer[nRowBlobLength+3] = 0;
        }

        nCurRow = iRow;
        nLastCol = -1;
        pabyIterVals = pabyBuffer + nNullableFieldsSizeInBytes;
        iAccNullable = 0;
        bError = FALSE;
        nChSaved = -1;
    }

    return TRUE;
}

/************************************************************************/
/*                      FileGDBDoubleDateToOGRDate()                    */
/************************************************************************/

int FileGDBDoubleDateToOGRDate(double dfVal, OGRField* psField)
{
    struct tm brokendowntime;

    /* 25569 = Number of days between 1899/12/30 00:00:00 and 1970/01/01 00:00:00 */
    CPLUnixTimeToYMDHMS((GIntBig)((dfVal - 25569) * 3600 * 24), &brokendowntime);

    psField->Date.Year = (GInt16)(brokendowntime.tm_year + 1900);
    psField->Date.Month = (GByte)brokendowntime.tm_mon + 1;
    psField->Date.Day = (GByte)brokendowntime.tm_mday;
    psField->Date.Hour = (GByte)brokendowntime.tm_hour;
    psField->Date.Minute = (GByte)brokendowntime.tm_min;
    psField->Date.Second = (float)brokendowntime.tm_sec;
    psField->Date.TZFlag = 0;
    psField->Date.Reserved = 0;

    return TRUE;
}

/************************************************************************/
/*                          GetFieldValue()                             */
/************************************************************************/

const OGRField* FileGDBTable::GetFieldValue(int iCol)
{
    const OGRField* errorRetValue = NULL;

    returnErrorIf(nCurRow < 0 );
    returnErrorIf((GUInt32)iCol >= apoFields.size() );
    returnErrorIf(bError );

    GByte* pabyEnd = pabyBuffer + nRowBlobLength;

    /* In case a string was previously read */
    if( nChSaved >= 0 )
    {
        *pabyIterVals = (GByte)nChSaved;
        nChSaved = -1;
    }

    if( iCol <= nLastCol )
    {
        nLastCol = -1;
        pabyIterVals = pabyBuffer + nNullableFieldsSizeInBytes;
        iAccNullable = 0;
    }

    // Skip previous fields
    for( int j = nLastCol + 1; j < iCol; j++ )
    {
        if( apoFields[j]->bNullable )
        {
            int bIsNull = TEST_BIT(pabyBuffer, iAccNullable);
            iAccNullable ++;
            if( bIsNull )
                continue;
        }

        GUInt32 nLength;
        switch( apoFields[j]->eType )
        {
            case FGFT_STRING:
            case FGFT_XML:
            case FGFT_GEOMETRY:
            case FGFT_BINARY:
            {
                if( !ReadVarUInt32(pabyIterVals, pabyEnd, nLength) )
                {
                    bError = TRUE;
                    returnError();
                }
                break;
            }

            /* Only 4 bytes ? */
            case FGFT_RASTER: nLength = sizeof(GInt32); break;

            case FGFT_INT16: nLength = sizeof(GInt16); break;
            case FGFT_INT32: nLength = sizeof(GInt32); break;
            case FGFT_FLOAT32: nLength = sizeof(float); break;
            case FGFT_FLOAT64: nLength = sizeof(double); break;
            case FGFT_DATETIME: nLength = sizeof(double); break;
            case FGFT_UUID_1:
            case FGFT_UUID_2: nLength = UUID_SIZE_IN_BYTES; break;

            default:
                nLength = 0;
                CPLAssert(FALSE);
                break;
        }

        if( nLength > (GUInt32)(pabyEnd - pabyIterVals) )
        {
            bError = TRUE;
            returnError();
        }
        pabyIterVals += nLength;
    }

    nLastCol = iCol;

    if( apoFields[iCol]->bNullable )
    {
        int bIsNull = TEST_BIT(pabyBuffer, iAccNullable);
        iAccNullable ++;
        if( bIsNull )
        {
            return NULL;
        }
    }

    switch( apoFields[iCol]->eType )
    {
        case FGFT_STRING:
        case FGFT_XML:
        {
            GUInt32 nLength;
            if( !ReadVarUInt32(pabyIterVals, pabyEnd, nLength) )
            {
                bError = TRUE;
                returnError();
            }
            if( nLength > (GUInt32)(pabyEnd - pabyIterVals) )
            {
                bError = TRUE;
                returnError();
            }

            /* eCurFieldType = OFTString; */
            sCurField.String = (char*) pabyIterVals;
            pabyIterVals += nLength;

            /* This is a trick to avoid a alloc()+copy(). We null-terminate */
            /* after the string, and save the pointer and value to restore */
            nChSaved = *pabyIterVals;
            *pabyIterVals = '\0';

            /* CPLDebug("OpenFileGDB", "Field %d, row %d: %s", iCol, nCurRow, sCurField.String); */ 

            break;
        }

        case FGFT_INT16:
        {
            if( pabyIterVals + sizeof(GInt16) > pabyEnd )
            {
                bError = TRUE;
                returnError();
            }

            /* eCurFieldType = OFTInteger; */
            sCurField.Integer = GetInt16(pabyIterVals, 0);
            
            pabyIterVals += sizeof(GInt16);
            /* CPLDebug("OpenFileGDB", "Field %d, row %d: %d", iCol, nCurRow, sCurField.Integer); */

            break;
        }

        case FGFT_INT32:
        {
            if( pabyIterVals + sizeof(GInt32) > pabyEnd )
            {
                bError = TRUE;
                returnError();
            }

            /* eCurFieldType = OFTInteger; */
            sCurField.Integer = GetInt32(pabyIterVals, 0);
            
            pabyIterVals += sizeof(GInt32);
            /* CPLDebug("OpenFileGDB", "Field %d, row %d: %d", iCol, nCurRow, sCurField.Integer); */

            break;
        }

        case FGFT_FLOAT32:
        {
            if( pabyIterVals + sizeof(float) > pabyEnd )
            {
                bError = TRUE;
                returnError();
            }

            /* eCurFieldType = OFTReal; */
            sCurField.Real = GetFloat32(pabyIterVals, 0);
            
            pabyIterVals += sizeof(float);
            /* CPLDebug("OpenFileGDB", "Field %d, row %d: %f", iCol, nCurRow, sCurField.Real); */

            break;
        }

        case FGFT_FLOAT64:
        {
            if( pabyIterVals + sizeof(double) > pabyEnd )
            {
                bError = TRUE;
                returnError();
            }

            /* eCurFieldType = OFTReal; */
            sCurField.Real = GetFloat64(pabyIterVals, 0);
            
            pabyIterVals += sizeof(double);
            /* CPLDebug("OpenFileGDB", "Field %d, row %d: %f", iCol, nCurRow, sCurField.Real); */

            break;
        }

        case FGFT_DATETIME:
        {
            if( pabyIterVals + sizeof(double) > pabyEnd )
            {
                bError = TRUE;
                returnError();
            }

            /* Number of days since 1899/12/30 00:00:00 */
            double dfVal = GetFloat64(pabyIterVals, 0);

            FileGDBDoubleDateToOGRDate(dfVal, &sCurField);
            /* eCurFieldType = OFTDateTime; */

            pabyIterVals += sizeof(double);

            break;
        }

        case FGFT_GEOMETRY:
        case FGFT_BINARY:
        {
            GUInt32 nLength;
            if( !ReadVarUInt32(pabyIterVals, pabyEnd, nLength) )
            {
                bError = TRUE;
                returnError();
            }
            if( nLength > (GUInt32)(pabyEnd - pabyIterVals) )
            {
                bError = TRUE;
                returnError();
            }

            /* eCurFieldType = OFTBinary; */
            sCurField.Binary.nCount = nLength;
            sCurField.Binary.paData = (GByte*) pabyIterVals;

            pabyIterVals += nLength;

            /* Null terminate binary in case it is used as a string */
            nChSaved = *pabyIterVals;
            *pabyIterVals = '\0';

            /* CPLDebug("OpenFileGDB", "Field %d, row %d: %d bytes", iCol, nCurRow, snLength); */

            break;
        }

        /* Only 4 bytes ? */
        case FGFT_RASTER:
        {
            if( pabyIterVals + sizeof(GInt32) > pabyEnd )
            {
                bError = TRUE;
                returnError();
            }

            /* GInt32 nVal = GetInt32(pabyIterVals, 0); */

            /* eCurFieldType = OFTBinary; */
            sCurField.Set.nMarker1 = OGRUnsetMarker;
            sCurField.Set.nMarker2 = OGRUnsetMarker;

            pabyIterVals += sizeof(GInt32);
            /* CPLDebug("OpenFileGDB", "Field %d, row %d: %d", iCol, nCurRow, sCurField.Integer); */
            break;
        }

        case FGFT_UUID_1:
        case FGFT_UUID_2:
        {
            if( pabyIterVals + UUID_SIZE_IN_BYTES > pabyEnd )
            {
                bError = TRUE;
                returnError();
            }

            /* eCurFieldType = OFTString; */
            sCurField.String = achGUIDBuffer;
            /*78563412BC9AF0DE1234567890ABCDEF --> {12345678-9ABC-DEF0-1234-567890ABCDEF} */
            sprintf(achGUIDBuffer,
                    "{%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
                    pabyIterVals[3], pabyIterVals[2], pabyIterVals[1], pabyIterVals[0],
                    pabyIterVals[5], pabyIterVals[4],
                    pabyIterVals[7], pabyIterVals[6],
                    pabyIterVals[8], pabyIterVals[9],
                    pabyIterVals[10], pabyIterVals[11], pabyIterVals[12],
                    pabyIterVals[13], pabyIterVals[14], pabyIterVals[15]);

            pabyIterVals += UUID_SIZE_IN_BYTES;
            /* CPLDebug("OpenFileGDB", "Field %d, row %d: %s", iCol, nCurRow, sCurField.String); */

            break;
        }

        default:
            CPLAssert(FALSE);
            break;
    }

    if( iCol == (int)apoFields.size() - 1 && pabyIterVals < pabyEnd )
    {
        CPLDebug("OpenFileGDB", "%d bytes remaining at end of record %d",
                 (int)(pabyEnd - pabyIterVals), nCurRow);
    }

    return &sCurField;
}

/************************************************************************/
/*                           GetIndexCount()                            */
/************************************************************************/

int FileGDBTable::GetIndexCount()
{
    const int errorRetValue = 0;
    if( bHasReadGDBIndexes )
        return (int) apoIndexes.size();

    bHasReadGDBIndexes = TRUE;

    const char* pszIndexesName = CPLFormFilename(CPLGetPath(osFilename.c_str()),
                                    CPLGetBasename(osFilename.c_str()), "gdbindexes");
    VSILFILE* fpIndexes = VSIFOpenL( pszIndexesName, "rb" );
    VSIStatBufL sStat;
    if( fpIndexes == NULL )
    {
        if ( VSIStatExL( pszIndexesName, &sStat, VSI_STAT_EXISTS_FLAG) == 0 )
            returnError();
        else
            return 0;
    }

    VSIFSeekL(fpIndexes, 0, SEEK_END);
    vsi_l_offset nFileSize = VSIFTellL(fpIndexes);
    returnErrorAndCleanupIf(nFileSize > 1024 * 1024, VSIFCloseL(fpIndexes) );

    GByte* pabyIdx = (GByte*)VSIMalloc((size_t)nFileSize);
    returnErrorAndCleanupIf(pabyIdx == NULL, VSIFCloseL(fpIndexes) );

    VSIFSeekL(fpIndexes, 0, SEEK_SET);
    int nRead = (int)VSIFReadL( pabyIdx, (size_t)nFileSize, 1, fpIndexes );
    VSIFCloseL(fpIndexes);
    returnErrorAndCleanupIf(nRead != 1, VSIFree(pabyIdx) );

    GByte* pabyCur = pabyIdx;
    GByte* pabyEnd = pabyIdx + nFileSize;
    returnErrorAndCleanupIf(pabyEnd - pabyCur < 4, VSIFree(pabyIdx) );
    GUInt32 nIndexCount = GetUInt32(pabyCur, 0);
    pabyCur += 4;

    // FileGDB v9 indexes structure not handled yet. Start with 13 98 85 03
    if( nIndexCount == 0x03859813 )
    {
        CPLDebug("OpenFileGDB", ".gdbindexes v9 not handled yet");
        VSIFree(pabyIdx);
        return 0;
    }
    returnErrorAndCleanupIf(nIndexCount >= (size_t)(GetFieldCount() + 1) * 10, VSIFree(pabyIdx) );

    GUInt32 i;
    for(i=0;i<nIndexCount;i++)
    {
        returnErrorAndCleanupIf((GUInt32)(pabyEnd - pabyCur) < sizeof(GUInt32), VSIFree(pabyIdx) );
        GUInt32 nIdxNameCarCount = GetUInt32(pabyCur, 0);
        pabyCur += sizeof(GUInt32);
        returnErrorAndCleanupIf(nIdxNameCarCount > 1024, VSIFree(pabyIdx) );
        returnErrorAndCleanupIf((GUInt32)(pabyEnd - pabyCur) < 2 * nIdxNameCarCount, VSIFree(pabyIdx) );
        std::string osIndexName;
        GUInt32 j;
        for(j=0;j<nIdxNameCarCount;j++) //FIXME? UTF16
            osIndexName += pabyCur[2 * j];
        pabyCur += 2 * nIdxNameCarCount;

        // Skip magic fields
        pabyCur += 2 + 4 + 2 + 4;

        returnErrorAndCleanupIf((GUInt32)(pabyEnd - pabyCur) < sizeof(GUInt32), VSIFree(pabyIdx) );
        GUInt32 nColNameCarCount = GetUInt32(pabyCur, 0);
        pabyCur += sizeof(GUInt32);
        returnErrorAndCleanupIf(nColNameCarCount > 1024, VSIFree(pabyIdx) );
        returnErrorAndCleanupIf((GUInt32)(pabyEnd - pabyCur) < 2 * nColNameCarCount, VSIFree(pabyIdx) );
        std::string osFieldName;
        for(j=0;j<nColNameCarCount;j++) //FIXME? UTF16
            osFieldName += pabyCur[2 * j];
        pabyCur += 2 * nColNameCarCount;

        // Skip magic field
        pabyCur += 2;

        FileGDBIndex* poIndex = new FileGDBIndex();
        poIndex->osIndexName = osIndexName;
        poIndex->osFieldName = osFieldName;
        apoIndexes.push_back(poIndex);

        if( osFieldName != osObjectIdColName )
        {
            int nFieldIdx = GetFieldIdx(osFieldName);
            if( nFieldIdx < 0 )
            {
                CPLDebug("OpenFileGDB",
                         "Index defined for field %s that does not exist",
                         osFieldName.c_str());
            }
            else
            {
                if( apoFields[nFieldIdx]->poIndex != NULL )
                {
                    CPLDebug("OpenFileGDB",
                             "There is already one index defined for field %s",
                              osFieldName.c_str());
                }
                else
                {
                    apoFields[nFieldIdx]->poIndex = poIndex;
                }
            }
        }
    }

    VSIFree(pabyIdx);

    return (int) apoIndexes.size();
}

/************************************************************************/
/*                       InstallFilterEnvelope()                        */
/************************************************************************/

#define MAX_GUINTBIG    (~((GUIntBig)0))

void FileGDBTable::InstallFilterEnvelope(const OGREnvelope* psFilterEnvelope)
{
    if( psFilterEnvelope != NULL )
    {
        CPLAssert( iGeomField >= 0 );
        FileGDBGeomField* poGeomField = (FileGDBGeomField*) GetField(iGeomField);

        /* We store the bounding box as unscaled coordinates, so that BBOX */
        /* intersection is done with integer comparisons */
        if( psFilterEnvelope->MinX >= poGeomField->dfXOrigin )
            nFilterXMin = (GUIntBig)(0.5 + (psFilterEnvelope->MinX -
                                poGeomField->dfXOrigin) * poGeomField->dfXYScale);
        else
            nFilterXMin = 0;
        if( psFilterEnvelope->MaxX - poGeomField->dfXOrigin <
                                        MAX_GUINTBIG / poGeomField->dfXYScale )
            nFilterXMax = (GUIntBig)(0.5 + (psFilterEnvelope->MaxX -
                            poGeomField->dfXOrigin) * poGeomField->dfXYScale);
        else
            nFilterXMax = MAX_GUINTBIG;
        if( psFilterEnvelope->MinY >= poGeomField->dfYOrigin )
            nFilterYMin = (GUIntBig)(0.5 + (psFilterEnvelope->MinY -
                                poGeomField->dfYOrigin) * poGeomField->dfXYScale);
        else
            nFilterYMin = 0;
        if( psFilterEnvelope->MaxY - poGeomField->dfYOrigin <
                                        MAX_GUINTBIG / poGeomField->dfXYScale )
            nFilterYMax = (GUIntBig)(0.5 + (psFilterEnvelope->MaxY -
                                poGeomField->dfYOrigin) * poGeomField->dfXYScale);
        else
            nFilterYMax = MAX_GUINTBIG;
    }
    else
    {
        nFilterXMin = 0;
        nFilterXMax = 0;
        nFilterYMin = 0;
        nFilterYMax = 0;
    }
}

/************************************************************************/
/*                         GetFeatureExtent()                           */
/************************************************************************/

int FileGDBTable::GetFeatureExtent(const OGRField* psField,
                                   OGREnvelope* psOutFeatureEnvelope)
{
    const int errorRetValue = FALSE;
    GByte* pabyCur = psField->Binary.paData;
    GByte* pabyEnd = pabyCur + psField->Binary.nCount;
    GUInt32 nGeomType;
    int nToSkip = 0;

    CPLAssert( iGeomField >= 0 );
    FileGDBGeomField* poGeomField = (FileGDBGeomField*) GetField(iGeomField);

    ReadVarUInt32NoCheck(pabyCur, nGeomType);

    switch( (nGeomType & 0xff) )
    {
        case SHPT_NULL:
            return FALSE;

        case SHPT_POINTZ:
        case SHPT_POINTZM:
        case SHPT_POINT:
        case SHPT_POINTM:
        {
            GUIntBig x, y;
            ReadVarUInt64NoCheck(pabyCur, x);
            x --;
            ReadVarUInt64NoCheck(pabyCur, y);
            y --;
            psOutFeatureEnvelope->MinX = x / poGeomField->dfXYScale + poGeomField->dfXOrigin;
            psOutFeatureEnvelope->MinY = y / poGeomField->dfXYScale + poGeomField->dfYOrigin;
            psOutFeatureEnvelope->MaxX = psOutFeatureEnvelope->MinX;
            psOutFeatureEnvelope->MaxY = psOutFeatureEnvelope->MinY;
            return TRUE;
        }

        case SHPT_MULTIPOINTZM:
        case SHPT_MULTIPOINTZ:
        case SHPT_MULTIPOINT:
        case SHPT_MULTIPOINTM:
        {
            break;
        }

        case SHPT_ARC:
        case SHPT_ARCZ:
        case SHPT_ARCZM:
        case SHPT_ARCM:
        case SHPT_POLYGON:
        case SHPT_POLYGONZ:
        case SHPT_POLYGONZM:
        case SHPT_POLYGONM:
        {
            nToSkip = 1;
            break;
        }
        case SHPT_GENERALPOLYLINE:
        case SHPT_GENERALPOLYGON:
        {
            nToSkip = 1 + ((nGeomType & 0x20000000) ? 1 : 0);
            break;
        }

        case SHPT_GENERALMULTIPATCH:
        case SHPT_MULTIPATCHM:
        case SHPT_MULTIPATCH:
        {
            nToSkip = 2;
            break;
        }

        default:
            return FALSE;
    }

    GUInt32 nPoints;
    ReadVarUInt32NoCheck(pabyCur, nPoints);
    if( nPoints == 0 )
        return TRUE;
    returnErrorIf(!SkipVarUInt(pabyCur, pabyEnd, nToSkip) );
    
    GUIntBig vxmin, vymin, vdx, vdy;

    returnErrorIf(pabyCur >= pabyEnd);
    ReadVarUInt64NoCheck(pabyCur, vxmin);
    ReadVarUInt64NoCheck(pabyCur, vymin);
    ReadVarUInt64NoCheck(pabyCur, vdx);
    ReadVarUInt64NoCheck(pabyCur, vdy);
    
    psOutFeatureEnvelope->MinX = vxmin / poGeomField->dfXYScale + poGeomField->dfXOrigin;
    psOutFeatureEnvelope->MinY = vymin / poGeomField->dfXYScale + poGeomField->dfYOrigin;
    psOutFeatureEnvelope->MaxX = (vxmin + vdx) / poGeomField->dfXYScale + poGeomField->dfXOrigin;
    psOutFeatureEnvelope->MaxY = (vymin + vdy) / poGeomField->dfXYScale + poGeomField->dfYOrigin;

    return TRUE;
}

/************************************************************************/
/*                 DoesGeometryIntersectsFilterEnvelope()               */
/************************************************************************/

int FileGDBTable::DoesGeometryIntersectsFilterEnvelope(const OGRField* psField)
{
    const int errorRetValue = TRUE;
    GByte* pabyCur = psField->Binary.paData;
    GByte* pabyEnd = pabyCur + psField->Binary.nCount;
    GUInt32 nGeomType;
    int nToSkip = 0;

    ReadVarUInt32NoCheck(pabyCur, nGeomType);

    switch( (nGeomType & 0xff) )
    {
        case SHPT_NULL:
            return TRUE;

        case SHPT_POINTZ:
        case SHPT_POINTZM:
        case SHPT_POINT:
        case SHPT_POINTM:
        {
            GUIntBig x, y;
            ReadVarUInt64NoCheck(pabyCur, x);
            x --;
            if( x < nFilterXMin || x > nFilterXMax )
                return FALSE;
            ReadVarUInt64NoCheck(pabyCur, y);
            y --;
            return( y >= nFilterYMin && y <= nFilterYMax );
        }

        case SHPT_MULTIPOINTZM:
        case SHPT_MULTIPOINTZ:
        case SHPT_MULTIPOINT:
        case SHPT_MULTIPOINTM:
        {
            break;
        }

        case SHPT_ARC:
        case SHPT_ARCZ:
        case SHPT_ARCZM:
        case SHPT_ARCM:
        case SHPT_POLYGON:
        case SHPT_POLYGONZ:
        case SHPT_POLYGONZM:
        case SHPT_POLYGONM:
        {
            nToSkip = 1;
            break;
        }

        case SHPT_GENERALPOLYLINE:
        case SHPT_GENERALPOLYGON:
        {
            nToSkip = 1 + ((nGeomType & 0x20000000) ? 1 : 0);
            break;
        }

        case SHPT_GENERALMULTIPATCH:
        case SHPT_MULTIPATCHM:
        case SHPT_MULTIPATCH:
        {
            nToSkip = 2;
            break;
        }

        default:
            return TRUE;
    }

    GUInt32 nPoints;
    ReadVarUInt32NoCheck(pabyCur, nPoints);
    if( nPoints == 0 )
        return TRUE;
    returnErrorIf(!SkipVarUInt(pabyCur, pabyEnd, nToSkip) );
    
    GUIntBig vxmin, vymin, vdx, vdy;

    returnErrorIf(pabyCur >= pabyEnd);
    ReadVarUInt64NoCheck(pabyCur, vxmin);
    if( vxmin > nFilterXMax )
        return FALSE;
    ReadVarUInt64NoCheck(pabyCur, vymin);
    if( vymin > nFilterYMax )
        return FALSE;
    ReadVarUInt64NoCheck(pabyCur, vdx);
    if( vxmin + vdx < nFilterXMin )
        return FALSE;
    ReadVarUInt64NoCheck(pabyCur, vdy);
    return vymin + vdy >= nFilterYMin;
}

/************************************************************************/
/*                           FileGDBField()                             */
/************************************************************************/

FileGDBField::FileGDBField(FileGDBTable* poParent) :
    poParent(poParent), eType(FGFT_UNDEFINED), bNullable(FALSE),
    nMaxWidth(0), poIndex(NULL)
{
    sDefault.Set.nMarker1 = OGRUnsetMarker;
    sDefault.Set.nMarker2 = OGRUnsetMarker;
}

/************************************************************************/
/*                          ~FileGDBField()                             */
/************************************************************************/

FileGDBField::~FileGDBField()
{
    if( eType == FGFT_STRING &&
        !(sDefault.Set.nMarker1 == OGRUnsetMarker &&
          sDefault.Set.nMarker2 == OGRUnsetMarker) )
        CPLFree(sDefault.String);
}


/************************************************************************/
/*                            HasIndex()                                */
/************************************************************************/

int FileGDBField::HasIndex()
{
     poParent->GetIndexCount();
     return poIndex != NULL;
}

/************************************************************************/
/*                            GetIndex()                                */
/************************************************************************/

FileGDBIndex *FileGDBField::GetIndex()
{
     poParent->GetIndexCount();
     return poIndex;
}

/************************************************************************/
/*                           FileGDBGeomField()                         */
/************************************************************************/

FileGDBGeomField::FileGDBGeomField(FileGDBTable* poParent) :
    FileGDBField(poParent), bHasZ(FALSE), bHasM(FALSE),
    dfXOrigin(0.0), dfYOrigin(0.0), dfXYScale(0.0), dfMOrigin(0.0),
    dfMScale(0.0), dfZOrigin(0.0), dfZScale(0.0), dfXYTolerance(0.0),
    dfMTolerance(0.0), dfZTolerance(0.0), dfXMin(0.0), dfYMin(0.0),
    dfXMax(0.0), dfYMax(0.0), bHas3D(FALSE)
{
}

/************************************************************************/
/*                      FileGDBOGRGeometryConverterImpl                 */
/************************************************************************/

class FileGDBOGRGeometryConverterImpl : public FileGDBOGRGeometryConverter
{
        const FileGDBGeomField      *poGeomField;
        GUInt32                     *panPointCount;
        GUInt32                      nPointCountMax;
#ifdef ASSUME_INNER_RINGS_IMMEDIATELY_AFTER_OUTER_RING
        int                          bUseOrganize;
#endif

        int                         ReadPartDefs( GByte*& pabyCur,
                                                  GByte* pabyEnd,
                                                  GUInt32& nPoints,
                                                  GUInt32& nParts,
                                                  int bHasCurveDesc,
                                                  int bIsMultiPatch );
        template <class XYSetter> int ReadXYArray(XYSetter& setter,
                                                  GByte*& pabyCur,
                                                  GByte* pabyEnd,
                                                  GUInt32 nPoints,
                                                  GIntBig& dx,
                                                  GIntBig& dy);
        template <class ZSetter> int ReadZArray(ZSetter& setter,
                                                GByte*& pabyCur,
                                                GByte* pabyEnd,
                                                GUInt32 nPoints,
                                                GIntBig& dz);

    public:
                                        FileGDBOGRGeometryConverterImpl(
                                            const FileGDBGeomField* poGeomField);
       virtual                         ~FileGDBOGRGeometryConverterImpl();

       virtual OGRGeometry*             GetAsGeometry(const OGRField* psField);
};

/************************************************************************/
/*                  FileGDBOGRGeometryConverterImpl()                   */
/************************************************************************/

FileGDBOGRGeometryConverterImpl::FileGDBOGRGeometryConverterImpl(
                                    const FileGDBGeomField* poGeomField) :
                                                poGeomField(poGeomField)
{
    panPointCount = NULL;
    nPointCountMax = 0;
#ifdef ASSUME_INNER_RINGS_IMMEDIATELY_AFTER_OUTER_RING
    bUseOrganize = CPLGetConfigOption("OGR_ORGANIZE_POLYGONS", NULL) != NULL;
#endif
}

/************************************************************************/
/*                 ~FileGDBOGRGeometryConverter()                       */
/************************************************************************/

FileGDBOGRGeometryConverterImpl::~FileGDBOGRGeometryConverterImpl()
{
    CPLFree(panPointCount);
}

/************************************************************************/
/*                          ReadPartDefs()                              */
/************************************************************************/

int FileGDBOGRGeometryConverterImpl::ReadPartDefs( GByte*& pabyCur,
                                GByte* pabyEnd,
                                GUInt32& nPoints,
                                GUInt32& nParts,
                                int bHasCurveDesc,
                                int bIsMultiPatch )
{
    const int errorRetValue = FALSE;
    returnErrorIf(!ReadVarUInt32(pabyCur, pabyEnd, nPoints));
    if( nPoints == 0 )
    {
        nParts = 0;
        return TRUE;
    }
    returnErrorIf(nPoints > (GUInt32)(pabyEnd - pabyCur) );
    if( bIsMultiPatch )
        returnErrorIf(!SkipVarUInt(pabyCur, pabyEnd) );
    returnErrorIf(!ReadVarUInt32(pabyCur, pabyEnd, nParts));
    returnErrorIf(nParts > (GUInt32)(pabyEnd - pabyCur));
    if( bHasCurveDesc )
        returnErrorIf(!SkipVarUInt(pabyCur, pabyEnd) );
    if( nParts == 0 )
        return TRUE;
    GUInt32 i;
    returnErrorIf(!SkipVarUInt(pabyCur, pabyEnd, 4) );
    if( nParts > nPointCountMax )
    {
        GUInt32* panPointCountNew =
            (GUInt32*) VSIRealloc( panPointCount, nParts * sizeof(GUInt32) );
        returnErrorIf(panPointCountNew == NULL );
        panPointCount = panPointCountNew;
        nPointCountMax = nParts;
    }
    GUIntBig nSumNPartsM1 = 0;
    for(i=0;i<nParts-1;i++)
    {
        GUInt32 nTmp;
        returnErrorIf(!ReadVarUInt32(pabyCur, pabyEnd, nTmp));
        returnErrorIf(nTmp > (GUInt32)(pabyEnd - pabyCur) );
        panPointCount[i] = nTmp;
        nSumNPartsM1 += nTmp;
    }
    returnErrorIf(nSumNPartsM1 > nPoints );
    panPointCount[nParts-1] = (GUInt32)(nPoints - nSumNPartsM1);

    return TRUE;
}

/************************************************************************/
/*                         XYLineStringSetter                           */
/************************************************************************/

class FileGDBOGRLineString: public OGRLineString
{
    public:
        FileGDBOGRLineString() {}

        OGRRawPoint * GetPoints() const { return paoPoints; }
};

class FileGDBOGRLinearRing: public OGRLinearRing
{
    public:
        FileGDBOGRLinearRing() {}

        OGRRawPoint * GetPoints() const { return paoPoints; }
};

class XYLineStringSetter
{
        OGRRawPoint* paoPoints;
    public:
        XYLineStringSetter(OGRRawPoint* paoPoints) : paoPoints(paoPoints) {}

        void set(int i, double dfX, double dfY)
        {
            paoPoints[i].x = dfX;
            paoPoints[i].y = dfY;
        }
};

/************************************************************************/
/*                         XYMultiPointSetter                           */
/************************************************************************/

class XYMultiPointSetter
{
        OGRMultiPoint* poMPoint;
    public:
        XYMultiPointSetter(OGRMultiPoint* poMPoint) : poMPoint(poMPoint) {}

        void set(int i, double dfX, double dfY)
        {
            (void)i;
            poMPoint->addGeometryDirectly(new OGRPoint(dfX, dfY));
        }
};

/************************************************************************/
/*                             XYArraySetter                            */
/************************************************************************/

class XYArraySetter
{
        double* padfX;
        double* padfY;
    public:
        XYArraySetter(double* padfX, double* padfY) : padfX(padfX), padfY(padfY) {}

        void set(int i, double dfX, double dfY)
        {
            padfX[i] = dfX;
            padfY[i] = dfY;
        }
};

/************************************************************************/
/*                          ReadXYArray()                               */
/************************************************************************/

template <class XYSetter> int FileGDBOGRGeometryConverterImpl::ReadXYArray(XYSetter& setter,
                                                        GByte*& pabyCur,
                                                        GByte* pabyEnd,
                                                        GUInt32 nPoints,
                                                        GIntBig& dx,
                                                        GIntBig& dy)
{
    const int errorRetValue = FALSE;
    GIntBig dxLocal = dx;
    GIntBig dyLocal = dy;

    for(GUInt32 i = 0; i < nPoints; i++ )
    {
        returnErrorIf(pabyCur /*+ 1*/ >= pabyEnd);

        ReadVarIntAndAddNoCheck(pabyCur, dxLocal);
        ReadVarIntAndAddNoCheck(pabyCur, dyLocal);

        double dfX = dxLocal / poGeomField->GetXYScale() + poGeomField->GetXOrigin();
        double dfY = dyLocal / poGeomField->GetXYScale() + poGeomField->GetYOrigin();
        setter.set(i, dfX, dfY);
    }

    dx = dxLocal;
    dy = dyLocal;
    return TRUE;
}

/************************************************************************/
/*                          ZLineStringSetter                           */
/************************************************************************/

class ZLineStringSetter
{
        OGRLineString* poLS;
    public:
        ZLineStringSetter(OGRLineString* poLS) : poLS(poLS) {}

        void set(int i, double dfZ)
        {
            poLS->setZ(i, dfZ);
        }
};

/************************************************************************/
/*                         ZMultiPointSetter                           */
/************************************************************************/

class ZMultiPointSetter
{
        OGRMultiPoint* poMPoint;
    public:
        ZMultiPointSetter(OGRMultiPoint* poMPoint) : poMPoint(poMPoint) {}

        void set(int i, double dfZ)
        {
            ((OGRPoint*)poMPoint->getGeometryRef(i))->setZ(dfZ);
        }
};

/************************************************************************/
/*                             ZArraySetter                            */
/************************************************************************/

class ZArraySetter
{
        double* padfZ;
    public:
        ZArraySetter(double* padfZ) : padfZ(padfZ) {}

        void set(int i, double dfZ)
        {
            padfZ[i] = dfZ;
        }
};

/************************************************************************/
/*                          ReadZArray()                                */
/************************************************************************/

template <class ZSetter> int FileGDBOGRGeometryConverterImpl::ReadZArray(ZSetter& setter,
                                                      GByte*& pabyCur,
                                                      GByte* pabyEnd,
                                                      GUInt32 nPoints,
                                                      GIntBig& dz)
{
    const int errorRetValue = FALSE;
    for(GUInt32 i = 0; i < nPoints; i++ )
    {
        returnErrorIf(pabyCur >= pabyEnd);
        ReadVarIntAndAddNoCheck(pabyCur, dz);

        double dfZ = dz / poGeomField->GetZScale() + poGeomField->GetZOrigin();
        setter.set(i, dfZ);
    }
    return TRUE;
}

/************************************************************************/
/*                          GetAsGeometry()                             */
/************************************************************************/

OGRGeometry* FileGDBOGRGeometryConverterImpl::GetAsGeometry(const OGRField* psField)
{
    OGRGeometry* errorRetValue = NULL;
    GByte* pabyCur = psField->Binary.paData;
    GByte* pabyEnd = pabyCur + psField->Binary.nCount;
    GUInt32 nGeomType, i, nPoints, nParts;
    GUIntBig x, y, z;
    GIntBig dx, dy, dz;

    ReadVarUInt32NoCheck(pabyCur, nGeomType);

    int bHasZ = (nGeomType & 0x80000000) != 0;
    switch( (nGeomType & 0xff) )
    {
        case SHPT_NULL:
            return NULL;

        case SHPT_POINTZ:
        case SHPT_POINTZM:
            bHasZ = TRUE; /* go on */
        case SHPT_POINT:
        case SHPT_POINTM:
        {
            double dfX, dfY, dfZ;
            ReadVarUInt64NoCheck(pabyCur, x);
            ReadVarUInt64NoCheck(pabyCur, y);

            dfX = (x - 1) / poGeomField->GetXYScale() + poGeomField->GetXOrigin();
            dfY = (y - 1) / poGeomField->GetXYScale() + poGeomField->GetYOrigin();
            if( bHasZ )
            {
                ReadVarUInt64NoCheck(pabyCur, z);
                dfZ = (z - 1) / poGeomField->GetZScale() + poGeomField->GetZOrigin();
                return new OGRPoint(dfX, dfY, dfZ);
            }
            else
            {
                return new OGRPoint(dfX, dfY);
            }
            break;
        }

        case SHPT_MULTIPOINTZM:
        case SHPT_MULTIPOINTZ:
            bHasZ = TRUE; /* go on */
        case SHPT_MULTIPOINT:
        case SHPT_MULTIPOINTM:
        {
            returnErrorIf(!ReadVarUInt32(pabyCur, pabyEnd, nPoints) );
            if( nPoints == 0 )
            {
                OGRMultiPoint* poMP = new OGRMultiPoint();
                if( bHasZ )
                    poMP->setCoordinateDimension(3);
                return poMP;
            }

            returnErrorIf(!SkipVarUInt(pabyCur, pabyEnd, 4) );

            dx = dy = dz = 0;

            OGRMultiPoint* poMP = new OGRMultiPoint();
            XYMultiPointSetter mpSetter(poMP);
            if( !ReadXYArray<XYMultiPointSetter>(mpSetter,
                             pabyCur, pabyEnd, nPoints, dx, dy) )
            {
                delete poMP;
                returnError();
            }

            if( bHasZ )
            {
                poMP->setCoordinateDimension(3);
                ZMultiPointSetter mpzSetter(poMP);
                if( !ReadZArray<ZMultiPointSetter>(mpzSetter,
                                pabyCur, pabyEnd, nPoints, dz) )
                {
                    delete poMP;
                    returnError();
                }
            }

            return poMP;
            break;
        }

        case SHPT_ARCZ:
        case SHPT_ARCZM:
            bHasZ = TRUE; /* go on */
        case SHPT_ARC:
        case SHPT_ARCM:
        case SHPT_GENERALPOLYLINE:
        {
            returnErrorIf(!ReadPartDefs(pabyCur, pabyEnd, nPoints, nParts,
                              (nGeomType & 0x20000000) != 0,
                              FALSE) );

            if( nPoints == 0 || nParts == 0 )
            {
                OGRLineString* poLS = new OGRLineString();
                if( bHasZ )
                    poLS->setCoordinateDimension(3);
                return poLS;
            }

            OGRMultiLineString* poMLS = NULL;
            FileGDBOGRLineString* poLS = NULL;
            if( nParts > 1 )
                poMLS = new OGRMultiLineString();

            dx = dy = dz = 0;
            for(i=0;i<nParts;i++)
            {
                poLS = new FileGDBOGRLineString();
                poLS->setNumPoints(panPointCount[i], FALSE);
                if( nParts > 1 )
                    poMLS->addGeometryDirectly(poLS);

                XYLineStringSetter lsSetter(poLS->GetPoints());
                if( !ReadXYArray<XYLineStringSetter>(lsSetter,
                                 pabyCur, pabyEnd,
                                 panPointCount[i],
                                 dx, dy) )
                {
                    if( nParts > 1 )
                        delete poMLS;
                    else
                        delete poLS;
                    returnError();
                }
            }

            if( bHasZ )
            {
                for(i=0;i<nParts;i++)
                {
                    if( nParts > 1 )
                        poLS = (FileGDBOGRLineString*) poMLS->getGeometryRef(i);

                    ZLineStringSetter lszSetter(poLS);
                    if( !ReadZArray<ZLineStringSetter>(lszSetter,
                                    pabyCur, pabyEnd,
                                    panPointCount[i], dz) )
                    {
                        if( nParts > 1 )
                            delete poMLS;
                        else
                            delete poLS;
                        returnError();
                    }
                }
            }

            if( poMLS )
                return poMLS;
            else
                return poLS;

            break;
        }

        case SHPT_POLYGONZ:
        case SHPT_POLYGONZM:
            bHasZ = TRUE; /* go on */
        case SHPT_POLYGON:
        case SHPT_POLYGONM:
        case SHPT_GENERALPOLYGON:
        {
            returnErrorIf(!ReadPartDefs(pabyCur, pabyEnd, nPoints, nParts,
                              (nGeomType & 0x20000000) != 0,
                              FALSE) );

            if( nPoints == 0 || nParts == 0 )
            {
                OGRPolygon* poPoly = new OGRPolygon();
                if( bHasZ )
                    poPoly->setCoordinateDimension(3);
                return poPoly;
            }

            OGRLinearRing** papoRings = new OGRLinearRing*[nParts];

            dx = dy = dz = 0;
            for(i=0;i<nParts;i++)
            {
                FileGDBOGRLinearRing* poRing = new FileGDBOGRLinearRing();
                papoRings[i] = poRing;
                poRing->setNumPoints(panPointCount[i], FALSE);

                XYLineStringSetter lsSetter(poRing->GetPoints());
                if( !ReadXYArray<XYLineStringSetter>(lsSetter,
                                 pabyCur, pabyEnd,
                                 panPointCount[i],
                                 dx, dy) )
                {
                    while( (int)i >= 0 )
                        delete papoRings[i--];
                    delete[] papoRings;
                    returnError();
                }
            }

            if( bHasZ )
            {
                for(i=0;i<nParts;i++)
                {
                    papoRings[i]->setCoordinateDimension(3);

                    ZLineStringSetter lszSetter(papoRings[i]);
                    if( !ReadZArray<ZLineStringSetter>(lszSetter,
                                    pabyCur, pabyEnd,
                                    panPointCount[i], dz) )
                    {
                        while( (int)i >= 0 )
                            delete papoRings[i--];
                        delete[] papoRings;
                        returnError();
                    }
                }
            }

            OGRGeometry* poRet;
            if( nParts == 1 )
            {
                OGRPolygon* poPoly = new OGRPolygon();
                poRet = poPoly;
                poPoly->addRingDirectly(papoRings[0]);
            }
            else
#ifdef ASSUME_INNER_RINGS_IMMEDIATELY_AFTER_OUTER_RING
                if( bUseOrganize || !(papoRings[0]->isClockwise()) )
#endif
            {
                /* Slow method : not used by default */
                OGRPolygon** papoPolygons = new OGRPolygon*[nParts];
                for(i=0;i<nParts;i++)
                {
                    papoPolygons[i] = new OGRPolygon();
                    papoPolygons[i]->addRingDirectly(papoRings[i]);
                }
                delete[] papoRings;
                papoRings = NULL;
                const char* papszOptions[] = { "METHOD=ONLY_CCW", NULL };
                poRet = OGRGeometryFactory::organizePolygons(
                    (OGRGeometry**) papoPolygons, nParts, NULL, papszOptions );
                delete[] papoPolygons;
            }
#ifdef ASSUME_INNER_RINGS_IMMEDIATELY_AFTER_OUTER_RING
            else
            {
                /* Inner rings are CCW oriented and follow immediately the outer */
                /* ring (that is CW oriented) in which they are included */
                OGRMultiPolygon* poMulti = NULL;
                OGRPolygon* poPoly = new OGRPolygon();
                OGRPolygon* poCur = poPoly;
                poRet = poCur;
                /* We have already checked that the first ring is CW */
                poPoly->addRingDirectly(papoRings[0]);
                OGREnvelope sEnvelope;
                papoRings[0]->getEnvelope(&sEnvelope);
                for(i=1;i<nParts;i++)
                {
                    int bIsCW = papoRings[i]->isClockwise();
                    if( bIsCW )
                    {
                        if( poMulti == NULL )
                        {
                            poMulti = new OGRMultiPolygon();
                            poRet = poMulti;
                            poMulti->addGeometryDirectly(poCur);
                        }
                        OGRPolygon* poPoly = new OGRPolygon();
                        poCur = poPoly;
                        poMulti->addGeometryDirectly(poCur);
                        poPoly->addRingDirectly(papoRings[i]);
                        papoRings[i]->getEnvelope(&sEnvelope);
                    }
                    else
                    {
                        poCur->addRingDirectly(papoRings[i]);
                        OGRPoint oPoint;
                        papoRings[i]->getPoint(0, &oPoint);
                        CPLAssert(oPoint.getX() >= sEnvelope.MinX &&
                                  oPoint.getX() <= sEnvelope.MaxX &&
                                  oPoint.getY() >= sEnvelope.MinY &&
                                  oPoint.getY() <= sEnvelope.MaxY);
                    }
                }
            }
#endif

            delete[] papoRings;
            return poRet;

            break;
        }

        case SHPT_MULTIPATCHM:
        case SHPT_MULTIPATCH:
            bHasZ = TRUE; /* go on */
        case SHPT_GENERALMULTIPATCH:
        {
            returnErrorIf(!ReadPartDefs(pabyCur, pabyEnd, nPoints, nParts, FALSE, TRUE ) );

            if( nPoints == 0 || nParts == 0 )
            {
                OGRPolygon* poPoly = new OGRPolygon();
                if( bHasZ )
                    poPoly->setCoordinateDimension(3);
                return poPoly;
            }
            int* panPartType = (int*) VSIMalloc(sizeof(int) * nParts);
            double* padfXYZ =  (double*) VSIMalloc(3 * sizeof(double) * nPoints);
            double* padfX = padfXYZ;
            double* padfY = padfXYZ + nPoints;
            double* padfZ = padfXYZ + 2 * nPoints;
            if( panPartType == NULL || padfXYZ == NULL  )
            {
                VSIFree(panPartType);
                VSIFree(padfXYZ);
                returnError();
            }
            for(i=0;i<nParts;i++)
            {
                GUInt32 nPartType;
                if( !ReadVarUInt32(pabyCur, pabyEnd, nPartType) )
                {
                    VSIFree(panPartType);
                    VSIFree(padfXYZ);
                    returnError();
                }
                panPartType[i] = (int)nPartType;
            }
            dx = dy = dz = 0;

            XYArraySetter arraySetter(padfX, padfY);
            if( !ReadXYArray<XYArraySetter>(arraySetter,
                             pabyCur, pabyEnd, nPoints, dx, dy) )
            {
                VSIFree(panPartType);
                VSIFree(padfXYZ);
                returnError();
            }

            if( bHasZ )
            {
                ZArraySetter arrayzSetter(padfZ);
                if( !ReadZArray<ZArraySetter>(arrayzSetter,
                                pabyCur, pabyEnd, nPoints, dz) )
                {
                    VSIFree(panPartType);
                    VSIFree(padfXYZ);
                    returnError();
                }
            }
            else
            {
                memset(padfZ, 0, nPoints * sizeof(double));
            }

            OGRMultiPolygon* poMP = new OGRMultiPolygon();
            OGRPolygon* poLastPoly = NULL;
            int iAccPoints = 0;
            for(i=0;i<nParts;i++)
            {
                OGRCreateFromMultiPatchPart(poMP, poLastPoly,
                                            panPartType[i],
                                            (int) panPointCount[i],
                                            padfX + iAccPoints,
                                            padfY + iAccPoints,
                                            padfZ + iAccPoints);
                iAccPoints += (int) panPointCount[i];
            }

            if( poLastPoly != NULL )
            {
                poMP->addGeometryDirectly( poLastPoly );
                poLastPoly = NULL;
            }

            VSIFree(panPartType);
            VSIFree(padfXYZ);

            return poMP;

            break;
        }

        default:
            CPLDebug("OpenFileGDB", "Unhandled geometry type = %d", (int)nGeomType);
            break;
/*
#define SHPT_GENERALPOINT       52
#define SHPT_GENERALMULTIPOINT  53
*/
    }
    return NULL;
}


/************************************************************************/
/*                           BuildConverter()                           */
/************************************************************************/

FileGDBOGRGeometryConverter* FileGDBOGRGeometryConverter::BuildConverter(
                                        const FileGDBGeomField* poGeomField)
{
    return new FileGDBOGRGeometryConverterImpl(poGeomField);
}

/************************************************************************/
/*                      GetGeometryTypeFromESRI()                       */
/************************************************************************/

static const struct
{
    const char          *pszStr;
    OGRwkbGeometryType   eType;
} AssocESRIGeomTypeToOGRGeomType[] =
{
    { "esriGeometryPoint", wkbPoint },
    { "esriGeometryMultipoint", wkbMultiPoint },
    { "esriGeometryLine", wkbMultiLineString },
    { "esriGeometryPolyline", wkbMultiLineString },
    { "esriGeometryPolygon", wkbMultiPolygon },
    { "esriGeometryMultiPatch", wkbMultiPolygon }
};

OGRwkbGeometryType FileGDBOGRGeometryConverter::GetGeometryTypeFromESRI(
                                                    const char* pszESRIType)
{
    for(size_t i=0;i<sizeof(AssocESRIGeomTypeToOGRGeomType)/
                     sizeof(AssocESRIGeomTypeToOGRGeomType[0]);i++)
    {
        if( strcmp(pszESRIType, AssocESRIGeomTypeToOGRGeomType[i].pszStr) == 0 )
            return AssocESRIGeomTypeToOGRGeomType[i].eType;
    }
    CPLDebug("OpenFileGDB", "Unhandled geometry type : %s", pszESRIType);
    return wkbUnknown;
}

}; /* namespace OpenFileGDB */
