/******************************************************************************
* $Id$
*
* Project:  OpenGIS Simple Features Reference Implementation
* Purpose:  Implements Open FileGDB OGR driver.
* Author:   Even Rouault, <even dot rouault at spatialys.com>
*
******************************************************************************
 * Copyright (c) 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_OPENFILEGDB_H_INCLUDED
#define OGR_OPENFILEGDB_H_INCLUDED

#include "ogrsf_frmts.h"
#include "filegdbtable.h"
#include "ogr_swq.h"
#include "cpl_quad_tree.h"

#include <vector>
#include <map>

using namespace OpenFileGDB;

std::string OFGDBGenerateUUID();

int OGROpenFileGDBIsComparisonOp(int op);

// UUID of object type
constexpr const char* pszFolderTypeUUID = "{f3783e6f-65ca-4514-8315-ce3985dad3b1}";
constexpr const char* pszWorkspaceTypeUUID = "{c673fe0f-7280-404f-8532-20755dd8fc06}";
constexpr const char* pszFeatureDatasetTypeUUID = "{74737149-DCB5-4257-8904-B9724E32A530}";
constexpr const char* pszFeatureClassTypeUUID = "{70737809-852c-4a03-9e22-2cecea5b9bfa}";
constexpr const char* pszTableTypeUUID = "{cd06bc3b-789d-4c51-aafa-a467912b8965}";
constexpr const char* pszRangeDomainTypeUUID = "{c29da988-8c3e-45f7-8b5c-18e51ee7beb4}";
constexpr const char* pszCodedDomainTypeUUID = "{8c368b12-a12e-4c7e-9638-c9c64e69e98f}";
constexpr const char* pszRelationshipTypeUUID = "{b606a7e1-fa5b-439c-849c-6e9c2481537b}";

// UUID of relationship type
constexpr const char* pszDatasetInFeatureDatasetUUID = "{a1633a59-46ba-4448-8706-d8abe2b2b02e}";
constexpr const char* pszDatasetInFolderUUID = "{dc78f1ab-34e4-43ac-ba47-1c4eabd0e7c7}";
constexpr const char* pszDomainInDatasetUUID = "{17e08adb-2b31-4dcd-8fdd-df529e88f843}";
constexpr const char* pszDatasetsRelatedThroughUUID = "{725badab-3452-491b-a795-55f32d67229c}";


/***********************************************************************/
/*                       FETCH_FIELD_IDX()                             */
/***********************************************************************/

#define FETCH_FIELD_IDX_WITH_RET(idxName, varName, type, errorCode) \
    const int idxName = oTable.GetFieldIdx(varName); \
    if( idxName < 0 || \
        oTable.GetField(idxName)->GetType() != type ) \
    { \
        CPLError(CE_Failure, CPLE_AppDefined, \
                 "Could not find field %s in table %s", \
                 varName, oTable.GetFilename().c_str()); \
        return errorCode; \
    }

#define FETCH_FIELD_IDX(idxName, varName, type) \
    FETCH_FIELD_IDX_WITH_RET(idxName, varName, type, false)

/************************************************************************/
/*                      OGROpenFileGDBLayer                             */
/************************************************************************/

class OGROpenFileGDBDataSource;
class OGROpenFileGDBGeomFieldDefn;
class OGROpenFileGDBFeatureDefn;

typedef enum
{
    SPI_IN_BUILDING,
    SPI_COMPLETED,
    SPI_INVALID,
} SPIState;

class OGROpenFileGDBLayer final: public OGRLayer
{
    friend class OGROpenFileGDBGeomFieldDefn;
    friend class OGROpenFileGDBFeatureDefn;

    OGROpenFileGDBDataSource* m_poDS = nullptr;
    CPLString         m_osGDBFilename{};
    CPLString         m_osName{};
    std::string       m_osPath{};
    std::string       m_osThisGUID{};
    bool              m_bEditable = false;
    bool              m_bRegisteredTable = true;
    CPLStringList     m_aosCreationOptions{};
    FileGDBTable     *m_poLyrTable = nullptr;
    OGROpenFileGDBFeatureDefn   *m_poFeatureDefn = nullptr;
    int               m_iGeomFieldIdx = -1;
    int               m_iAreaField = -1; // index of Shape_Area field
    int               m_iLengthField = -1; // index of Shape_Length field
    int               m_iCurFeat = 0;
    std::string       m_osDefinition{};
    std::string       m_osDocumentation{};
    std::string       m_osConfigurationKeyword{};
    OGRwkbGeometryType m_eGeomType = wkbNone;
    int               m_bValidLayerDefn = -1;
    int               m_bEOF = false;
    bool              m_bTimeInUTC = false;
    std::string       m_osFeatureDatasetGUID{};

