/******************************************************************************
 * $Id: ogrbnalayer.cpp
 *
 * Project:  BNA Translator
 * Purpose:  Implements OGRBNALayer class.
 * Author:   Even Rouault, even dot rouault at mines dash paris dot org
 *
 ******************************************************************************
 * Copyright (c) 2007, Even Rouault
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "ogr_bna.h"
#include "cpl_conv.h"
#include "cpl_string.h"
#include "cpl_csv.h"

#ifndef M_PI
# define M_PI  3.1415926535897932384626433832795
#endif

/************************************************************************/
/*                            OGRBNALayer()                             */
/*                                                                      */
/*      Note that the OGRBNALayer assumes ownership of the passed       */
/*      file pointer.                                                   */
/************************************************************************/

OGRBNALayer::OGRBNALayer( const char *pszFilename,
                          const char* layerName,
                          BNAFeatureType bnaFeatureType,
                          OGRwkbGeometryType eLayerGeomType,
                          int bWriter,
                          OGRBNADataSource* poDS,
                          int nIDs)

{
    eof = FALSE;
    failed = FALSE;
    curLine = 0;
    nNextFID = 0;
    
    this->bWriter = bWriter;
    this->poDS = poDS;
    this->nIDs = nIDs;

    nFeatures = 0;
    partialIndexTable = TRUE;
    offsetAndLineFeaturesTable = NULL;

    const char* iKnowHowToCount[] = { "Primary", "Secondary", "Third", "Fourth", "Fifth" };
    char tmp[32];

    poFeatureDefn = new OGRFeatureDefn( CPLSPrintf("%s_%s", 
                                                   CPLGetBasename( pszFilename ) , 
                                                   layerName ));
    poFeatureDefn->Reference();
    poFeatureDefn->SetGeomType( eLayerGeomType );
    this->bnaFeatureType = bnaFeatureType;

    if (! bWriter )
    {
        int i;
        for(i=0;i<nIDs;i++)
        {
            if (i < (int) (sizeof(iKnowHowToCount)/sizeof(iKnowHowToCount[0])) )
            {
                sprintf(tmp, "%s ID", iKnowHowToCount[i]);
                OGRFieldDefn oFieldID(tmp, OFTString );
                poFeatureDefn->AddFieldDefn( &oFieldID );
            }
            else
            {
                sprintf(tmp, "%dth ID", i+1);
                OGRFieldDefn oFieldID(tmp, OFTString );
                poFeatureDefn->AddFieldDefn( &oFieldID );
            }
        }

        if (bnaFeatureType == BNA_ELLIPSE)
        {
            OGRFieldDefn oFieldMajorRadius( "Major radius", OFTReal );
            poFeatureDefn->AddFieldDefn( &oFieldMajorRadius );

            OGRFieldDefn oFieldMinorRadius( "Minor radius", OFTReal );
            poFeatureDefn->AddFieldDefn( &oFieldMinorRadius );
        }

        fpBNA = VSIFOpen( pszFilename, "rb" );
        if( fpBNA == NULL )
            return;
    }
    else
    {
        fpBNA = NULL;
    }
}

/************************************************************************/
/*                            ~OGRBNALayer()                            */
/************************************************************************/

OGRBNALayer::~OGRBNALayer()

{
    poFeatureDefn->Release();

    CPLFree(offsetAndLineFeaturesTable);

    if (fpBNA)
        VSIFClose( fpBNA );
}

/************************************************************************/
/*                         SetFeatureIndexTable()                       */
/************************************************************************/
void  OGRBNALayer::SetFeatureIndexTable(int nFeatures, OffsetAndLine* offsetAndLineFeaturesTable, int partialIndexTable)
{
    this->nFeatures = nFeatures;
    this->offsetAndLineFeaturesTable = offsetAndLineFeaturesTable;
    this->partialIndexTable = partialIndexTable;
}

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

void OGRBNALayer::ResetReading()

{
    eof = FALSE;
    failed = FALSE;
    curLine = 0;
    nNextFID = 0;
    VSIFSeek( fpBNA, 0, SEEK_SET );
}


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

