/******************************************************************************
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  Implements OGRIngresLayer class.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2008, Frank Warmerdam <warmerdam@pobox.com>
 *
 * 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_ingres.h"
#include "cpl_conv.h"
#include "cpl_string.h"

CPL_CVSID("$Id: ogringreslayer.cpp 36682 2016-12-04 20:34:45Z rouault $");

/************************************************************************/
/*                           OGRIngresLayer()                            */
/************************************************************************/

OGRIngresLayer::OGRIngresLayer()

{
    poDS = NULL;

    iNextShapeId = 0;
    nResultOffset = 0;

    poSRS = NULL;
    nSRSId = -2; // we haven't even queried the database for it yet.

    poFeatureDefn = NULL;

    poResultSet = NULL;
}

/************************************************************************/
/*                           ~OGRIngresLayer()                           */
/************************************************************************/

OGRIngresLayer::~OGRIngresLayer()

{
    if( m_nFeaturesRead > 0 && poFeatureDefn != NULL )
    {
        CPLDebug( "Ingres", "%d features read on layer '%s'.",
                  (int) m_nFeaturesRead,
                  poFeatureDefn->GetName() );
    }

    ResetReading();

    if( poSRS != NULL )
        poSRS->Release();

    if( poFeatureDefn )
        poFeatureDefn->Release();
}

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

void OGRIngresLayer::ResetReading()

{
    iNextShapeId = 0;

    if( poResultSet != NULL )
    {
        delete poResultSet;
        poResultSet = NULL;
    }
}

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

OGRFeature *OGRIngresLayer::GetNextFeature()

{
    while( true )
    {
        OGRFeature *poFeature = GetNextRawFeature();
        if( poFeature == NULL )
            return NULL;

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

        delete poFeature;
    }
}

/************************************************************************/
/*                              ParseXY()                               */
/************************************************************************/

static bool ParseXY( const char **ppszNext, double *padfXY )

{
    const char *pszNext = *ppszNext;

    int iStartY = 0;  // Used after for.
    for( ; ; iStartY++ )
    {
        if( pszNext[iStartY] == '\0' )
            return false;

        if( pszNext[iStartY] == ',' )
        {
            iStartY++;
            break;
        }
    }

    padfXY[0] = CPLAtof(pszNext);
    padfXY[1] = CPLAtof(pszNext + iStartY);

    int iEnd = iStartY;  // Used after for.
    for( ; pszNext[iEnd] != ')'; iEnd++ )
    {
        if( pszNext[iEnd] == '\0' )
            return false;
    }

    *ppszNext += iEnd;

    return true;
}

/************************************************************************/
/*                         TranslateGeometry()                          */
/*                                                                      */
/*      This currently only supports "old style" ingres geometry in     */
/*      text format.  Essentially tuple lists of vertices.              */
/************************************************************************/

OGRGeometry *OGRIngresLayer::TranslateGeometry( const char *pszGeom )