    bool              m_bWarnedDateNotConvertibleUTC = false;

    bool              m_bHasCreatedBackupForTransaction = false;
    std::unique_ptr<OGRFeatureDefn> m_poFeatureDefnBackup{};

    int               BuildLayerDefinition();
    int               BuildGeometryColumnGDBv10(const std::string& osParentDefinition);
    OGRFeature       *GetCurrentFeature();

    std::unique_ptr<FileGDBOGRGeometryConverter> m_poGeomConverter{};

    int               m_iFieldToReadAsBinary = -1;

    FileGDBIterator      *m_poAttributeIterator = nullptr;
    int                   m_bIteratorSufficientToEvaluateFilter = FALSE;
    FileGDBIterator*      BuildIteratorFromExprNode(swq_expr_node* poNode);

    FileGDBIterator*      m_poIterMinMax = nullptr;

    FileGDBSpatialIndexIterator* m_poSpatialIndexIterator = nullptr;
    FileGDBIterator      *m_poCombinedIterator = nullptr;

    // Legacy behavior prior to handling of .spx file
    // To remove ultimately.
    SPIState            m_eSpatialIndexState = SPI_IN_BUILDING;
    CPLQuadTree        *m_pQuadTree = nullptr;
    void              **m_pahFilteredFeatures = nullptr;
    int                 m_nFilteredFeatureCount = -1;
    static void         GetBoundsFuncEx(const void* hFeature,
                                        CPLRectObj* pBounds,
                                        void* pQTUserData);

    void                TryToDetectMultiPatchKind();
    static OGRSpatialReference* BuildSRS(const CPLXMLNode* psInfo);
    void                BuildCombinedIterator();
    bool                RegisterTable();
    void                RefreshXMLDefinitionInMemory();
    bool                CreateFeatureDataset(const char* pszFeatureDataset);
    std::string         GetLaunderedFieldName(const std::string& osNameOri) const;
    std::string         GetLaunderedLayerName(const std::string& osNameOri) const;

    mutable std::vector<std::string> m_aosTempStrings{};
    bool                PrepareFileGDBFeature( OGRFeature *poFeature,
                                               std::vector<OGRField>& fields,
                                               const OGRGeometry*& poGeom );

public:

                        OGROpenFileGDBLayer(OGROpenFileGDBDataSource* poDS,
                                            const char* pszGDBFilename,
                                            const char* pszName,
                                            const std::string& osDefinition,
                                            const std::string& osDocumentation,
                                            bool bEditable,
                                            OGRwkbGeometryType eGeomType = wkbUnknown,
                                            const std::string& osParentDefinition = std::string());

                        OGROpenFileGDBLayer(OGROpenFileGDBDataSource* poDS,
                                            const char* pszGDBFilename,
                                            const char* pszName,
                                            OGRwkbGeometryType eType,
                                            CSLConstList papszOptions);

  virtual              ~OGROpenFileGDBLayer();

  bool                  Create(const OGRSpatialReference* poSRS);
  void                  Close();

  const std::string&    GetFilename() const { return m_osGDBFilename; }
  const std::string&    GetXMLDefinition() { return m_osDefinition; }
  const std::string&    GetXMLDocumentation() { return m_osDocumentation; }
  int                   GetAttrIndexUse() { return (m_poAttributeIterator == nullptr) ? 0 : (m_bIteratorSufficientToEvaluateFilter) ? 2 : 1; }
  const OGRField*       GetMinMaxValue(OGRFieldDefn* poFieldDefn, int bIsMin,
                                       int& eOutType);
  int                   GetMinMaxSumCount(OGRFieldDefn* poFieldDefn,
                                          double& dfMin, double& dfMax,
                                          double& dfSum, int& nCount);
  int                   HasIndexForField(const char* pszFieldName);
  FileGDBIterator*      BuildIndex(const char* pszFieldName,
                                   int bAscending,
                                   int op,
                                   swq_expr_node* poValue);
  SPIState              GetSpatialIndexState() const { return m_eSpatialIndexState; }
  int                   IsValidLayerDefn() { return BuildLayerDefinition(); }

