/******************************************************************************
 *
 * Project:  Oracle Spatial Driver
 * Purpose:  Implementation of the OGROCILoaderLayer class.  This implements
 *           an output only OGRLayer for writing an SQL*Loader file.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2003, Frank Warmerdam <warmerdam@pobox.com>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "ogr_oci.h"
#include "cpl_conv.h"
#include "cpl_string.h"

/************************************************************************/
/*                         OGROCILoaderLayer()                          */
/************************************************************************/

OGROCILoaderLayer::OGROCILoaderLayer(OGROCIDataSource *poDSIn,
                                     const char *pszTableName,
                                     const char *pszGeomColIn, int nSRIDIn,
                                     const char *pszLoaderFilenameIn)
    : OGROCIWritableLayer(poDSIn)

{
    iNextFIDToWrite = 1;

    bTruncationReported = FALSE;
    bHeaderWritten = FALSE;
    nLDRMode = LDRM_UNKNOWN;

    poFeatureDefn = new OGRFeatureDefn(pszTableName);
    SetDescription(poFeatureDefn->GetName());
    poFeatureDefn->Reference();

    pszGeomName = CPLStrdup(pszGeomColIn);
    pszFIDName = (char *)CPLGetConfigOption("OCI_FID", "OGR_FID");

    nSRID = nSRIDIn;
    poSRS = poDSIn->FetchSRS(nSRID);

    if (poSRS != nullptr)
        poSRS->Reference();

    /* -------------------------------------------------------------------- */
    /*      Open the loader file.                                           */
    /* -------------------------------------------------------------------- */
    pszLoaderFilename = CPLStrdup(pszLoaderFilenameIn);

    fpData = nullptr;
    fpLoader = VSIFOpen(pszLoaderFilename, "wt");
    if (fpLoader == nullptr)
    {
        CPLError(CE_Failure, CPLE_OpenFailed,
                 "Failed to open SQL*Loader control file:%s",
                 pszLoaderFilename);
        return;
    }
}

/************************************************************************/
/*                         ~OGROCILoaderLayer()                         */
/************************************************************************/

OGROCILoaderLayer::~OGROCILoaderLayer()

{
    if (fpData != nullptr)
        VSIFClose(fpData);

    if (fpLoader != nullptr)
    {
        VSIFClose(fpLoader);
        FinalizeNewLayer();
    }

    CPLFree(pszLoaderFilename);

    if (poSRS != nullptr && poSRS->Dereference() == 0)
        delete poSRS;
}

/************************************************************************/
/*                         WriteLoaderHeader()                          */
/************************************************************************/

void OGROCILoaderLayer::WriteLoaderHeader()