OGRFeature *OGRBNALayer::GetNextFeature()
{
    OGRFeature  *poFeature;
    BNARecord* record;
    int offset, line;

    if (failed || eof) return NULL;

    while(1)
    {
        int ok = FALSE;
        offset = VSIFTell(fpBNA);
        line = curLine;
        if (nNextFID < nFeatures)
        {
            VSIFSeek( fpBNA, offsetAndLineFeaturesTable[nNextFID].offset, SEEK_SET );
            curLine = offsetAndLineFeaturesTable[nNextFID].line;
        }
        record =  BNA_GetNextRecord(fpBNA, &ok, &curLine, TRUE, bnaFeatureType);
        if (ok == FALSE)
        {
            BNA_FreeRecord(record);
            failed = TRUE;
            return NULL;
        }
        if (record == NULL)
        {
            /* end of file */
            eof = TRUE;

            /* and we have finally build the whole index table */
            partialIndexTable = FALSE;
            return NULL;
        }

        if (record->featureType == bnaFeatureType)
        {
            if (nNextFID >= nFeatures)
            {
                nFeatures++;
                offsetAndLineFeaturesTable =
                    (OffsetAndLine*)CPLRealloc(offsetAndLineFeaturesTable, nFeatures * sizeof(OffsetAndLine));
                offsetAndLineFeaturesTable[nFeatures-1].offset = offset;
                offsetAndLineFeaturesTable[nFeatures-1].line = line;
            }

            poFeature = BuildFeatureFromBNARecord(record, nNextFID++);

            BNA_FreeRecord(record);

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

            delete poFeature;
        }
        else
        {
            BNA_FreeRecord(record);
        }
    }
}


void OGRBNALayer::WriteFeatureAttributes(FILE* fp, OGRFeature *poFeature )
{
    int i;
    OGRFieldDefn *poField;
    int nbOutID = poDS->GetNbOutId();
    if (nbOutID < 0)
        nbOutID = poFeatureDefn->GetFieldCount();
    for(i=0;i<nbOutID;i++)
    { 
        if (i < poFeatureDefn->GetFieldCount())
        {
            poField = poFeatureDefn->GetFieldDefn( i );
            if( poFeature->IsFieldSet( i ) )
            {
                const char *pszRaw = poFeature->GetFieldAsString( i );
                VSIFPrintf( fp, "\"%s\",", pszRaw);
            }
            else
            {
                VSIFPrintf( fp, "\"\",");
            }
        }
        else
        {
            VSIFPrintf( fp, "\"\",");
        }
    }
}

/************************************************************************/
/*                           CreateFeature()                            */
/************************************************************************/

OGRErr OGRBNALayer::CreateFeature( OGRFeature *poFeature )