  void                  CreateSpatialIndex();
  void                  CreateIndex(const std::string& osIdxName, const std::string& osExpression);
  bool                  Repack();
  void                  RecomputeExtent();

  bool                  CheckFreeListConsistency();

  bool                  BeginEmulatedTransaction();
  bool                  CommitEmulatedTransaction();
  bool                  RollbackEmulatedTransaction();

  virtual const char* GetName() override { return m_osName.c_str(); }
  virtual OGRwkbGeometryType GetGeomType() override;

  virtual const char* GetFIDColumn() override;

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

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

  virtual OGRFeatureDefn* GetLayerDefn() override;

  virtual void        SetSpatialFilter( OGRGeometry * ) override;
  virtual void        SetSpatialFilter( int iGeomField, OGRGeometry *poGeom ) override
                { OGRLayer::SetSpatialFilter(iGeomField, poGeom); }
  virtual OGRErr      SetAttributeFilter( const char* pszFilter ) override;

  virtual int         TestCapability( const char * ) override;

  virtual OGRErr      Rename(const char* pszNewName) override;

  virtual OGRErr      CreateField( OGRFieldDefn *poField, int bApproxOK ) override;
  virtual OGRErr      DeleteField( int iFieldToDelete ) override;
  virtual OGRErr      AlterFieldDefn( int iFieldToAlter, OGRFieldDefn* poNewFieldDefn, int nFlags ) override;
  virtual OGRErr      AlterGeomFieldDefn( int iGeomFieldToAlter,
                                          const OGRGeomFieldDefn* poNewGeomFieldDefn,
                                          int nFlagsIn ) override;

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

  virtual OGRErr      SyncToDisk() override;
};

/************************************************************************/
/*                      OGROpenFileGDBGeomFieldDefn                     */
/************************************************************************/
class OGROpenFileGDBGeomFieldDefn: public OGRGeomFieldDefn
{
        OGROpenFileGDBLayer* m_poLayer;

    public:
        OGROpenFileGDBGeomFieldDefn(OGROpenFileGDBLayer* poLayer,
                                    const char *pszNameIn,
                                    OGRwkbGeometryType eGeomTypeIn) :
            OGRGeomFieldDefn(pszNameIn, eGeomTypeIn),
            m_poLayer(poLayer)
        {}

        ~OGROpenFileGDBGeomFieldDefn() {}

        void UnsetLayer() { m_poLayer = nullptr; }

        virtual OGRSpatialReference* GetSpatialRef() const override
        {
            if( poSRS )
                return poSRS;
            if( m_poLayer != nullptr )
                (void) m_poLayer->BuildLayerDefinition();
            return poSRS;
        }
};

/************************************************************************/
/*                      OGROpenFileGDBFeatureDefn                       */
/************************************************************************/
class OGROpenFileGDBFeatureDefn: public OGRFeatureDefn
{
        OGROpenFileGDBLayer* m_poLayer;
        mutable bool m_bHasBuiltFieldDefn;

        void LazyGeomInit() const
        {
            /* FileGDB v9 case */
            if( !m_bHasBuiltFieldDefn &&
                m_poLayer != nullptr && m_poLayer->m_eGeomType != wkbNone &&
                m_poLayer->m_osDefinition.empty() )
            {
                m_bHasBuiltFieldDefn = true;
                (void) m_poLayer->BuildLayerDefinition();
            }
        }

    public:
        OGROpenFileGDBFeatureDefn( OGROpenFileGDBLayer* poLayer,
                                   const char * pszName,
                                   bool bHasBuiltFieldDefn ) :
                        OGRFeatureDefn(pszName), m_poLayer(poLayer),
                        m_bHasBuiltFieldDefn(bHasBuiltFieldDefn)
        {
        }

        ~OGROpenFileGDBFeatureDefn() {}

