/******************************************************************************
 * $Id$
 *
 * Project:  OpenGIS Simple Features Reference Implementation
 * Purpose:  Standard includes and class definitions ArcObjects OGR driver.
 * Author:   Ragi Yaser Burhum, ragi@burhum.com
 *
 ******************************************************************************
 * Copyright (c) 2009, Ragi Yaser Burhum
 * Copyright (c) 2011-2014, Even Rouault <even dot rouault at spatialys.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.
 ****************************************************************************/

#ifndef OGR_FGDB_H_INCLUDED
#define OGR_FGDB_H_INCLUDED

#include <vector>
#include <set>
#include "ogrsf_frmts.h"
#include "ogremulatedtransaction.h"

/* GDAL string utilities */
#include "cpl_string.h"

/* GDAL XML handler */
#include "cpl_minixml.h"

/* FGDB API headers through our own inclusion file */
#include "filegdbsdk_headers.h"

/* Workaround needed for Linux, at least for FileGDB API 1.1 (#4455) */
#if defined(__linux__)
#define EXTENT_WORKAROUND
#endif

/************************************************************************
 * Default layer creation options
 */

#define FGDB_FEATURE_DATASET "";
#define FGDB_GEOMETRY_NAME "SHAPE"
#define FGDB_OID_NAME "OBJECTID"
constexpr const char *pszRelationshipTypeUUID =
    "{B606A7E1-FA5B-439C-849C-6E9C2481537B}";

/* The ESRI FGDB API namespace */
using namespace FileGDBAPI;

class FGdbDriver;

/************************************************************************/
/*                           FGdbBaseLayer                              */
/************************************************************************/

class FGdbBaseLayer CPL_NON_FINAL : public OGRLayer
{
  protected:
    FGdbBaseLayer();
    virtual ~FGdbBaseLayer();

    OGRFeatureDefn *m_pFeatureDefn;
    OGRSpatialReference *m_pSRS;

    EnumRows *m_pEnumRows;

    std::vector<std::wstring>
        m_vOGRFieldToESRIField;  // OGR Field Index to ESRI Field Name Mapping
    std::vector<std::string>
        m_vOGRFieldToESRIFieldType;  // OGR Field Index to ESRI Field Type
                                     // Mapping

    bool m_suppressColumnMappingError;
    bool m_forceMulti;
    bool m_bTimeInUTC = false;

    bool OGRFeatureFromGdbRow(Row *pRow, OGRFeature **ppFeature);

    virtual void CloseGDBObjects();

  public:
    virtual OGRFeature *GetNextFeature() override;
};

/************************************************************************/
/*                            FGdbLayer                                 */
/************************************************************************/

class FGdbDataSource;

class FGdbLayer final : public FGdbBaseLayer
{
    friend class FGdbDataSource;

    bool m_bWorkaroundCrashOnCDFWithBinaryField = false;

    int m_bBulkLoadAllowed;
    int m_bBulkLoadInProgress;

    virtual void CloseGDBObjects() override;
    int EditIndexesForFIDHack(const char *pszRadixTablename);
    int EditGDBTablX(const CPLString &osGDBTablX,
                     const CPLString &osNewGDBTablX);
    int EditATXOrSPX(const CPLString &osIndex);
    int EditATXOrSPX(VSILFILE *fp, int nThisPage, int &nLastPageVisited,
                     int nDepth, int nSizeIndexedValue,
                     GByte *pabyLastIndexedValue, int &bIndexedValueIsValid,
                     int &nFirstIndexAtThisValue,
                     std::vector<int> &anPagesAtThisValue, int &bSortThisValue,
                     int &bInvalidateIndex);

    void StartBulkLoad();
    void EndBulkLoad();

#ifdef EXTENT_WORKAROUND
    bool m_bLayerJustCreated;
    OGREnvelope sLayerEnvelope;
    bool m_bLayerEnvelopeValid;
    void WorkAroundExtentProblem();
    bool UpdateRowWithGeometry(Row &row, OGRGeometry *poGeom);
#endif