{
    int i,j,k,n;
    OGRGeometry     *poGeom = poFeature->GetGeometryRef();
    char eol[3];
    const char* partialEol = (poDS->GetMultiLine()) ? eol : poDS->GetCoordinateSeparator();

    if (poGeom == NULL || poGeom->IsEmpty() )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "OGR BNA driver cannot write features with empty geometries.");
        return OGRERR_FAILURE;
    }

    if (poDS->GetUseCRLF())
    {
        eol[0] = 13;
        eol[1] = 10;
        eol[2] = 0;
    }
    else
    {
        eol[0] = 10;
        eol[1] = 0;
    }
    
    if ( ! bWriter )
    {
        return OGRERR_FAILURE;
    }
    
    if( poFeature->GetFID() == OGRNullFID )
        poFeature->SetFID( nFeatures++ );
    
    FILE* fp = poDS->GetOutputFP();
    int nbPairPerLine = poDS->GetNbPairPerLine();
    char formatCoordinates[32];
    sprintf(formatCoordinates, "%%s%%.%df%s%%.%df",
            poDS->GetCoordinatePrecision(), poDS->GetCoordinateSeparator(), poDS->GetCoordinatePrecision());

    switch( poGeom->getGeometryType() )
    {
        case wkbPoint:
        case wkbPoint25D:
        {
            OGRPoint* point = (OGRPoint*)poGeom;
            WriteFeatureAttributes(fp, poFeature);
            VSIFPrintf( fp, "1");
            VSIFPrintf( fp, formatCoordinates, partialEol, point->getX(), point->getY());
            VSIFPrintf( fp, "%s", eol);
            break;
        }
            
        case wkbPolygon:
        case wkbPolygon25D:
        {
            OGRPolygon* polygon = (OGRPolygon*)poGeom;
            OGRLinearRing* ring = polygon->getExteriorRing();
            if (ring == NULL)
            {
                return OGRERR_FAILURE;
            }
            
            double firstX = ring->getX(0);
            double firstY = ring->getY(0);
            int nBNAPoints = ring->getNumPoints();
            int is_ellipse = FALSE;
            
            /* This code tries to detect an ellipse in a polygon geometry */
            /* This will only work presumably on ellipses already read from a BNA file */
            /* Mostly a BNA to BNA feature... */
            if (poDS->GetEllipsesAsEllipses() &&
                polygon->getNumInteriorRings() == 0 &&
                nBNAPoints == 361)
            {
                double oppositeX = ring->getX(180);
                double oppositeY = ring->getY(180);
                double quarterX = ring->getX(90);
                double quarterY = ring->getY(90);
                double antiquarterX = ring->getX(270);
                double antiquarterY = ring->getY(270);
                double center1X = 0.5*(firstX + oppositeX);
                double center1Y = 0.5*(firstY + oppositeY);
                double center2X = 0.5*(quarterX + antiquarterX);
                double center2Y = 0.5*(quarterY + antiquarterY);
                if (fabs(center1X - center2X) < 1e-5 && fabs(center1Y - center2Y) < 1e-5 &&
                    fabs(oppositeY - firstY) < 1e-5 &&
                    fabs(quarterX - antiquarterX) < 1e-5)
                {
                    double major_radius = fabs(firstX - center1X);
                    double minor_radius = fabs(quarterY - center1Y);
                    is_ellipse = TRUE;
                    for(i=0;i<360;i++)
                    {
                        if (!(fabs(center1X + major_radius * cos(i * (M_PI / 180)) - ring->getX(i)) < 1e-5 &&
                              fabs(center1Y + minor_radius * sin(i * (M_PI / 180)) - ring->getY(i)) < 1e-5))
                        {
                            is_ellipse = FALSE;
                            break;
                        }
                    }
                    if ( is_ellipse == TRUE )
                    {
                        WriteFeatureAttributes(fp, poFeature);
                        VSIFPrintf( fp, "2");
                        VSIFPrintf( fp, formatCoordinates, partialEol, center1X, center1Y);
                        VSIFPrintf( fp, formatCoordinates,  partialEol, major_radius, minor_radius);
                        VSIFPrintf( fp, "%s", eol);
                    }
                }
            }

            if ( is_ellipse == FALSE)
            {
                int nInteriorRings = polygon->getNumInteriorRings();
                for(i=0;i<nInteriorRings;i++)
                {
                    nBNAPoints += polygon->getInteriorRing(i)->getNumPoints() + 1;
                }
                if (nBNAPoints <= 3)
                {
                    CPLError( CE_Failure, CPLE_AppDefined, "Invalid geometry" );
                    return OGRERR_FAILURE;
                }
                WriteFeatureAttributes(fp, poFeature);
                VSIFPrintf( fp, "%d", nBNAPoints);
                n = ring->getNumPoints();
                int nbPair = 0;
                for(i=0;i<n;i++)
                {
                    VSIFPrintf( fp, formatCoordinates,
                                ((nbPair % nbPairPerLine) == 0) ? partialEol : " ", ring->getX(i), ring->getY(i));
                    nbPair++;
                }
                for(i=0;i<nInteriorRings;i++)
                {
                    ring = polygon->getInteriorRing(i);
                    n = ring->getNumPoints();
                    for(j=0;j<n;j++)
                    {
                        VSIFPrintf( fp, formatCoordinates,
                                    ((nbPair % nbPairPerLine) == 0) ? partialEol : " ", ring->getX(j), ring->getY(j));
                        nbPair++;
                    }
                    VSIFPrintf( fp, formatCoordinates,
                                ((nbPair % nbPairPerLine) == 0) ? partialEol : " ", firstX, firstY);
                    nbPair++;
                }
                VSIFPrintf( fp, "%s", eol);
            }
            break;
        }

        case wkbMultiPolygon:
        case wkbMultiPolygon25D:
        {
            OGRMultiPolygon* multipolygon = (OGRMultiPolygon*)poGeom;
            int N = multipolygon->getNumGeometries();
            int nBNAPoints = 0;
            double firstX = 0, firstY = 0; 
            for(i=0;i<N;i++)
            {
                OGRPolygon* polygon = (OGRPolygon*)multipolygon->getGeometryRef(i);
                OGRLinearRing* ring = polygon->getExteriorRing();
                if (ring == NULL)
                    continue;

                if (nBNAPoints)
                    nBNAPoints ++;
                else
                {
                    firstX = ring->getX(0);
                    firstY = ring->getY(0);
                }
                nBNAPoints += ring->getNumPoints();
                int nInteriorRings = polygon->getNumInteriorRings();
                for(j=0;j<nInteriorRings;j++)
                {
                    nBNAPoints += polygon->getInteriorRing(j)->getNumPoints() + 1;
                }
            }
            if (nBNAPoints <= 3)
            {
                CPLError( CE_Failure, CPLE_AppDefined, "Invalid geometry" );
                return OGRERR_FAILURE;
            }
            WriteFeatureAttributes(fp, poFeature);
            VSIFPrintf( fp, "%d", nBNAPoints);
            int nbPair = 0;
            for(i=0;i<N;i++)
            {
                OGRPolygon* polygon = (OGRPolygon*)multipolygon->getGeometryRef(i);
                OGRLinearRing* ring = polygon->getExteriorRing();
                if (ring == NULL)
                    continue;

                n = ring->getNumPoints();
                int nInteriorRings = polygon->getNumInteriorRings();
                for(j=0;j<n;j++)
                {
                    VSIFPrintf( fp, formatCoordinates,
                                ((nbPair % nbPairPerLine) == 0) ? partialEol : " ",ring->getX(j), ring->getY(j));
                    nbPair++;
                }
                if (i != 0)
                {
                    VSIFPrintf( fp, formatCoordinates,
                                ((nbPair % nbPairPerLine) == 0) ? partialEol : " ",firstX, firstY);
                    nbPair++;
                }
                for(j=0;j<nInteriorRings;j++)
                {
                    ring = polygon->getInteriorRing(j);
                    n = ring->getNumPoints();
                    for(k=0;k<n;k++)
                    {
                        VSIFPrintf( fp, formatCoordinates, 
                                    ((nbPair % nbPairPerLine) == 0) ? partialEol : " ", ring->getX(k), ring->getY(k));
                        nbPair++;
                    }
                    VSIFPrintf( fp, formatCoordinates,
                                ((nbPair % nbPairPerLine) == 0) ? partialEol : " ", firstX, firstY);
                    nbPair++;
                }
            }
            VSIFPrintf( fp, "%s", eol);
            break;
        }

        case wkbLineString:
        case wkbLineString25D:
        {
            OGRLineString* line = (OGRLineString*)poGeom;
            int n = line->getNumPoints();
            int i;
            if (n < 2)
            {
                CPLError( CE_Failure, CPLE_AppDefined, "Invalid geometry" );
                return OGRERR_FAILURE;
            }
            WriteFeatureAttributes(fp, poFeature);
            VSIFPrintf( fp, "-%d", n);
            int nbPair = 0;
            for(i=0;i<n;i++)
            {
                VSIFPrintf( fp, formatCoordinates,
                            ((nbPair % nbPairPerLine) == 0) ? partialEol : " ", line->getX(i), line->getY(i));
                nbPair++;
            }
            VSIFPrintf( fp, "%s", eol);
            break;
        }
            
        default:
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Unsupported geometry type : %s.",
                      poGeom->getGeometryName() );

            return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
        }
    }
    
    return OGRERR_NONE;
}



