/***********************************************************************
 * File :    postgisraster.h
 * Project:  PostGIS Raster driver
 * Purpose:  Main header file for PostGIS Raster Driver
 * Author:   Jorge Arevalo, jorge.arevalo@deimos-space.com
 *                          jorgearevalo@libregis.org
 *
 * Author:   David Zwarg, dzwarg@azavea.com
 *
 *
 ***********************************************************************
 * Copyright (c) 2009 - 2013, Jorge Arevalo, David Zwarg
 * Copyright (c) 2013, Even Rouault
 *
 * SPDX-License-Identifier: MIT
 **********************************************************************/

#ifndef POSTGISRASTER_H_INCLUDED
#define POSTGISRASTER_H_INCLUDED

#include "gdal_priv.h"
#include "libpq-fe.h"
#include "vrtdataset.h"
#include "cpl_mem_cache.h"
#include "cpl_quad_tree.h"
#include <float.h>
#include <map>

// #define DEBUG_VERBOSE
// #define DEBUG_QUERY

#if defined(DEBUG_VERBOSE) && !defined(DEBUG_QUERY)
#define DEBUG_QUERY
#endif

/**
 * The block size for the cache will be the minimum between the tile
 * size from sources and this value. So, please keep it at 2048 or
 * lower
 **/
#define MAX_BLOCK_SIZE 2048

#define NO_VALID_RES "-1234.56"

/**
 * To move over the data return by queries
 **/
#define POSTGIS_RASTER_VERSION static_cast<GUInt16>(0)
#define RASTER_HEADER_SIZE 61
#define RASTER_BAND_HEADER_FIXED_SIZE 1

#define BAND_SIZE(nodatasize, datasize)                                        \
    (RASTER_BAND_HEADER_FIXED_SIZE + (nodatasize) + (datasize))

#define GET_BAND_DATA(raster, nband, nodatasize, datasize)                     \
    ((raster) + RASTER_HEADER_SIZE + (nband)*BAND_SIZE(nodatasize, datasize) - \
     (datasize))

#define GEOTRSFRM_TOPLEFT_X 0
#define GEOTRSFRM_WE_RES 1
#define GEOTRSFRM_ROTATION_PARAM1 2
#define GEOTRSFRM_TOPLEFT_Y 3
#define GEOTRSFRM_ROTATION_PARAM2 4
#define GEOTRSFRM_NS_RES 5

// Number of results return by ST_Metadata PostGIS function
#define ELEMENTS_OF_METADATA_RECORD 10

// Positions of elements of ST_Metadata PostGIS function
#define POS_UPPERLEFTX 0
#define POS_UPPERLEFTY 1
#define POS_WIDTH 2
#define POS_HEIGHT 3
#define POS_SCALEX 4
#define POS_SCALEY 5
#define POS_SKEWX 6
#define POS_SKEWY 7
#define POS_SRID 8
#define POS_NBANDS 9

// Number of results return by ST_BandMetadata PostGIS function
#define ELEMENTS_OF_BAND_METADATA_RECORD 4

// Positions of elements of ST_BandMetadata PostGIS function
#define POS_PIXELTYPE 0
#define POS_NODATAVALUE 1
#define POS_ISOUTDB 2
#define POS_PATH 3

/**
 * The driver can work in these modes:
 * - NO_MODE: Error case
 * - ONE_RASTER_PER_ROW: Each row of the requested table is considered
 *              as a separated raster object. This is the default mode, if
 *              database and table name are provided, and no mode is specified.
 * - ONE_RASTER_PER_TABLE: All the rows of the requested table are
 *              considered as tiles of a bigger raster coverage (the whole
 *              table). If database and table name are specified and mode = 2
 *              is present in the connection string, this is the selected mode.
 * - BROWSE_SCHEMA: If no table name is specified, just database and
 *              schema names, the driver will yell of the schema's raster tables
 *              as possible datasets.
 * - BROWSE_DATABASE: If no table name is specified, just database name,
 *              the driver will yell of the database's raster tables as possible
 *              datasets.
 **/
typedef enum
{
    NO_MODE,
    ONE_RASTER_PER_ROW,
    ONE_RASTER_PER_TABLE,
    BROWSE_SCHEMA,
    BROWSE_DATABASE
} WorkingMode;

enum class OutDBResolution
{
    SERVER_SIDE,
    CLIENT_SIDE,
    CLIENT_SIDE_IF_POSSIBLE
};

/**
 * Important metadata of a PostGIS Raster band
 **/
typedef struct
{
    GDALDataType eDataType;
    int nBitsDepth;
    GBool bHasNoDataValue;
    GBool bIsOffline;
    char *path;
    double dfNoDataValue;
} BandMetadata;

typedef struct
{
    char *pszSchema;
    char *pszTable;
    char *pszColumn;
    int nFactor;
} PROverview;

// Some tools definitions
char *ReplaceQuotes(const char *, int);
GBool TranslateDataType(const char *, GDALDataType *, int *);

class PostGISRasterRasterBand;
class PostGISRasterTileDataset;