    std::vector<ByteArray *> m_apoByteArrays;
    OGRErr PopulateRowWithFeature(Row &row, OGRFeature *poFeature);
    OGRErr GetRow(EnumRows &enumRows, Row &row, GIntBig nFID);

    char **m_papszOptions;

    int m_bCreateMultipatch;

    std::map<int, int> m_oMapOGRFIDToFGDBFID;
    std::map<int, int> m_oMapFGDBFIDToOGRFID;
    int m_nResyncThreshold;
    void ResyncIDs();

    int m_bSymlinkFlag;
    int CreateRealCopy();

    char *CreateFieldDefn(OGRFieldDefn &oField, int bApproxOK,
                          std::string &fieldname_clean,
                          std::string &gdbFieldType);

  public:
    FGdbLayer();
    virtual ~FGdbLayer();

    // Internal used by FGDB driver */
    bool Initialize(FGdbDataSource *pParentDataSource, Table *pTable,
                    const std::wstring &wstrTablePath,
                    const std::wstring &wstrType);
    bool Create(FGdbDataSource *pParentDataSource, const char *pszLayerName,
                OGRSpatialReference *poSRS, OGRwkbGeometryType eType,
                char **papszOptions);
    static bool CreateFeatureDataset(FGdbDataSource *pParentDataSource,
                                     const std::string &feature_dataset_name,
                                     OGRSpatialReference *poSRS,
                                     char **papszOptions);

    // virtual const char *GetName();
    virtual const char *GetFIDColumn() override
    {
        return m_strOIDFieldName.c_str();
    }

    virtual void ResetReading() override;
    virtual OGRFeature *GetNextFeature() override;
    virtual OGRFeature *GetFeature(GIntBig nFeatureId) override;

    Table *GetTable()
    {
        return m_pTable;
    }

    std::wstring GetTablePath() const
    {
        return m_wstrTablePath;
    }
    std::wstring GetType() const
    {
        return m_wstrType;
    }

    virtual OGRErr CreateField(OGRFieldDefn *poField, int bApproxOK) override;
    virtual OGRErr DeleteField(int iFieldToDelete) override;
#ifdef AlterFieldDefn_implemented_but_not_working
    virtual OGRErr AlterFieldDefn(int iFieldToAlter,
                                  OGRFieldDefn *poNewFieldDefn, int nFlags);
#endif

    virtual OGRErr ICreateFeature(OGRFeature *poFeature) override;
    virtual OGRErr ISetFeature(OGRFeature *poFeature) override;
    virtual OGRErr DeleteFeature(GIntBig nFID) override;

    virtual OGRErr GetExtent(OGREnvelope *psExtent, int bForce) override;
    virtual OGRErr GetExtent(int iGeomField, OGREnvelope *psExtent,
                             int bForce) override
    {
        return OGRLayer::GetExtent(iGeomField, psExtent, bForce);
    }

    virtual GIntBig GetFeatureCount(int bForce) override;
    virtual OGRErr SetAttributeFilter(const char *pszQuery) override;

    virtual void SetSpatialFilter(OGRGeometry *) override;
    virtual void SetSpatialFilter(int iGeomField, OGRGeometry *poGeom) override
    {
        OGRLayer::SetSpatialFilter(iGeomField, poGeom);
    }

    //  virtual OGRErr        StartTransaction( );
    //  virtual OGRErr        CommitTransaction( );
    //  virtual OGRErr        RollbackTransaction( );

    OGRFeatureDefn *GetLayerDefn() override
    {
        return m_pFeatureDefn;
    }

    virtual int TestCapability(const char *) override;

    // Access the XML directly. The 2 following methods are not currently used
    // by the driver, but can be used by external code for specific purposes.
    OGRErr GetLayerXML(char **poXml);
    OGRErr GetLayerMetadataXML(char **poXmlMeta);

    void SetSymlinkFlag()
    {
        m_bSymlinkFlag = TRUE;
    }