/************************************************************************/
/*                            CreateField()                             */
/************************************************************************/

OGRErr OGRBNALayer::CreateField( OGRFieldDefn *poField, int bApproxOK )

{
    if( !bWriter || nFeatures != 0)
        return OGRERR_FAILURE;

    poFeatureDefn->AddFieldDefn( poField );

    return OGRERR_NONE;
}


/************************************************************************/
/*                           BuildFeatureFromBNARecord()                */
/************************************************************************/
OGRFeature *    OGRBNALayer::BuildFeatureFromBNARecord (BNARecord* record, long fid)
{
    OGRFeature  *poFeature;
    int i;

    poFeature = new OGRFeature( poFeatureDefn );
    for(i=0;i<nIDs;i++)
    {
        poFeature->SetField( i, record->ids[i] ? record->ids[i] : "");
    }
    poFeature->SetFID( fid );
    if (bnaFeatureType == BNA_POINT)
    {
        poFeature->SetGeometryDirectly( new OGRPoint( record->tabCoords[0][0], record->tabCoords[0][1] ) );
    }
    else if (bnaFeatureType == BNA_POLYLINE)
    {
        OGRLineString* lineString = new OGRLineString ();
        lineString->setCoordinateDimension(2);
        lineString->setNumPoints(record->nCoords);
        for(i=0;i<record->nCoords;i++)
        {
            lineString->setPoint(i, record->tabCoords[i][0], record->tabCoords[i][1] );
        }
        poFeature->SetGeometryDirectly(lineString);
    }
    else if (bnaFeatureType == BNA_POLYGON)
    {
        double firstX = record->tabCoords[0][0];
        double firstY = record->tabCoords[0][1];
        int isFirstPolygon = 1;
        double secondaryFirstX = 0, secondaryFirstY = 0;
  
        OGRLinearRing* ring = new OGRLinearRing ();
        ring->setCoordinateDimension(2);
        ring->addPoint(record->tabCoords[0][0], record->tabCoords[0][1] );
  
        /* record->nCoords is really a safe upper bound */
        int nbPolygons = 0;
        OGRPolygon** tabPolygons =
            (OGRPolygon**)CPLMalloc(record->nCoords * sizeof(OGRPolygon*));

        for(i=1;i<record->nCoords;i++)
        {
            ring->addPoint(record->tabCoords[i][0], record->tabCoords[i][1] );
            if (isFirstPolygon == 1 &&
                record->tabCoords[i][0] == firstX &&
                record->tabCoords[i][1] == firstY)
            {
                OGRPolygon* polygon = new OGRPolygon ();
                polygon->addRingDirectly(ring);
                tabPolygons[nbPolygons] = polygon;
                nbPolygons++;
    
                if (i == record->nCoords - 1)
                {
                    break;
                }
    
                isFirstPolygon = 0;
    
                i ++;
                secondaryFirstX = record->tabCoords[i][0];
                secondaryFirstY = record->tabCoords[i][1];
                ring = new OGRLinearRing ();
                ring->setCoordinateDimension(2);
                ring->addPoint(record->tabCoords[i][0], record->tabCoords[i][1] );
            }
            else if (isFirstPolygon == 0 &&
                    record->tabCoords[i][0] == secondaryFirstX &&
                    record->tabCoords[i][1] == secondaryFirstY)
            {

                OGRPolygon* polygon = new OGRPolygon ();
                polygon->addRingDirectly(ring);
                tabPolygons[nbPolygons] = polygon;
                nbPolygons++;

                if (i < record->nCoords - 1)
                {
                    /* After the closing of a subpolygon, the first coordinates of the first polygon */
                    /* should be recalled... in theory */
                    if (record->tabCoords[i+1][0] == firstX &&  record->tabCoords[i+1][1] == firstY)
                    {
                        if (i + 1 == record->nCoords - 1)
                            break;
                        i ++;
                    }
                    else
                    {
#if 0
                        CPLError(CE_Warning, CPLE_AppDefined, 
                                 "Geometry of polygon of fid %d starting at line %d is not strictly conformant. "
                                 "Trying to go on...\n",
                                 fid,
                                 offsetAndLineFeaturesTable[fid].line + 1);
#endif
                    }

                    i ++;
                    secondaryFirstX = record->tabCoords[i][0];
                    secondaryFirstY = record->tabCoords[i][1];
                    ring = new OGRLinearRing ();
                    ring->setCoordinateDimension(2);
                    ring->addPoint(record->tabCoords[i][0], record->tabCoords[i][1] );
                }
                else
                {
#if 0
                    CPLError(CE_Warning, CPLE_AppDefined, 
                        "Geometry of polygon of fid %d starting at line %d is not strictly conformant. Trying to go on...\n",
                        fid,
                        offsetAndLineFeaturesTable[fid].line + 1);
#endif
                }
            }
        }
        if (i == record->nCoords)
        {
            /* Let's be a bit tolerant abount non closing polygons */
            if (isFirstPolygon)
            {
                ring->addPoint(record->tabCoords[0][0], record->tabCoords[0][1] );

                OGRPolygon* polygon = new OGRPolygon ();
                polygon->addRingDirectly(ring);
                tabPolygons[nbPolygons] = polygon;
                nbPolygons++;
            }
        }
        
        if (nbPolygons == 1)
        {
            /* Special optimization here : we directly put the polygon into the multipolygon. */
            /* This should save quite a few useless copies */
            OGRMultiPolygon* multipolygon = new OGRMultiPolygon();
            multipolygon->addGeometryDirectly(tabPolygons[0]);
            poFeature->SetGeometryDirectly(multipolygon);
        }
        else
        {
            int isValidGeometry;
            poFeature->SetGeometryDirectly(
                OGRGeometryFactory::organizePolygons((OGRGeometry**)tabPolygons, nbPolygons, &isValidGeometry, NULL));
            
            if (!isValidGeometry)
            {
                CPLError(CE_Warning, CPLE_AppDefined, 
                        "Geometry of polygon of fid %ld starting at line %d cannot be translated to Simple Geometry. "
                        "All polygons will be contained in a multipolygon.\n",
                        fid,
                        offsetAndLineFeaturesTable[fid].line + 1);
            }
        }

        CPLFree(tabPolygons);
    }
    else
    {
        /* Circle or ellipses are not part of the OGR Simple Geometry, so we discretize them
           into polygons by 1 degree step */
        OGRPolygon* polygon = new OGRPolygon ();
        OGRLinearRing* ring = new OGRLinearRing ();
        ring->setCoordinateDimension(2);
        double center_x = record->tabCoords[0][0];
        double center_y = record->tabCoords[0][1];
        double major_radius = record->tabCoords[1][0];
        double minor_radius = record->tabCoords[1][1];
        if (minor_radius == 0)
            minor_radius = major_radius;
        for(i=0;i<360;i++)
        {
            ring->addPoint(center_x + major_radius * cos(i * (M_PI / 180)),
                           center_y + minor_radius * sin(i * (M_PI / 180)) );
        }
        ring->addPoint(center_x + major_radius, center_y);
        polygon->addRingDirectly  (  ring );
        poFeature->SetGeometryDirectly(polygon);

        poFeature->SetField( nIDs, major_radius);
        poFeature->SetField( nIDs+1, minor_radius);
    }
    
    return poFeature;
}