{
    if (bHeaderWritten)
        return;

    /* -------------------------------------------------------------------- */
    /*      Determine name of geometry column to use.                       */
    /* -------------------------------------------------------------------- */
    const char *pszGeometryName =
        CSLFetchNameValue(papszOptions, "GEOMETRY_NAME");
    if (pszGeometryName == nullptr)
        pszGeometryName = "ORA_GEOMETRY";

    /* -------------------------------------------------------------------- */
    /*      Determine our operation mode.                                   */
    /* -------------------------------------------------------------------- */
    const char *pszLDRMode = CSLFetchNameValue(papszOptions, "LOADER_MODE");

    if (pszLDRMode != nullptr && EQUAL(pszLDRMode, "VARIABLE"))
        nLDRMode = LDRM_VARIABLE;
    else if (pszLDRMode != nullptr && EQUAL(pszLDRMode, "BINARY"))
        nLDRMode = LDRM_BINARY;
    else
        nLDRMode = LDRM_STREAM;

    /* -------------------------------------------------------------------- */
    /*      Write loader header info.                                       */
    /* -------------------------------------------------------------------- */
    VSIFPrintf(fpLoader, "LOAD DATA\n");
    if (nLDRMode == LDRM_STREAM)
    {
        VSIFPrintf(fpLoader, "INFILE *\n");
        VSIFPrintf(fpLoader, "CONTINUEIF NEXT(1:1) = '#'\n");
    }
    else if (nLDRMode == LDRM_VARIABLE)
    {
        const char *pszDataFilename =
            CPLResetExtension(pszLoaderFilename, "dat");
        fpData = VSIFOpen(pszDataFilename, "wb");
        if (fpData == nullptr)
        {
            CPLError(CE_Failure, CPLE_OpenFailed,
                     "Unable to open data output file `%s'.", pszDataFilename);
            return;
        }

        VSIFPrintf(fpLoader, "INFILE %s \"var 8\"\n", pszDataFilename);
    }
    const char *pszExpectedFIDName = CPLGetConfigOption("OCI_FID", "OGR_FID");

    VSIFPrintf(fpLoader, "INTO TABLE \"%s\" REPLACE\n",
               poFeatureDefn->GetName());
    VSIFPrintf(fpLoader, "FIELDS TERMINATED BY '|'\n");
    VSIFPrintf(fpLoader, "TRAILING NULLCOLS (\n");
    VSIFPrintf(fpLoader, "    %s INTEGER EXTERNAL,\n", pszExpectedFIDName);
    VSIFPrintf(fpLoader, "    %s COLUMN OBJECT (\n", pszGeometryName);
    VSIFPrintf(fpLoader, "      SDO_GTYPE INTEGER EXTERNAL,\n");
    VSIFPrintf(fpLoader, "      SDO_ELEM_INFO VARRAY TERMINATED BY '|/'\n");
    VSIFPrintf(fpLoader, "        (elements INTEGER EXTERNAL),\n");
    VSIFPrintf(fpLoader, "      SDO_ORDINATES VARRAY TERMINATED BY '|/'\n");
    VSIFPrintf(fpLoader, "        (ordinates FLOAT EXTERNAL)\n");
    VSIFPrintf(fpLoader, "    ),\n");

    /* -------------------------------------------------------------------- */
    /*      Write user field schema.                                        */
    /* -------------------------------------------------------------------- */
    int iField;

    for (iField = 0; iField < poFeatureDefn->GetFieldCount(); iField++)
    {
        OGRFieldDefn *poFldDefn = poFeatureDefn->GetFieldDefn(iField);

        if (poFldDefn->GetType() == OFTInteger)
        {
            VSIFPrintf(fpLoader, "    \"%s\" INTEGER EXTERNAL",
                       poFldDefn->GetNameRef());
        }
        else if (poFldDefn->GetType() == OFTInteger64)
        {
            VSIFPrintf(fpLoader, "    \"%s\" LONGINTEGER EXTERNAL",
                       poFldDefn->GetNameRef());
        }
        else if (poFldDefn->GetType() == OFTReal)
        {
            VSIFPrintf(fpLoader, "    \"%s\" FLOAT EXTERNAL",
                       poFldDefn->GetNameRef());
        }
        else /* if( poFldDefn->GetType() == OFTString ) or default case */
        {
            VSIFPrintf(fpLoader, "    \"%s\" VARCHARC(4)",
                       poFldDefn->GetNameRef());
        }

        if (iField < poFeatureDefn->GetFieldCount() - 1)
            VSIFPrintf(fpLoader, ",");
        VSIFPrintf(fpLoader, "\n");
    }

    VSIFPrintf(fpLoader, ")\n");

    if (nLDRMode == LDRM_STREAM)
        VSIFPrintf(fpLoader, "begindata\n");

    bHeaderWritten = TRUE;
}

/************************************************************************/
/*                           GetNextFeature()                           */
/*                                                                      */
/*      We override the next feature method because we know that we     */
/*      implement the attribute query within the statement and so we    */
/*      don't have to test here.   Eventually the spatial query will    */
/*      be fully tested within the statement as well.                   */
/************************************************************************/

OGRFeature *OGROCILoaderLayer::GetNextFeature()

{
    CPLError(CE_Failure, CPLE_NotSupported,
             "GetNextFeature() not supported for an OGROCILoaderLayer.");
    return nullptr;
}

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

void OGROCILoaderLayer::ResetReading()

{
    OGROCILayer::ResetReading();
}

/************************************************************************/
/*                       WriteFeatureStreamMode()                       */
/************************************************************************/

OGRErr OGROCILoaderLayer::WriteFeatureStreamMode(OGRFeature *poFeature)