    virtual const char *GetMetadataItem(const char *pszName,
                                        const char *pszDomain) override;

    virtual OGRErr Rename(const char *pszNewName) override;

  protected:
    bool GDBToOGRFields(CPLXMLNode *psFields);
    bool ParseGeometryDef(CPLXMLNode *psGeometryDef);

    static bool ParseSpatialReference(CPLXMLNode *psSpatialRefNode,
                                      std::string *pOutWkt,
                                      std::string *pOutWKID,
                                      std::string *pOutLatestWKID);

    FGdbDataSource *m_pDS;
    Table *m_pTable;

    std::string
        m_strName;  // contains underlying FGDB table name (not catalog name)

    std::string m_strOIDFieldName;
    std::string m_strShapeFieldName;

    std::wstring m_wstrTablePath;
    std::wstring m_wstrType;  // the type: "Table" or "Feature Class"

    std::wstring m_wstrSubfields;
    std::wstring m_wstrWhereClause;

    bool m_bFilterDirty;  // optimization to avoid multiple calls to search
                          // until necessary

    bool m_bLaunderReservedKeywords;
};

/************************************************************************/
/*                         FGdbResultLayer                              */
/************************************************************************/

class FGdbResultLayer final : public FGdbBaseLayer
{
  public:
    FGdbResultLayer(FGdbDataSource *pParentDataSource, const char *pszStatement,
                    EnumRows *pEnumRows);
    virtual ~FGdbResultLayer();

    virtual void ResetReading() override;

    OGRFeatureDefn *GetLayerDefn() override
    {
        return m_pFeatureDefn;
    }

    virtual int TestCapability(const char *) override;

  protected:
    FGdbDataSource *m_pDS;
    CPLString osSQL;
};

/************************************************************************/
/*                           FGdbDataSource                            */
/************************************************************************/

class FGdbDatabaseConnection;
class OGRFileGDBGroup;

class FGdbDataSource final : public OGRDataSource
{
    CPLString m_osFSName;
    CPLString m_osPublicName;
    std::set<OGRLayer *> m_oSetSelectLayers;
    std::shared_ptr<GDALGroup> m_poRootGroup{};
    std::map<std::string, std::unique_ptr<GDALRelationship>>
        m_osMapRelationships{};

    int FixIndexes();
    int bPerLayerCopyingForTransaction;

  public:
    FGdbDataSource(bool bUseDriverMutex, FGdbDatabaseConnection *pConnection);
    virtual ~FGdbDataSource();

    int Open(const char *pszFSName, int bUpdate, const char *pszPublicName);

    const char *GetName() override
    {
        return m_osPublicName.c_str();
    }
    const char *GetFSName()
    {
        return m_osFSName.c_str();
    }

    int GetLayerCount() override
    {
        return static_cast<int>(m_layers.size());
    }

    OGRLayer *GetLayer(int) override;

    virtual OGRLayer *ICreateLayer(const char *,
                                   OGRSpatialReference * = nullptr,
                                   OGRwkbGeometryType = wkbUnknown,
                                   char ** = nullptr) override;

    virtual OGRErr DeleteLayer(int) override;

    virtual OGRLayer *ExecuteSQL(const char *pszSQLCommand,
                                 OGRGeometry *poSpatialFilter,
                                 const char *pszDialect) override;
    virtual void ReleaseResultSet(OGRLayer *poResultsSet) override;

    int TestCapability(const char *) override;

    const OGRFieldDomain *
    GetFieldDomain(const std::string &name) const override;
    std::vector<std::string>
    GetFieldDomainNames(CSLConstList papszOptions = nullptr) const override;

    bool AddFieldDomain(std::unique_ptr<OGRFieldDomain> &&domain,
                        std::string &failureReason) override;

    bool DeleteFieldDomain(const std::string &name,
                           std::string &failureReason) override;

    bool UpdateFieldDomain(std::unique_ptr<OGRFieldDomain> &&domain,
                           std::string &failureReason) override;