/***********************************************************************
 * PostGISRasterDriver: extends GDALDriver to support PostGIS Raster
 * connect.
 **********************************************************************/
class PostGISRasterDriver final : public GDALDriver
{

  private:
    CPLMutex *hMutex = nullptr;
    std::map<CPLString, PGconn *> oMapConnection{};

    CPL_DISALLOW_COPY_ASSIGN(PostGISRasterDriver)
  public:
    PostGISRasterDriver();
    ~PostGISRasterDriver() override;
    PGconn *GetConnection(const char *pszConnectionString,
                          const char *pszServiceIn, const char *pszDbnameIn,
                          const char *pszHostIn, const char *pszPortIn,
                          const char *pszUserIn);

    static PostGISRasterDriver *gpoPostGISRasterDriver;
};

/***********************************************************************
 * PostGISRasterDataset: extends VRTDataset to support PostGIS Raster
 * datasets
 **********************************************************************/
class PostGISRasterDataset final : public VRTDataset
{
    friend class PostGISRasterRasterBand;
    friend class PostGISRasterTileRasterBand;

  private:
    typedef enum
    {
        LOWEST_RESOLUTION,
        HIGHEST_RESOLUTION,
        AVERAGE_RESOLUTION,
        USER_RESOLUTION,
        AVERAGE_APPROX_RESOLUTION
    } ResolutionStrategy;

    char **papszSubdatasets;
    int nSrid;
    int nOverviewFactor;
    int nBandsToCreate;
    PGconn *poConn;
    GBool bRegularBlocking;
    GBool bAllTilesSnapToSameGrid;
    GBool bCheckAllTiles;
    char *pszSchema;
    char *pszTable;
    char *pszColumn;
    char *pszWhere;
    char *pszPrimaryKeyName;
    GBool bIsFastPK;
    int bHasTriedFetchingPrimaryKeyName;
    mutable OGRSpatialReference m_oSRS{};
    ResolutionStrategy resolutionStrategy;
    WorkingMode nMode;
    OutDBResolution eOutDBResolution{OutDBResolution::SERVER_SIDE};
    bool bHasStBandFileSize = false;
    int m_nTiles;
    double xmin;
    double ymin;
    double xmax;
    double ymax;
    PostGISRasterTileDataset **papoSourcesHolders;
    CPLQuadTree *hQuadTree;

    GBool bHasBuiltOverviews;
    int nOverviewCount;
    PostGISRasterDataset *poParentDS;
    PostGISRasterDataset **papoOverviewDS;

    std::map<CPLString, PostGISRasterTileDataset *> oMapPKIDToRTDS{};

    GBool bAssumeMultiBandReadPattern;
    int nNextExpectedBand;
    int nXOffPrev;
    int nYOffPrev;
    int nXSizePrev;
    int nYSizePrev;

    GBool bHasTriedHasSpatialIndex;
    GBool bHasSpatialIndex;

    GBool bBuildQuadTreeDynamically;

    GBool bTilesSameDimension;
    int nTileWidth;
    int nTileHeight;

    int m_nLastLoadSourcesXOff = 0;
    int m_nLastLoadSourcesYOff = 0;
    int m_nLastLoadSourcesXSize = 0;
    int m_nLastLoadSourcesYSize = 0;
    int m_nLastLoadSourcesBand = 0;

    lru11::Cache<std::string, std::shared_ptr<GDALDataset>> oOutDBDatasetCache{
        8, 0};
    lru11::Cache<std::string, bool> oOutDBFilenameUsable{100, 0};

    GBool ConstructOneDatasetFromTiles(PGresult *);
    GBool YieldSubdatasets(PGresult *, const char *);
    GBool SetRasterProperties(const char *);
    GBool BrowseDatabase(const char *, const char *);
    void AddComplexSource(PostGISRasterTileDataset *poRTDS);
    void GetDstWin(PostGISRasterTileDataset *, int *, int *, int *, int *);
    BandMetadata *GetBandsMetadata(int *);
    PROverview *GetOverviewTables(int *);

    PostGISRasterTileDataset *
    BuildRasterTileDataset(const char *pszMetadata, const char *pszPKID,
                           int nBandsFetched, BandMetadata *poBandMetaData);
    void UpdateGlobalResolutionWithTileResolution(double tilePixelSizeX,
                                                  double tilePixelSizeY);
    void BuildOverviews();
    void BuildBands(BandMetadata *poBandMetaData, int nBandsFetched);

    PostGISRasterTileDataset *GetMatchingSourceRef(const char *pszPKID)
    {
        return oMapPKIDToRTDS[pszPKID];
    }

    PostGISRasterTileDataset *GetMatchingSourceRef(double dfUpperLeftX,
                                                   double dfUpperLeftY);

    bool CanUseClientSideOutDB(bool bAllBandCaching, int nBand,
                               const CPLString &osWHERE);

    bool LoadOutdbRaster(int &nCurOffset, GDALDataType eDT, int nBand,
                         const GByte *pbyData, int nWKBLength, void *pImage,
                         double dfTileUpperLeftX, double dfTileUpperLeftY,
                         double dfTileResX, double dfTileResY, int nTileXSize,
                         int nTileYSize);

