/******************************************************************************
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  Implements OGRIDBLayer class, code shared between
 *           the direct table access, and the generic SQL results
 *           (based on ODBC and PG drivers).
 * Author:   Oleg Semykin, oleg.semykin@gmail.com
 *
 ******************************************************************************
 * Copyright (c) 2006, Oleg Semykin
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "cpl_conv.h"
#include "ogr_idb.h"
#include "cpl_string.h"

/************************************************************************/
/*                            OGRIDBLayer()                            */
/************************************************************************/

OGRIDBLayer::OGRIDBLayer()

{
    poDS = nullptr;

    bGeomColumnWKB = FALSE;
    pszFIDColumn = nullptr;
    pszGeomColumn = nullptr;

    m_poCurr = nullptr;

    iNextShapeId = 0;

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

/************************************************************************/
/*                            ~OGRIDBLayer()                             */
/************************************************************************/

OGRIDBLayer::~OGRIDBLayer()

{
    if (m_poCurr != nullptr)
    {
        m_poCurr->Close();
        delete m_poCurr;
        m_poCurr = nullptr;
    }

    if (pszGeomColumn)
        CPLFree(pszGeomColumn);

    if (pszFIDColumn)
        CPLFree(pszFIDColumn);

    if (poFeatureDefn)
    {
        poFeatureDefn->Release();
        poFeatureDefn = nullptr;
    }

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

/************************************************************************/
/*                          BuildFeatureDefn()                          */
/*                                                                      */
/*      Build feature definition from a set of column definitions       */
/*      set on a statement.  Sift out geometry and FID fields.          */
/************************************************************************/

CPLErr OGRIDBLayer::BuildFeatureDefn(const char *pszLayerName, ITCursor *poCurr)

{
    poFeatureDefn = new OGRFeatureDefn(pszLayerName);
    SetDescription(poFeatureDefn->GetName());
    const ITTypeInfo *poInfo = poCurr->RowType();
    int nRawColumns = poInfo->ColumnCount();

    poFeatureDefn->Reference();

    for (int iCol = 0; iCol < nRawColumns; iCol++)
    {
        const char *pszColName = poInfo->ColumnName(iCol);
        const ITTypeInfo *poTI = poInfo->ColumnType(iCol);
        const char *pszTypName = poTI->Name();

        OGRFieldDefn oField(pszColName, OFTString);

        oField.SetWidth(MAX(0, poTI->Bound()));

        if (pszGeomColumn != nullptr && EQUAL(pszColName, pszGeomColumn))
            continue;

        if (STARTS_WITH_CI(pszTypName, "st_") && pszGeomColumn == nullptr)
        {
            // We found spatial column!
            pszGeomColumn = CPLStrdup(pszColName);

            if (EQUAL("st_point", pszTypName))
                poFeatureDefn->SetGeomType(wkbPoint);
            else if (EQUAL("st_linestring", pszTypName))
                poFeatureDefn->SetGeomType(wkbLineString);
            else if (EQUAL("st_polygon", pszTypName))
                poFeatureDefn->SetGeomType(wkbPolygon);
            else if (EQUAL("st_multipoint", pszTypName))
                poFeatureDefn->SetGeomType(wkbMultiPoint);
            else if (EQUAL("st_multilinestring", pszTypName))
                poFeatureDefn->SetGeomType(wkbMultiLineString);
            else if (EQUAL("st_multipolygon", pszTypName))
                poFeatureDefn->SetGeomType(wkbMultiPolygon);

            continue;
        }

        // Check other field types
        if (EQUAL(pszTypName, "blob") || EQUAL(pszTypName, "byte") ||
            EQUAL(pszTypName, "opaque") || EQUAL(pszTypName, "text") ||
            STARTS_WITH_CI(pszTypName, "list") ||
            STARTS_WITH_CI(pszTypName, "collection") ||
            STARTS_WITH_CI(pszTypName, "row") ||
            STARTS_WITH_CI(pszTypName, "set"))
        {
            CPLDebug("OGR_IDB",
                     "'%s' column type not supported yet. Column '%s'",
                     pszTypName, pszColName);
            continue;
        }

        if (STARTS_WITH_CI(pszTypName, "st_"))
        {
            oField.SetType(OFTBinary);
        }
        else if (EQUAL(pszTypName, "date"))
        {
            oField.SetType(OFTDate);
        }
        else if (EQUAL(pszTypName, "datetime"))
        {
            oField.SetType(OFTDateTime);
        }
        else if (EQUAL(pszTypName, "decimal") || EQUAL(pszTypName, "money") ||
                 EQUAL(pszTypName, "float") || EQUAL(pszTypName, "smallfloat"))
        {
            oField.SetType(OFTReal);
            oField.SetPrecision(MAX(0, poTI->Scale()));  // -1 for numeric
        }
        else if (EQUAL(pszTypName, "integer") || EQUAL(pszTypName, "serial"))
        {
            oField.SetType(OFTInteger);
            // 10 as hardcoded max int32 value length + 1 sig bit
            oField.SetWidth(11);
        }
        else if (EQUAL(pszTypName, "smallint"))
        {
            oField.SetType(OFTInteger);
            // 5 as hardcoded max int16 value length + 1 sig bit
            oField.SetWidth(6);
        }
        else
        {
            // leave as string:
            // *char, character, character varying, *varchar
            // interval. int8, serial8
        }

        poFeatureDefn->AddFieldDefn(&oField);
    }

    /* -------------------------------------------------------------------- */
    /*      If we don't already have an FID, check if there is a special    */
    /*      FID named column available.                                     */
    /* -------------------------------------------------------------------- */
    if (pszFIDColumn == nullptr)
    {
        const char *pszOGR_FID = CPLGetConfigOption("IDB_OGR_FID", "OGR_FID");
        if (poFeatureDefn->GetFieldIndex(pszOGR_FID) != -1)
            pszFIDColumn = CPLStrdup(pszOGR_FID);
    }

    if (pszFIDColumn != nullptr)
        CPLDebug("OGR_IDB", "Using column %s as FID for table %s.",
                 pszFIDColumn, poFeatureDefn->GetName());
    else
        CPLDebug("OGR_IDB", "Table %s has no identified FID column.",
                 poFeatureDefn->GetName());

    return CE_None;
}

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

void OGRIDBLayer::ResetReading()

{
    iNextShapeId = 0;
}

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

OGRFeature *OGRIDBLayer::GetNextFeature()

{
    while (true)
    {
        OGRFeature *poFeature;

        poFeature = GetNextRawFeature();
        if (poFeature == nullptr)
            return nullptr;

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

        delete poFeature;
    }
}

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

OGRFeature *OGRIDBLayer::GetNextRawFeature()

{
    if (GetQuery() == nullptr)
        return nullptr;

    /* -------------------------------------------------------------------- */
    /*      If we are marked to restart then do so, and fetch a record.     */
    /* -------------------------------------------------------------------- */
    ITRow *row = m_poCurr->NextRow();
    if (!row)
    {
        delete m_poCurr;
        m_poCurr = nullptr;
        return nullptr;
    }

    iNextShapeId++;
    m_nFeaturesRead++;

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

    const ITTypeInfo *poRowType = m_poCurr->RowType();
    int nFieldCount = poRowType->ColumnCount();

    for (iField = 0; iField < nFieldCount; iField++)
    {
        /* --------------------------------------------------------------------
         */
        /*      Handle FID column */
        /* --------------------------------------------------------------------
         */
        if (pszFIDColumn != nullptr &&
            EQUAL(poRowType->ColumnName(iField), pszFIDColumn))
            poFeature->SetFID(atoi(row->Column(iField)->Printable()));

        /* --------------------------------------------------------------------
         */
        /*      Handle geometry */
        /* --------------------------------------------------------------------
         */
        if (pszGeomColumn != nullptr &&
            EQUAL(poRowType->ColumnName(iField), pszGeomColumn))
        {
            OGRGeometry *poGeom = nullptr;
            OGRErr eErr = OGRERR_NONE;

            ITValue *v = row->Column(iField);

            if (!v->IsNull() && !bGeomColumnWKB)
            {
                const char *pszGeomText = v->Printable();
                if (pszGeomText != nullptr)
                    eErr = OGRGeometryFactory::createFromWkt(pszGeomText, poSRS,
                                                             &poGeom);
            }
            else if (!v->IsNull() && bGeomColumnWKB)
            {
                ITDatum *rv = nullptr;
                if (v->QueryInterface(ITDatumIID, (void **)&rv) ==
                    IT_QUERYINTERFACE_SUCCESS)
                {
                    int nLength = rv->DataLength();
                    unsigned char *wkb = (unsigned char *)rv->Data();

                    eErr = OGRGeometryFactory::createFromWkb(wkb, poSRS,
                                                             &poGeom, nLength);
                    rv->Release();
                }
            }

            v->Release();

            if (eErr != OGRERR_NONE)
            {
                const char *pszMessage;

                switch (eErr)
                {
                    case OGRERR_NOT_ENOUGH_DATA:
                        pszMessage = "Not enough data to deserialize";
                        break;
                    case OGRERR_UNSUPPORTED_GEOMETRY_TYPE:
                        pszMessage = "Unsupported geometry type";
                        break;
                    case OGRERR_CORRUPT_DATA:
                        pszMessage = "Corrupt data";
                        break;
                    default:
                        pszMessage = "Unrecognized error";
                }
                CPLError(CE_Failure, CPLE_AppDefined, "GetNextRawFeature(): %s",
                         pszMessage);
            }

            if (poGeom != nullptr)
            {
                poFeature->SetGeometryDirectly(poGeom);
            }

            continue;
        }

        /* --------------------------------------------------------------------
         */
        /*      Transfer regular data fields. */
        /* --------------------------------------------------------------------
         */
        int iOGRField =
            poFeatureDefn->GetFieldIndex(poRowType->ColumnName(iField));

        if (iOGRField < 0)
            continue;

        const char *pszColData = row->Column(iField)->Printable();

        if (!pszColData)
            continue;

        if (poFeatureDefn->GetFieldDefn(iOGRField)->GetType() == OFTBinary)
            poFeature->SetField(iOGRField,
                                poRowType->ColumnType(iField)->Size(),
                                (GByte *)pszColData);
        else
            poFeature->SetField(iOGRField, pszColData);
    }

    row->Release();
    return poFeature;
}

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

OGRFeature *OGRIDBLayer::GetFeature(GIntBig nFeatureId)

{
    /* This should be implemented directly! */

    return OGRLayer::GetFeature(nFeatureId);
}

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

int OGRIDBLayer::TestCapability(const char * /*pszCap*/)

{
    return FALSE;
}

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

OGRSpatialReference *OGRIDBLayer::GetSpatialRef()

{
    return poSRS;
}

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

const char *OGRIDBLayer::GetFIDColumn()

{
    if (pszFIDColumn != nullptr)
        return pszFIDColumn;
    else
        return "";
}

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

const char *OGRIDBLayer::GetGeometryColumn()

{
    if (pszGeomColumn != nullptr)
        return pszGeomColumn;
    else
        return "";
}

/* TODO Query to get layer extent */
/*
EXECUTE FUNCTION SE_BoundingBox ('table_name', 'geom_column' )
*/