{
    /* -------------------------------------------------------------------- */
    /*      Write the FID.                                                  */
    /* -------------------------------------------------------------------- */
    VSIFPrintf(fpLoader, " " CPL_FRMT_GIB "|", poFeature->GetFID());

    /* -------------------------------------------------------------------- */
    /*      Set the geometry                                                */
    /* -------------------------------------------------------------------- */
    int nLineLen = 0;
    if (poFeature->GetGeometryRef() != nullptr)
    {
        char szSRID[128];
        int nGType;
        int i;

        if (nSRID == -1)
            strcpy(szSRID, "NULL");
        else
            snprintf(szSRID, sizeof(szSRID), "%d", nSRID);

        if (TranslateToSDOGeometry(poFeature->GetGeometryRef(), &nGType) ==
            OGRERR_NONE)
        {
            VSIFPrintf(fpLoader, "%d|", nGType);
            for (i = 0; i < nElemInfoCount; i++)
            {
                VSIFPrintf(fpLoader, "%d|", panElemInfo[i]);
                if (++nLineLen > 18 && i < nElemInfoCount - 1)
                {
                    VSIFPrintf(fpLoader, "\n#");
                    nLineLen = 0;
                }
            }
            VSIFPrintf(fpLoader, "/");

            for (i = 0; i < nOrdinalCount; i++)
            {
                VSIFPrintf(fpLoader, "%.16g|", padfOrdinals[i]);
                if (++nLineLen > 6 && i < nOrdinalCount - 1)
                {
                    VSIFPrintf(fpLoader, "\n#");
                    nLineLen = 0;
                }
            }
            VSIFPrintf(fpLoader, "/");
        }
        else
        {
            VSIFPrintf(fpLoader, "0|/|/");
        }
    }
    else
    {
        VSIFPrintf(fpLoader, "0|/|/");
    }

    /* -------------------------------------------------------------------- */
    /*      Set the other fields.                                           */
    /* -------------------------------------------------------------------- */
    int i;

    nLineLen = 0;
    VSIFPrintf(fpLoader, "\n#");

    for (i = 0; i < poFeatureDefn->GetFieldCount(); i++)
    {
        OGRFieldDefn *poFldDefn = poFeatureDefn->GetFieldDefn(i);

        if (!poFeature->IsFieldSetAndNotNull(i))
        {
            if (poFldDefn->GetType() != OFTInteger &&
                poFldDefn->GetType() != OFTInteger64 &&
                poFldDefn->GetType() != OFTReal)
                VSIFPrintf(fpLoader, "%04d", 0);
            continue;
        }

        const char *pszStrValue = poFeature->GetFieldAsString(i);

        if (nLineLen > 70)
        {
            VSIFPrintf(fpLoader, "\n#");
            nLineLen = 0;
        }

        nLineLen += static_cast<int>(strlen(pszStrValue));

        if (poFldDefn->GetType() == OFTInteger ||
            poFldDefn->GetType() == OFTInteger64 ||
            poFldDefn->GetType() == OFTReal)
        {
            if (poFldDefn->GetWidth() > 0 && bPreservePrecision &&
                (int)strlen(pszStrValue) > poFldDefn->GetWidth())
            {
                ReportTruncation(poFldDefn);
                VSIFPrintf(fpLoader, "|");
            }
            else
                VSIFPrintf(fpLoader, "%s|", pszStrValue);
        }
        else
        {
            int nLength = static_cast<int>(strlen(pszStrValue));

            if (poFldDefn->GetWidth() > 0 && nLength > poFldDefn->GetWidth())
            {
                ReportTruncation(poFldDefn);
                nLength = poFldDefn->GetWidth();
            }

            VSIFPrintf(fpLoader, "%04d", nLength);
            VSIFWrite((void *)pszStrValue, 1, nLength, fpLoader);
        }
    }

    if (VSIFPrintf(fpLoader, "\n") == 0)
    {
        CPLError(CE_Failure, CPLE_FileIO,
                 "Write to loader file failed, likely out of disk space.");
        return OGRERR_FAILURE;
    }
    else
        return OGRERR_NONE;
}

/************************************************************************/
/*                      WriteFeatureVariableMode()                      */
/************************************************************************/

OGRErr OGROCILoaderLayer::WriteFeatureVariableMode(OGRFeature *poFeature)