    std::vector<std::string>
    GetRelationshipNames(CSLConstList papszOptions = nullptr) const override;

    const GDALRelationship *
    GetRelationship(const std::string &name) const override;

    std::shared_ptr<GDALGroup> GetRootGroup() const override
    {
        return m_poRootGroup;
    }

    Geodatabase *GetGDB()
    {
        return m_pGeodatabase;
    }
    bool GetUpdate()
    {
        return m_bUpdate;
    }
    FGdbDatabaseConnection *GetConnection()
    {
        return m_pConnection;
    }

    GDALDriver *GetOpenFileGDBDrv()
    {
        return m_poOpenFileGDBDrv;
    }
    int HasSelectLayers()
    {
        return !m_oSetSelectLayers.empty();
    }

    int Close(int bCloseGeodatabase = FALSE);
    int ReOpen();

    int HasPerLayerCopyingForTransaction();
    void SetPerLayerCopyingForTransaction(int bFlag)
    {
        bPerLayerCopyingForTransaction = bFlag;
    }
    void SetSymlinkFlagOnAllLayers();

    /*
    protected:

    void EnumerateSpatialTables();
    void OpenSpatialTable( const char* pszTableName );
    */
  protected:
    bool LoadLayers(const std::wstring &parent);
    bool OpenFGDBTables(OGRFileGDBGroup *group, const std::wstring &type,
                        const std::vector<std::wstring> &layers);

    bool m_bUseDriverMutex = true;
    FGdbDatabaseConnection *m_pConnection;
    std::vector<OGRLayer *> m_layers;
    Geodatabase *m_pGeodatabase;
    bool m_bUpdate;
    GDALDriver *m_poOpenFileGDBDrv;
    std::unique_ptr<GDALDataset> m_poOpenFileGDBDS;
};

/************************************************************************/
/*                              FGdbDriver                                */
/************************************************************************/

class FGdbDatabaseConnection
{
  public:
    FGdbDatabaseConnection(CPLString osName, Geodatabase *pGeodatabase)
        : m_osName(osName), m_pGeodatabase(pGeodatabase), m_nRefCount(1),
          m_bLocked(FALSE), m_bFIDHackInProgress(FALSE)
    {
    }

    CPLString m_osName;
    Geodatabase *m_pGeodatabase;
    int m_nRefCount;
    int m_bLocked;
    int m_bFIDHackInProgress;

    Geodatabase *GetGDB()
    {
        return m_pGeodatabase;
    }
    void SetLocked(int bLockedIn)
    {
        m_bLocked = bLockedIn;
    }
    int GetRefCount() const
    {
        return m_nRefCount;
    }
    int IsLocked() const
    {
        return m_bLocked;
    }

    int IsFIDHackInProgress() const
    {
        return m_bFIDHackInProgress;
    }
    void SetFIDHackInProgress(int bFlag)
    {
        m_bFIDHackInProgress = bFlag;
    }
    int OpenGeodatabase(const char *pszOverriddenName);
    void CloseGeodatabase();
};

class FGdbTransactionManager final : public IOGRTransactionBehaviour
{
  public:
    virtual OGRErr StartTransaction(OGRDataSource *&poDSInOut,
                                    int &bOutHasReopenedDS) override;
    virtual OGRErr CommitTransaction(OGRDataSource *&poDSInOut,
                                     int &bOutHasReopenedDS) override;
    virtual OGRErr RollbackTransaction(OGRDataSource *&poDSInOut,
                                       int &bOutHasReopenedDS) override;
};

class FGdbDriver final : public GDALDriver
{
  public:
    static void Release(const char *pszName);

    static FGdbTransactionManager *GetTransactionManager();

    static CPLMutex *hMutex;
    static FGdbTransactionManager *m_poTransactionManager;
};

CPL_C_START
void CPL_DLL RegisterOGRFileGDB();
CPL_C_END

#endif /* ndef _OGR_PG_H_INCLUDED */