    CPL_DISALLOW_COPY_ASSIGN(PostGISRasterDataset)

  protected:
    int CloseDependentDatasets() override;
    CPLErr FlushCache(bool bAtClosing) override;

  public:
    PostGISRasterDataset();
    ~PostGISRasterDataset() override;
    static GDALDataset *Open(GDALOpenInfo *);
    static GDALDataset *CreateCopy(const char *, GDALDataset *, int, char **,
                                   GDALProgressFunc, void *);
    static GBool InsertRaster(PGconn *, PostGISRasterDataset *, const char *,
                              const char *, const char *);
    static CPLErr Delete(const char *);
    char **GetMetadataDomainList() override;
    char **GetMetadata(const char *) override;

    const OGRSpatialReference *GetSpatialRef() const override;
    CPLErr SetSpatialRef(const OGRSpatialReference *poSRS) override;

    CPLErr SetGeoTransform(const GDALGeoTransform &gt) override;
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
    char **GetFileList() override;

    int GetOverviewCount();
    PostGISRasterDataset *GetOverviewDS(int iOvr);

    const char *GetPrimaryKeyRef();
    GBool HasSpatialIndex();
    GBool LoadSources(int nXOff, int nYOff, int nXSize, int nYSize, int nBand);
    GBool PolygonFromCoords(int nXOff, int nYOff, int nXEndOff, int nYEndOff,
                            double adfProjWin[8]);
    void CacheTile(const char *pszMetadata, const char *pszRaster,
                   const char *pszPKID, int nBand, bool bAllBandCaching);
};

/***********************************************************************
 * PostGISRasterRasterBand: extends VRTSourcedRasterBand to support
 * PostGIS Raster bands
 **********************************************************************/
class PostGISRasterTileRasterBand;

class PostGISRasterRasterBand final : public VRTSourcedRasterBand
{
    friend class PostGISRasterDataset;

    CPL_DISALLOW_COPY_ASSIGN(PostGISRasterRasterBand)
  protected:
    const char *pszSchema;
    const char *pszTable;
    const char *pszColumn;

    void NullBuffer(void *pData, int nBufXSize, int nBufYSize,
                    GDALDataType eBufType, int nPixelSpace, int nLineSpace);

  public:
    PostGISRasterRasterBand(PostGISRasterDataset *poDSIn, int nBandIn,
                            GDALDataType eDataTypeIn, GBool bNoDataValueSetIn,
                            double dfNodata);

    ~PostGISRasterRasterBand() override;

    double GetNoDataValue(int *pbSuccess = nullptr) override;
    CPLErr SetNoDataValue(double) override;
    CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
                     GDALDataType, GSpacing nPixelSpace, GSpacing nLineSpace,
                     GDALRasterIOExtraArg *psExtraArg) override;

    int GetOverviewCount() override;
    GDALRasterBand *GetOverview(int) override;
    GDALColorInterp GetColorInterpretation() override;

    double GetMinimum(int *pbSuccess) override;
    double GetMaximum(int *pbSuccess) override;
    virtual CPLErr ComputeRasterMinMax(int bApproxOK,
                                       double *adfMinMax) override;
};

/***********************************************************************
 * PostGISRasterTileDataset: it holds just a raster tile
 **********************************************************************/
class PostGISRasterTileRasterBand;

class PostGISRasterTileDataset final : public GDALDataset
{
    friend class PostGISRasterDataset;
    friend class PostGISRasterRasterBand;
    friend class PostGISRasterTileRasterBand;

  private:
    PostGISRasterDataset *poRDS;
    char *pszPKID;
    GDALGeoTransform m_gt{};

    CPL_DISALLOW_COPY_ASSIGN(PostGISRasterTileDataset)

  public:
    PostGISRasterTileDataset(PostGISRasterDataset *poRDS, int nXSize,
                             int nYSize);
    ~PostGISRasterTileDataset() override;
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
    void GetNativeExtent(double *pdfMinX, double *pdfMinY, double *pdfMaxX,
                         double *pdfMaxY) const;

    const char *GetPKID() const
    {
        return pszPKID;
    }
};

/***********************************************************************
 * PostGISRasterTileRasterBand: it holds a raster tile band, that will
 * be used as a source for PostGISRasterRasterBand
 **********************************************************************/
class PostGISRasterTileRasterBand final : public GDALRasterBand
{
    friend class PostGISRasterRasterBand;
    friend class PostGISRasterDataset;

  private:
    GBool IsCached();

    VRTSource *poSource;

    CPL_DISALLOW_COPY_ASSIGN(PostGISRasterTileRasterBand)

  public:
    PostGISRasterTileRasterBand(PostGISRasterTileDataset *poRTDS, int nBand,
                                GDALDataType eDataType);
    ~PostGISRasterTileRasterBand() override;
    CPLErr IReadBlock(int, int, void *) override;
};

#endif  // POSTGISRASTER_H_INCLUDED