{
    OGROCIStringBuf oLine;

    if (fpData == nullptr)
        return OGRERR_FAILURE;

    /* -------------------------------------------------------------------- */
    /*      Write the FID.                                                  */
    /* -------------------------------------------------------------------- */
    oLine.Append("00000000");
    oLine.Appendf(32, " " CPL_FRMT_GIB "|", poFeature->GetFID());

    /* -------------------------------------------------------------------- */
    /*      Set the geometry                                                */
    /* -------------------------------------------------------------------- */
    if (poFeature->GetGeometryRef() != nullptr)
    {
        char szSRID[128];
        int nGType;
        int i;

        if (nSRID == -1)
            strcpy(szSRID, "NULL");
        else
            snprintf(szSRID, sizeof(szSRID), "%d", nSRID);

        if (TranslateToSDOGeometry(poFeature->GetGeometryRef(), &nGType) ==
            OGRERR_NONE)
        {
            oLine.Appendf(32, "%d|", nGType);
            for (i = 0; i < nElemInfoCount; i++)
            {
                oLine.Appendf(32, "%d|", panElemInfo[i]);
            }
            oLine.Append("/");

            for (i = 0; i < nOrdinalCount; i++)
            {
                oLine.Appendf(32, "%.16g|", padfOrdinals[i]);
            }
            oLine.Append("/");
        }
        else
        {
            oLine.Append("0|/|/");
        }
    }
    else
    {
        oLine.Append("0|/|/");
    }

    /* -------------------------------------------------------------------- */
    /*      Set the other fields.                                           */
    /* -------------------------------------------------------------------- */
    int i;

    for (i = 0; i < poFeatureDefn->GetFieldCount(); i++)
    {
        OGRFieldDefn *poFldDefn = poFeatureDefn->GetFieldDefn(i);

        if (!poFeature->IsFieldSetAndNotNull(i))
        {
            if (poFldDefn->GetType() != OFTInteger &&
                poFldDefn->GetType() != OFTInteger64 &&
                poFldDefn->GetType() != OFTReal)
                oLine.Append("0000");
            else
                oLine.Append("|");
            continue;
        }

        const char *pszStrValue = poFeature->GetFieldAsString(i);

        if (poFldDefn->GetType() == OFTInteger ||
            poFldDefn->GetType() == OFTInteger64 ||
            poFldDefn->GetType() == OFTReal)
        {
            if (poFldDefn->GetWidth() > 0 && bPreservePrecision &&
                (int)strlen(pszStrValue) > poFldDefn->GetWidth())
            {
                ReportTruncation(poFldDefn);
                oLine.Append("|");
            }
            else
            {
                oLine.Append(pszStrValue);
                oLine.Append("|");
            }
        }
        else
        {
            int nLength = static_cast<int>(strlen(pszStrValue));

            if (poFldDefn->GetWidth() > 0 && nLength > poFldDefn->GetWidth())
            {
                ReportTruncation(poFldDefn);
                nLength = poFldDefn->GetWidth();
                ((char *)pszStrValue)[nLength] = '\0';
            }

            oLine.Appendf(5, "%04d", nLength);
            oLine.Append(pszStrValue);
        }
    }

    oLine.Appendf(3, "\n");

    /* -------------------------------------------------------------------- */
    /*      Update the line's length, and write to disk.                    */
    /* -------------------------------------------------------------------- */
    char szLength[9] = {};
    size_t nStringLen = strlen(oLine.GetString());

    snprintf(szLength, sizeof(szLength), "%08d", (int)(nStringLen - 8));
    memcpy(oLine.GetString(), szLength, 8);

    if (VSIFWrite(oLine.GetString(), 1, nStringLen, fpData) != nStringLen)
    {
        CPLError(CE_Failure, CPLE_FileIO,
                 "Write to loader file failed, likely out of disk space.");
        return OGRERR_FAILURE;
    }
    else
        return OGRERR_NONE;
}

/************************************************************************/
/*                       WriteFeatureBinaryMode()                       */
/************************************************************************/

OGRErr OGROCILoaderLayer::WriteFeatureBinaryMode(OGRFeature * /*poFeature*/)

{
    return OGRERR_UNSUPPORTED_OPERATION;
}

/************************************************************************/
/*                           ICreateFeature()                            */
/************************************************************************/

OGRErr OGROCILoaderLayer::ICreateFeature(OGRFeature *poFeature)

{
    WriteLoaderHeader();

    /* -------------------------------------------------------------------- */
    /*      Set the FID.                                                    */
    /* -------------------------------------------------------------------- */
    if (poFeature->GetFID() == OGRNullFID)
        poFeature->SetFID(iNextFIDToWrite++);

    /* -------------------------------------------------------------------- */
    /*      Add extents of this geometry to the existing layer extents.     */
    /* -------------------------------------------------------------------- */
    if (poFeature->GetGeometryRef() != nullptr)
    {
        OGREnvelope sThisExtent;

        poFeature->GetGeometryRef()->getEnvelope(&sThisExtent);
        sExtent.Merge(sThisExtent);
    }

    /* -------------------------------------------------------------------- */
    /*      Call the mode specific write function.                          */
    /* -------------------------------------------------------------------- */
    if (nLDRMode == LDRM_STREAM)
        return WriteFeatureStreamMode(poFeature);
    else if (nLDRMode == LDRM_VARIABLE)
        return WriteFeatureVariableMode(poFeature);
    else if (nLDRMode == LDRM_BINARY)
        return WriteFeatureBinaryMode(poFeature);
    else
        return OGRERR_UNSUPPORTED_OPERATION;
}

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