        void UnsetLayer()
        {
            if( !apoGeomFieldDefn.empty() )
                cpl::down_cast<OGROpenFileGDBGeomFieldDefn *>(
                    apoGeomFieldDefn[0].get())->UnsetLayer();
            m_poLayer = nullptr;
        }

        virtual int GetFieldCount() const override
        {
            if( !m_bHasBuiltFieldDefn && m_poLayer != nullptr )
            {
                m_bHasBuiltFieldDefn = false;
                (void) m_poLayer->BuildLayerDefinition();
            }
            return OGRFeatureDefn::GetFieldCount();
        }

        virtual int GetGeomFieldCount() const override
        {
            LazyGeomInit();
            return OGRFeatureDefn::GetGeomFieldCount();
        }

        virtual OGRGeomFieldDefn* GetGeomFieldDefn( int i ) override
        {
            LazyGeomInit();
            return OGRFeatureDefn::GetGeomFieldDefn(i);
        }

        virtual const OGRGeomFieldDefn* GetGeomFieldDefn( int i ) const override
        {
            LazyGeomInit();
            return OGRFeatureDefn::GetGeomFieldDefn(i);
        }
};

/************************************************************************/
/*                       OGROpenFileGDBDataSource                       */
/************************************************************************/

class OGROpenFileGDBDataSource final: public OGRDataSource
{
  friend class OGROpenFileGDBLayer;

  char                          *m_pszName;
  CPLString                      m_osDirName;
  std::vector<std::unique_ptr<OGROpenFileGDBLayer>> m_apoLayers{};
  std::vector<std::unique_ptr<OGROpenFileGDBLayer>> m_apoHiddenLayers{};
  char                         **m_papszFiles;
  std::map<std::string, int>     m_osMapNameToIdx;
  std::shared_ptr<GDALGroup>     m_poRootGroup{};

  std::string                    m_osRootGUID{};
  std::string                    m_osGDBSystemCatalogFilename{};
  std::string                    m_osGDBSpatialRefsFilename{};
  std::string                    m_osGDBItemsFilename{};
  std::string                    m_osGDBItemRelationshipsFilename{};
  std::map<std::string,std::unique_ptr<GDALRelationship>> m_osMapRelationships{};

  // Related to transactions
  bool                           m_bInTransaction = false;
  bool                           m_bSystemTablesBackedup = false;
  std::string                    m_osTransactionBackupDirname{};
  std::set<OGROpenFileGDBLayer*> m_oSetLayersCreatedInTransaction{}; // must be vector of raw pointer
  std::set<std::unique_ptr<OGROpenFileGDBLayer>> m_oSetLayersDeletedInTransaction{};

  /* For debugging/testing */
  bool                           bLastSQLUsedOptimizedImplementation;

  int                 OpenFileGDBv10(int iGDBItems,
                                     int nInterestTable);
  int                 OpenFileGDBv9 (int iGDBFeatureClasses,
                                     int iGDBObjectClasses,
                                     int nInterestTable);

  int                 FileExists(const char* pszFilename);
  OGRLayer*           AddLayer( const CPLString& osName,
                                int nInterestTable,
                                int& nCandidateLayers,
                                int& nLayersSDCOrCDF,
                                const CPLString& osDefinition,
                                const CPLString& osDocumentation,
                                OGRwkbGeometryType eGeomType,
                                const std::string& osParentDefinition );

  static bool         IsPrivateLayerName( const CPLString& osName );

  bool                CreateGDBSystemCatalog();
  bool                CreateGDBDBTune();
  bool                CreateGDBSpatialRefs();
  bool                CreateGDBItems();
  bool                CreateGDBItemTypes();
  bool                CreateGDBItemRelationships();
  bool                CreateGDBItemRelationshipTypes();

  bool                BackupSystemTablesForTransaction();

public:
           OGROpenFileGDBDataSource();
  virtual ~OGROpenFileGDBDataSource();

  int                 Open( const GDALOpenInfo* poOpenInfo );
  bool                Create( const char* pszName );

  virtual void FlushCache(bool bAtClosing = false) override;

  virtual const char* GetName() override { return m_pszName; }
  virtual int         GetLayerCount() override { return static_cast<int>(m_apoLayers.size()); }