{
    OGRGeometry *poGeom = NULL;

/* -------------------------------------------------------------------- */
/*      Parse the tuple list into an array of x/y vertices.  The        */
/*      input may look like "(2,3)" or "((2,3),(4,5),...)".  Extra      */
/*      spaces may occur between tokens.                                */
/* -------------------------------------------------------------------- */
    double *padfXY = NULL;
    int nVertMax = 0;
    int nVertCount = 0;
    int nDepth = 0;
    const char *pszNext = pszGeom;

    while( *pszNext != '\0' )
    {
        while( *pszNext == ' ' )
            pszNext++;

        if( *pszNext == '(' )
        {
            pszNext++;
            nDepth++;
            continue;
        }

        if( *pszNext == ')' )
        {
            pszNext++;
            CPLAssert( nDepth == 1 );
            nDepth--;
            break;
        }

        if( *pszNext == ',' )
        {
            pszNext++;
            CPLAssert( nDepth == 1 );
            continue;
        }

        if( nVertCount == nVertMax )
        {
            nVertMax = nVertMax * 2 + 1;
            padfXY = (double *)
                CPLRealloc(padfXY, sizeof(double) * nVertMax * 2 );
        }

        if( !ParseXY( &pszNext, padfXY + nVertCount*2 ) )
        {
            CPLDebug( "INGRES", "Error parsing geometry: %s",
                      pszGeom );
            CPLFree( padfXY );
            return NULL;
        }

        CPLAssert( *pszNext == ')' );
        nVertCount++;
        pszNext++;
        nDepth--;

        while( *pszNext == ' ' )
            pszNext++;
    }

    CPLAssert( nDepth == 0 );

/* -------------------------------------------------------------------- */
/*      Handle Box/IBox.                                                */
/* -------------------------------------------------------------------- */
    if( EQUAL(osIngresGeomType,"BOX")
        || EQUAL(osIngresGeomType,"IBOX") )
    {
        CPLAssert( nVertCount == 2 );

        OGRLinearRing *poRing = new OGRLinearRing();
        poRing->addPoint( padfXY[0], padfXY[1] );
        poRing->addPoint( padfXY[2], padfXY[1] );
        poRing->addPoint( padfXY[2], padfXY[3] );
        poRing->addPoint( padfXY[0], padfXY[3] );
        poRing->addPoint( padfXY[0], padfXY[1] );

        OGRPolygon *poPolygon = new OGRPolygon();
        poPolygon->addRingDirectly( poRing );

        poGeom = poPolygon;
    }

/* -------------------------------------------------------------------- */
/*      Handle Point/IPoint                                             */
/* -------------------------------------------------------------------- */
    else if( EQUAL(osIngresGeomType,"POINT")
             || EQUAL(osIngresGeomType,"IPOINT") )
    {
        CPLAssert( nVertCount == 1 );

        poGeom = new OGRPoint( padfXY[0], padfXY[1] );
    }

/* -------------------------------------------------------------------- */
/*      Handle various linestring types.                                */
/* -------------------------------------------------------------------- */
    else if( EQUAL(osIngresGeomType,"LSEG")
             || EQUAL(osIngresGeomType,"ILSEG")
             || EQUAL(osIngresGeomType,"LINE")
             || EQUAL(osIngresGeomType,"LONG LINE")
             || EQUAL(osIngresGeomType,"ILINE") )
    {
        OGRLineString *poLine = new OGRLineString();
        int iVert;

        poLine->setNumPoints( nVertCount );
        for( iVert = 0; iVert < nVertCount; iVert++ )
            poLine->setPoint( iVert, padfXY[iVert*2+0], padfXY[iVert*2+1] );

        poGeom = poLine;
    }

/* -------------------------------------------------------------------- */
/*      Handle Polygon/IPolygon/LongPolygon.                            */
/* -------------------------------------------------------------------- */
    else if( EQUAL(osIngresGeomType,"POLYGON")
             || EQUAL(osIngresGeomType,"IPOLYGON")
             || EQUAL(osIngresGeomType,"LONG POLYGON") )
    {
        OGRLinearRing *poLine = new OGRLinearRing();
        int iVert;

        poLine->setNumPoints( nVertCount );
        for( iVert = 0; iVert < nVertCount; iVert++ )
            poLine->setPoint( iVert, padfXY[iVert*2+0], padfXY[iVert*2+1] );

        // INGRES polygons are implicitly closed, but OGR expects explicit
        if( poLine->getX(nVertCount-1) != poLine->getX(0)
            || poLine->getY(nVertCount-1) != poLine->getY(0) )
            poLine->addPoint( poLine->getX(0), poLine->getY(0) );

        OGRPolygon *poPolygon = new OGRPolygon();
        poPolygon->addRingDirectly( poLine );
        poGeom = poPolygon;
    }

    return poGeom;
}

/************************************************************************/
/*                          RecordToFeature()                           */
/*                                                                      */
/*      Convert the indicated record of the current result set into     */
/*      a feature.                                                      */
/************************************************************************/

OGRFeature *OGRIngresLayer::RecordToFeature( char **papszRow )