int OGROCILoaderLayer::TestCapability(const char *pszCap)

{
    if (EQUAL(pszCap, OLCSequentialWrite))
        return TRUE;

    else if (EQUAL(pszCap, OLCCreateField))
        return TRUE;

    else
        return OGROCILayer::TestCapability(pszCap);
}

/************************************************************************/
/*                          GetFeatureCount()                           */
/*                                                                      */
/*      If a spatial filter is in effect, we turn control over to       */
/*      the generic counter.  Otherwise we return the total count.      */
/*      Eventually we should consider implementing a more efficient     */
/*      way of counting features matching a spatial query.              */
/************************************************************************/

GIntBig OGROCILoaderLayer::GetFeatureCount(int /* bForce */)

{
    return iNextFIDToWrite - 1;
}

/************************************************************************/
/*                          FinalizeNewLayer()                          */
/*                                                                      */
/*      Our main job here is to update the USER_SDO_GEOM_METADATA       */
/*      table to include the correct array of dimension object with     */
/*      the appropriate extents for this layer.  We may also do         */
/*      spatial indexing at this point.                                 */
/************************************************************************/

void OGROCILoaderLayer::FinalizeNewLayer()

{
    OGROCIStringBuf sDimUpdate;

    /* -------------------------------------------------------------------- */
    /*      If the dimensions are degenerate (all zeros) then we assume     */
    /*      there were no geometries, and we don't bother setting the       */
    /*      dimensions.                                                     */
    /* -------------------------------------------------------------------- */
    if (sExtent.MaxX == 0 && sExtent.MinX == 0 && sExtent.MaxY == 0 &&
        sExtent.MinY == 0)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "Layer %s appears to have no geometry ... not setting SDO "
                 "DIMINFO metadata.",
                 poFeatureDefn->GetName());
        return;
    }

    /* -------------------------------------------------------------------- */
    /*      Establish the extents and resolution to use.                    */
    /* -------------------------------------------------------------------- */
    double dfResSize;
    double dfXMin, dfXMax, dfXRes;
    double dfYMin, dfYMax, dfYRes;
    double dfZMin, dfZMax, dfZRes;

    if (sExtent.MaxX - sExtent.MinX > 400)
        dfResSize = 0.001;
    else
        dfResSize = 0.0000001;

    dfXMin = sExtent.MinX - dfResSize * 3;
    dfXMax = sExtent.MaxX + dfResSize * 3;
    dfXRes = dfResSize;
    ParseDIMINFO("DIMINFO_X", &dfXMin, &dfXMax, &dfXRes);

    dfYMin = sExtent.MinY - dfResSize * 3;
    dfYMax = sExtent.MaxY + dfResSize * 3;
    dfYRes = dfResSize;
    ParseDIMINFO("DIMINFO_Y", &dfYMin, &dfYMax, &dfYRes);

    dfZMin = -100000.0;
    dfZMax = 100000.0;
    dfZRes = 0.002;
    ParseDIMINFO("DIMINFO_Z", &dfZMin, &dfZMax, &dfZRes);

    /* -------------------------------------------------------------------- */
    /*      Prepare dimension update statement.                             */
    /* -------------------------------------------------------------------- */
    sDimUpdate.Append("UPDATE USER_SDO_GEOM_METADATA SET DIMINFO = ");
    sDimUpdate.Append("MDSYS.SDO_DIM_ARRAY(");

    sDimUpdate.Appendf(200, "MDSYS.SDO_DIM_ELEMENT('X',%.16g,%.16g,%.12g)",
                       dfXMin, dfXMax, dfXRes);
    sDimUpdate.Appendf(200, ",MDSYS.SDO_DIM_ELEMENT('Y',%.16g,%.16g,%.12g)",
                       dfYMin, dfYMax, dfYRes);

    if (nDimension == 3)
    {
        sDimUpdate.Appendf(200, ",MDSYS.SDO_DIM_ELEMENT('Z',%.16g,%.16g,%.12g)",
                           dfZMin, dfZMax, dfZRes);
    }

    sDimUpdate.Append(")");

    sDimUpdate.Appendf(static_cast<int>(strlen(poFeatureDefn->GetName()) + 100),
                       " WHERE table_name = UPPER('%s')",
                       poFeatureDefn->GetName());

    /* -------------------------------------------------------------------- */
    /*      Execute the metadata update.                                    */
    /* -------------------------------------------------------------------- */
    OGROCIStatement oExecStatement(poDS->GetSession());

    if (oExecStatement.Execute(sDimUpdate.GetString()) != CE_None)
        return;
}