  virtual OGRLayer*   GetLayer( int ) override;
  virtual OGROpenFileGDBLayer* GetLayerByName( const char* pszName ) override;
  bool                IsLayerPrivate( int ) const override;

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

  virtual int         TestCapability( const char * ) override;

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

  virtual char      **GetFileList() override;

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

  virtual OGRErr      StartTransaction(int bForce) override;
  virtual OGRErr      CommitTransaction() override;
  virtual OGRErr      RollbackTransaction() 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;

  bool        AddRelationship(std::unique_ptr<GDALRelationship>&& relationship,
                              std::string& failureReason) override;

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

  bool        UpdateRelationship(std::unique_ptr<GDALRelationship>&& relationship,
                                 std::string& failureReason) override;

  bool                GetExistingSpatialRef( const std::string& osWKT,
                                             double dfXOrigin,
                                             double dfYOrigin,
                                             double dfXYScale,
                                             double dfZOrigin,
                                             double dfZScale,
                                             double dfMOrigin,
                                             double dfMScale,
                                             double dfXYTolerance,
                                             double dfZTolerance,
                                             double dfMTolerance );

  bool                AddNewSpatialRef( const std::string& osWKT,
                                             double dfXOrigin,
                                             double dfYOrigin,
                                             double dfXYScale,
                                             double dfZOrigin,
                                             double dfZScale,
                                             double dfMOrigin,
                                             double dfMScale,
                                             double dfXYTolerance,
                                             double dfZTolerance,
                                             double dfMTolerance );

  bool                RegisterLayerInSystemCatalog(const std::string& osLayerName);

  bool                RegisterInItemRelationships(const std::string& osOriginGUID,
                                                  const std::string& osDestGUID,
                                                  const std::string& osTypeGUID);

  bool                RegisterRelationshipInItemRelationships(const std::string& osRelationshipGUID,
                                                              const std::string& osOriginGUID,
                                                              const std::string& osDestGUID);

  bool                RemoveRelationshipFromItemRelationships(const std::string& osRelationshipGUID);

  bool                RegisterFeatureDatasetInItems(const std::string& osFeatureDatasetGUID,
                                                    const std::string& osName,
                                                    const char* pszXMLDefinition);

  bool                FindUUIDFromName(const std::string& osFeatureDatasetName,
                                       std::string& osUUIDOut);

  bool                RegisterFeatureClassInItems(const std::string& osLayerGUID,
                                                  const std::string& osLayerName,
                                                  const std::string& osPath,
                                                  const FileGDBTable* poLyrTable,
                                                  const char* pszXMLDefinition,
                                                  const char* pszDocumentation);

  bool                RegisterASpatialTableInItems(const std::string& osLayerGUID,
                                                   const std::string& osLayerName,
                                                   const std::string& osPath,
                                                   const char* pszXMLDefinition,
                                                   const char* pszDocumentation);

  bool                LinkDomainToTable(const std::string& osDomainName,
                                        const std::string& osLayerGUID);
  bool                UnlinkDomainToTable(const std::string& osDomainName,
                                          const std::string& osLayerGUID);

  bool                UpdateXMLDefinition(const std::string& osLayerName,
                                          const char* pszXMLDefinition);

  bool                IsInTransaction() const { return m_bInTransaction; }
  const std::string&  GetBackupDirName() const { return m_osTransactionBackupDirname; }
};

/************************************************************************/
/*                   OGROpenFileGDBSingleFeatureLayer                   */
/************************************************************************/

class OGROpenFileGDBSingleFeatureLayer final: public OGRLayer
{
  private:
    char               *pszVal;
    OGRFeatureDefn     *poFeatureDefn;
    int                 iNextShapeId;

  public:
                        OGROpenFileGDBSingleFeatureLayer( const char* pszLayerName,
                                                          const char *pszVal );
               virtual ~OGROpenFileGDBSingleFeatureLayer();

    virtual void        ResetReading() override { iNextShapeId = 0; }
    virtual OGRFeature *GetNextFeature() override;
    virtual OGRFeatureDefn *GetLayerDefn() override { return poFeatureDefn; }
    virtual int         TestCapability( const char * ) override { return FALSE; }
};

#endif /* ndef OGR_OPENFILEGDB_H_INCLUDED */