{
/* -------------------------------------------------------------------- */
/*      Create a feature from the current result.                       */
/* -------------------------------------------------------------------- */
    int         iField;
    OGRFeature *poFeature = new OGRFeature( poFeatureDefn );

    poFeature->SetFID( iNextShapeId );
    m_nFeaturesRead++;

/* ==================================================================== */
/*      Transfer all result fields we can.                              */
/* ==================================================================== */
    for( iField = 0;
         iField < (int) poResultSet->getDescrParm.gd_descriptorCount;
         iField++ )
    {
        IIAPI_DATAVALUE *psDV =
            poResultSet->pasDataBuffer + iField;
        IIAPI_DESCRIPTOR *psFDesc =
            poResultSet->getDescrParm.gd_descriptor + iField;
        int     iOGRField;

/* -------------------------------------------------------------------- */
/*      Ignore NULL fields.                                             */
/* -------------------------------------------------------------------- */
        if( psDV->dv_null )
            continue;

/* -------------------------------------------------------------------- */
/*      Handle FID.                                                     */
/* -------------------------------------------------------------------- */
        if( osFIDColumn.size()
            && EQUAL(psFDesc->ds_columnName,osFIDColumn)
            && psFDesc->ds_dataType == IIAPI_INT_TYPE
            && psDV->dv_length == 4 )
        {
            if( papszRow[iField] == NULL )
            {
                CPLError( CE_Failure, CPLE_AppDefined,
                          "NULL primary key in RecordToFeature()" );
                return NULL;
            }

            GInt32 nValue;
            memcpy( &nValue, papszRow[iField], 4 );
            poFeature->SetFID( nValue );
        }

/* -------------------------------------------------------------------- */
/*      Handle Ingres geometry                                           */
/* -------------------------------------------------------------------- */
        if( osGeomColumn.size()
            && EQUAL(psFDesc->ds_columnName,osGeomColumn))
        {
            if( poDS->IsNewIngres() )
            {
                OGRGeometry *poGeometry = NULL;
                unsigned char *pszWKB = (unsigned char *) papszRow[iField];

                // GRGeometryFactory::createFromWkt(&pszWKT, NULL, &poGeometry);
                OGRGeometryFactory::createFromWkb(pszWKB, NULL, &poGeometry, -1);

                poFeature->SetGeometryDirectly(poGeometry);
            }
            else
            {
                poFeature->SetGeometryDirectly(
                    TranslateGeometry( papszRow[iField] ) );
            }
            continue;
        }

/* -------------------------------------------------------------------- */
/*      Transfer regular data fields.                                   */
/* -------------------------------------------------------------------- */
        iOGRField = poFeatureDefn->GetFieldIndex(psFDesc->ds_columnName);
        if( iOGRField < 0 )
            continue;

        switch( psFDesc->ds_dataType )
        {
          case IIAPI_CHR_TYPE:
          case IIAPI_CHA_TYPE:
          case IIAPI_LVCH_TYPE:
          case IIAPI_LTXT_TYPE:
            poFeature->SetField( iOGRField, papszRow[iField] );
            break;

          case IIAPI_VCH_TYPE:
          case IIAPI_TXT_TYPE:
            GUInt16 nLength;
            memcpy( &nLength, papszRow[iField], 2 );
            papszRow[iField][nLength+2] = '\0';
            poFeature->SetField( iOGRField, papszRow[iField]+2 );
            break;

          case IIAPI_INT_TYPE:
            if( psDV->dv_length == 8 )
            {
                GIntBig nValue;
                memcpy( &nValue, papszRow[iField], 8 );
                poFeature->SetField( iOGRField, (int) nValue );
            }
            else if( psDV->dv_length == 4 )
            {
                GInt32 nValue;
                memcpy( &nValue, papszRow[iField], 4 );
                poFeature->SetField( iOGRField, nValue );
            }
            else if( psDV->dv_length == 2 )
            {
                GInt16 nValue;
                memcpy( &nValue, papszRow[iField], 2 );
                poFeature->SetField( iOGRField, nValue );
            }
            else if( psDV->dv_length == 1 )
            {
                GByte nValue;
                memcpy( &nValue, papszRow[iField], 1 );
                poFeature->SetField( iOGRField, nValue );
            }
            break;

          case IIAPI_FLT_TYPE:
            if( psDV->dv_length == 4 )
            {
                float fValue;
                memcpy( &fValue, papszRow[iField], 4 );
                poFeature->SetField( iOGRField, fValue );
            }
            else if( psDV->dv_length == 8 )
            {
                double dfValue;
                memcpy( &dfValue, papszRow[iField], 8 );
                poFeature->SetField( iOGRField, dfValue );
            }
            break;

          case IIAPI_DEC_TYPE:
          {
              IIAPI_CONVERTPARM sCParm;
              char szFormatBuf[30];

              memset( &sCParm, 0, sizeof(sCParm) );

              memcpy( &(sCParm.cv_srcDesc), psFDesc,
                      sizeof(IIAPI_DESCRIPTOR) );
              memcpy( &(sCParm.cv_srcValue), psDV,
                      sizeof(IIAPI_DATAVALUE) );

              sCParm.cv_dstDesc.ds_dataType = IIAPI_CHA_TYPE;
              sCParm.cv_dstDesc.ds_nullable = FALSE;
              sCParm.cv_dstDesc.ds_length = sizeof(szFormatBuf);

              sCParm.cv_dstValue.dv_value = szFormatBuf;

              IIapi_convertData( &sCParm );

              poFeature->SetField( iOGRField, szFormatBuf );
              break;
          }
        }
    }

    return poFeature;
}