/************************************************************************/
/*                           FastParseUntil()                           */
/************************************************************************/
void OGRBNALayer::FastParseUntil ( int interestFID)
{
    if (partialIndexTable)
    {
        ResetReading();

        BNARecord* record;

        if (nFeatures > 0)
        {
            VSIFSeek( fpBNA, offsetAndLineFeaturesTable[nFeatures-1].offset, SEEK_SET );
            curLine = offsetAndLineFeaturesTable[nFeatures-1].line;

            /* Just skip the last read one */
            int ok = FALSE;
            record =  BNA_GetNextRecord(fpBNA, &ok, &curLine, TRUE, BNA_READ_NONE);
            BNA_FreeRecord(record);
        }

        while(1)
        {
            int ok = FALSE;
            int offset = VSIFTell(fpBNA);
            int line = curLine;
            record =  BNA_GetNextRecord(fpBNA, &ok, &curLine, TRUE, BNA_READ_NONE);
            if (ok == FALSE)
            {
                failed = TRUE;
                return;
            }
            if (record == NULL)
            {
                /* end of file */
                eof = TRUE;

                /* and we have finally build the whole index table */
                partialIndexTable = FALSE;
                return;
            }

            if (record->featureType == bnaFeatureType)
            {
                nFeatures++;
                offsetAndLineFeaturesTable =
                    (OffsetAndLine*)CPLRealloc(offsetAndLineFeaturesTable, nFeatures * sizeof(OffsetAndLine));
                offsetAndLineFeaturesTable[nFeatures-1].offset = offset;
                offsetAndLineFeaturesTable[nFeatures-1].line = line;

                BNA_FreeRecord(record);

                if (nFeatures - 1 == interestFID)
                  return;
            }
            else
            {
                BNA_FreeRecord(record);
            }
        }
    }
}

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

OGRFeature *  OGRBNALayer::GetFeature( long nFID )
{
    OGRFeature  *poFeature;
    BNARecord* record;
    int ok;
    
    if (nFID < 0)
        return NULL;

    FastParseUntil(nFID);

    if (nFID >= nFeatures)
        return NULL;

    VSIFSeek( fpBNA, offsetAndLineFeaturesTable[nFID].offset, SEEK_SET );
    curLine = offsetAndLineFeaturesTable[nFID].line;
    record =  BNA_GetNextRecord(fpBNA, &ok, &curLine, TRUE, bnaFeatureType);

    poFeature = BuildFeatureFromBNARecord(record, nFID);

    BNA_FreeRecord(record);

    return poFeature;
}

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

int OGRBNALayer::TestCapability( const char * pszCap )

{
    if( EQUAL(pszCap,OLCSequentialWrite) )
        return bWriter;
    else if( EQUAL(pszCap,OLCCreateField) )
        return bWriter && nFeatures == 0;
    else
        return FALSE;
}