/************************************************************************/
/*                         GetNextRawFeature()                          */
/************************************************************************/

OGRFeature *OGRIngresLayer::GetNextRawFeature()

{
/* -------------------------------------------------------------------- */
/*      Do we need to establish an initial query?                       */
/* -------------------------------------------------------------------- */
    if( iNextShapeId == 0 && poResultSet == NULL )
    {
        CPLAssert( !osQueryStatement.empty() );

        poDS->EstablishActiveLayer( this );

        poResultSet = new OGRIngresStatement( poDS->GetConn() );

        if( !poResultSet->ExecuteSQL( osQueryStatement ) )
            return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Fetch next record.                                              */
/* -------------------------------------------------------------------- */
    char **papszRow = poResultSet->GetRow();
    if( papszRow == NULL )
    {
        ResetReading();
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Process record.                                                 */
/* -------------------------------------------------------------------- */
    OGRFeature *poFeature = RecordToFeature( papszRow );

    iNextShapeId++;

    return poFeature;
}

/************************************************************************/
/*                             GetFeature()                             */
/*                                                                      */
/*      Note that we actually override this in OGRIngresTableLayer.      */
/************************************************************************/

OGRFeature *OGRIngresLayer::GetFeature( GIntBig nFeatureId )

{
    return OGRLayer::GetFeature( nFeatureId );
}

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

int OGRIngresLayer::TestCapability( const char * pszCap )

{
    return FALSE;

#if 0
    if( EQUAL(pszCap, OLCRandomRead) )
        return FALSE;

    else if( EQUAL(pszCap, OLCFastFeatureCount) )
        return FALSE;

    else if( EQUAL(pszCap, OLCFastSpatialFilter) )
        return FALSE;

    else if( EQUAL(pszCap, OLCTransactions) )
        return FALSE;

    else if( EQUAL(pszCap, OLCFastGetExtent) )
        return FALSE;

    return FALSE;
#endif
}

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

const char *OGRIngresLayer::GetFIDColumn()

{
    return osFIDColumn;
}

/************************************************************************/
/*                         GetGeometryColumn()                          */
/************************************************************************/

const char *OGRIngresLayer::GetGeometryColumn()

{
    return osGeomColumn;
}

/************************************************************************/
/*                         FetchSRSId()                                 */
/************************************************************************/

int OGRIngresLayer::FetchSRSId(OGRFeatureDefn *poDefn)
{
/* -------------------------------------------------------------------- */
/*      We only support srses in the new ingres geospatial implementation.*/
/* -------------------------------------------------------------------- */
    if( !poDS->IsNewIngres() )
    {
        nSRSId = -1;
    }

/* -------------------------------------------------------------------- */
/*      If we haven't queried for the srs id yet, do so now.            */
/* -------------------------------------------------------------------- */
    if( nSRSId == -2 )
    {
        OGRIngresStatement oStatement(poDS->GetConn());

        char szCommand[1024] = {};
        sprintf( szCommand,
                 "SELECT srid FROM geometry_columns "
                 "WHERE f_table_name = '%s' AND f_geometry_column = '%s'",
                 poDefn->GetName(),
                 GetGeometryColumn());

        oStatement.ExecuteSQL(szCommand);

        char **papszRow = oStatement.GetRow();

        if( papszRow != NULL && papszRow[0] != NULL )
        {
            nSRSId = *((II_INT4 *) papszRow[0]);
        }
    }

    return nSRSId;
}

/************************************************************************/
/*                           GetSpatialRef()                            */
/************************************************************************/

OGRSpatialReference *OGRIngresLayer::GetSpatialRef()

{
    if( poSRS == NULL && nSRSId > -1 )
    {
        poSRS = poDS->FetchSRS( nSRSId );
        if( poSRS != NULL )
            poSRS->Reference();
        else
            nSRSId = -1;
    }

    return poSRS;
}
