/******************************************************************************
 * $Id$
 *
 * Project:  Raster Matrix Format
 * Purpose:  Private class declarations for the RMF classes used to read/write
 *           GIS "Integratsia" raster files (also known as "Panorama" GIS).
 * Author:   Andrey Kiselev, dron@ak4719.spb.edu
 *
 ******************************************************************************
 * Copyright (c) 2007, Andrey Kiselev <dron@ak4719.spb.edu>
 *
 * 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.
 ****************************************************************************/

#include <list>
#include "gdal_priv.h"
#include "cpl_worker_thread_pool.h"

#define RMF_HEADER_SIZE 320
#define RMF_EXT_HEADER_SIZE 320
#define RMF_MIN_EXT_HEADER_SIZE (36 + 4)
#define RMF_MAX_EXT_HEADER_SIZE 1000000

#define RMF_COMPRESSION_NONE 0
#define RMF_COMPRESSION_LZW 1
#define RMF_COMPRESSION_JPEG 2
#define RMF_COMPRESSION_DEM 32

enum RMFType
{
    RMFT_RSW,  // Raster map
    RMFT_MTW   // Digital elevation model
};

enum RMFVersion
{
    RMF_VERSION = 0x0200,      // Version for "small" files less than 4 Gb
    RMF_VERSION_HUGE = 0x0201  // Version for "huge" files less than 4 Tb. Since
                               // GIS Panorama v11
};

class RMFDataset;

#define RMF_HUGE_OFFSET_FACTOR 256

#define RMF_JPEG_BAND_COUNT 3
#define RMF_DEM_BAND_COUNT 1

/************************************************************************/
/*                            RMFHeader                                 */
/************************************************************************/

typedef struct
{
#define RMF_SIGNATURE_SIZE 4
    char bySignature[RMF_SIGNATURE_SIZE];  // "RSW" for raster
                                           // map or "MTW" for DEM
    GUInt32 iVersion;
    GUInt32 nSize;       // File size in bytes
    GUInt32 nOvrOffset;  // Offset to overview
    GUInt32 iUserID;
#define RMF_NAME_SIZE 32
    GByte byName[RMF_NAME_SIZE];
    GUInt32 nBitDepth;  // Number of bits per pixel
    GUInt32 nHeight;    // Image length
    GUInt32 nWidth;     // Image width
    GUInt32 nXTiles;    // Number of tiles in line
    GUInt32 nYTiles;    // Number of tiles in column
    GUInt32 nTileHeight;
    GUInt32 nTileWidth;
    GUInt32 nLastTileHeight;
    GUInt32 nLastTileWidth;
    GUInt32 nROIOffset;
    GUInt32 nROISize;
    GUInt32 nClrTblOffset;   // Position and size
    GUInt32 nClrTblSize;     // of the colour table
    GUInt32 nTileTblOffset;  // Position and size of the
    GUInt32 nTileTblSize;    // tile offsets/sizes table
    GInt32 iMapType;
    GInt32 iProjection;
    GInt32 iEPSGCode;
    double dfScale;
    double dfResolution;
    double dfPixelSize;
    double dfLLX;
    double dfLLY;
    double dfStdP1;
    double dfStdP2;
    double dfCenterLong;
    double dfCenterLat;
    GByte iCompression;
    GByte iMaskType;
    GByte iMaskStep;
    GByte iFrameFlag;
    GUInt32 nFlagsTblOffset;
    GUInt32 nFlagsTblSize;
    GUInt32 nFileSize0;
    GUInt32 nFileSize1;
    GByte iUnknown;
    GByte iGeorefFlag;
    GByte iInverse;
    GByte iJpegQuality;
#define RMF_INVISIBLE_COLORS_SIZE 32
    GByte abyInvisibleColors[RMF_INVISIBLE_COLORS_SIZE];
    double adfElevMinMax[2];
    double dfNoData;
    GUInt32 iElevationUnit;
    GByte iElevationType;
    GUInt32 nExtHdrOffset;
    GUInt32 nExtHdrSize;
} RMFHeader;

/************************************************************************/
/*                            RMFExtHeader                              */
/************************************************************************/

typedef struct
{
    GInt32 nEllipsoid;
    GInt32 nVertDatum;
    GInt32 nDatum;
    GInt32 nZone;
} RMFExtHeader;

/************************************************************************/
/*                            RMFCompressionJob                         */
/************************************************************************/

struct RMFCompressionJob
{
    RMFDataset *poDS = nullptr;
    CPLErr eResult = CE_None;
    int nBlockXOff = -1;
    int nBlockYOff = -1;
    GByte *pabyUncompressedData = nullptr;
    size_t nUncompressedBytes = 0;
    GByte *pabyCompressedData = nullptr;
    size_t nCompressedBytes = 0;
    GUInt32 nXSize = 0;
    GUInt32 nYSize = 0;
};

/************************************************************************/
/*                            RMFCompressData                           */
/************************************************************************/

struct RMFCompressData
{
    CPLWorkerThreadPool oThreadPool;
    std::vector<RMFCompressionJob> asJobs;
    std::list<RMFCompressionJob *> asReadyJobs;
    GByte *pabyBuffers = nullptr;
    CPLMutex *hReadyJobMutex = nullptr;
    CPLMutex *hWriteTileMutex = nullptr;

    RMFCompressData(const RMFCompressData &) = delete;
    RMFCompressData &operator=(const RMFCompressData &) = delete;

    RMFCompressData();
    ~RMFCompressData();
};

/************************************************************************/
/*                            RMFTileData                               */
/************************************************************************/

struct RMFTileData
{
    std::vector<GByte> oData;
    int nBandsWritten = 0;
};

/************************************************************************/
/*                              RMFDataset                              */
/************************************************************************/

class RMFDataset final : public GDALDataset
{
    friend class RMFRasterBand;

  private:
    RMFHeader sHeader;
    RMFExtHeader sExtHeader;
    RMFType eRMFType;
    GUInt32 nXTiles;
    GUInt32 nYTiles;
    GUInt32 *paiTiles;
    GByte *pabyDecompressBuffer;
    GByte *pabyCurrentTile;
    bool bCurrentTileIsNull;
    int nCurrentTileXOff;
    int nCurrentTileYOff;
    GUInt32 nCurrentTileBytes;
    GUInt32 nColorTableSize;
    GByte *pabyColorTable;
    GDALColorTable *poColorTable;
    double adfGeoTransform[6];
    OGRSpatialReference m_oSRS{};

    char *pszUnitType;

    bool bBigEndian;
    bool bHeaderDirty;

    VSILFILE *fp;

    std::shared_ptr<RMFCompressData> poCompressData;
    std::map<GUInt32, RMFTileData> oUnfinishedTiles;

    CPLErr WriteHeader();
    static size_t LZWDecompress(const GByte *, GUInt32, GByte *, GUInt32,
                                GUInt32, GUInt32);
    static size_t LZWCompress(const GByte *, GUInt32, GByte *, GUInt32, GUInt32,
                              GUInt32, const RMFDataset *);
#ifdef HAVE_LIBJPEG
    static size_t JPEGDecompress(const GByte *, GUInt32, GByte *, GUInt32,
                                 GUInt32, GUInt32);
    static size_t JPEGCompress(const GByte *, GUInt32, GByte *, GUInt32,
                               GUInt32, GUInt32, const RMFDataset *);
#endif  // HAVE_LIBJPEG
    static size_t DEMDecompress(const GByte *, GUInt32, GByte *, GUInt32,
                                GUInt32, GUInt32);
    static size_t DEMCompress(const GByte *, GUInt32, GByte *, GUInt32, GUInt32,
                              GUInt32, const RMFDataset *);

    /*!
        Tile decompress callback
        pabyIn - input compressed data
        nSizeIn - input compressed data size (in bytes)
        pabyOut - pointer to uncompressed data
        nSizeOut - maximum uncompressed data size
        nTileSx - width of uncompressed tile (in pixels)
        nTileSy - height of uncompressed tile (in pixels)

        Returns: actual uncompressed data size or 0 on error (if nSizeOut is
                 small returns 0 too).
    */
    size_t (*Decompress)(const GByte *pabyIn, GUInt32 nSizeIn, GByte *pabyOut,
                         GUInt32 nSizeOut, GUInt32 nTileSx, GUInt32 nTileSy);

    /*!
        Tile compress callback
        pabyIn - input uncompressed data
        nSizeIn - input uncompressed data size (in bytes)
        pabyOut - pointer to compressed data
        nSizeOut - maximum compressed data size
        nTileSx - width of uncompressed tile (in pixels)
        nTileSy - height of uncompressed tile (in pixels)
        poDS - pointer to parent dataset

        Returns: actual compressed data size or 0 on error (if nSizeOut is
                 small returns 0 too).
    */
    size_t (*Compress)(const GByte *pabyIn, GUInt32 nSizeIn, GByte *pabyOut,
                       GUInt32 nSizeOut, GUInt32 nTileSx, GUInt32 nTileSy,
                       const RMFDataset *poDS);

    std::vector<RMFDataset *> poOvrDatasets;
    vsi_l_offset nHeaderOffset;
    RMFDataset *poParentDS;

  public:
    RMFDataset();
    virtual ~RMFDataset();

    static int Identify(GDALOpenInfo *poOpenInfo);
    static GDALDataset *Open(GDALOpenInfo *);
    static RMFDataset *Open(GDALOpenInfo *, RMFDataset *poParentDS,
                            vsi_l_offset nNextHeaderOffset);
    static GDALDataset *Create(const char *, int, int, int, GDALDataType,
                               char **);
    static GDALDataset *Create(const char *, int, int, int, GDALDataType,
                               char **, RMFDataset *poParentDS,
                               double dfOvFactor);
    virtual void FlushCache(bool bAtClosing) override;

    virtual CPLErr GetGeoTransform(double *padfTransform) override;
    virtual CPLErr SetGeoTransform(double *) override;
    const OGRSpatialReference *GetSpatialRef() const override;
    CPLErr SetSpatialRef(const OGRSpatialReference *poSRS) override;

    virtual CPLErr IBuildOverviews(const char *pszResampling, int nOverviews,
                                   const int *panOverviewList, int nBandsIn,
                                   const int *panBandList,
                                   GDALProgressFunc pfnProgress,
                                   void *pProgressData,
                                   CSLConstList papszOptions) override;
    virtual CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
                             int nXSize, int nYSize, void *pData, int nBufXSize,
                             int nBufYSize, GDALDataType eBufType,
                             int nBandCount, int *panBandMap,
                             GSpacing nPixelSpace, GSpacing nLineSpace,
                             GSpacing nBandSpace,
                             GDALRasterIOExtraArg *psExtraArg) override;
    vsi_l_offset GetFileOffset(GUInt32 iRMFOffset) const;
    GUInt32 GetRMFOffset(vsi_l_offset iFileOffset,
                         vsi_l_offset *piNewFileOffset) const;
    RMFDataset *OpenOverview(RMFDataset *poParentDS, GDALOpenInfo *);
    vsi_l_offset GetLastOffset() const;
    CPLErr CleanOverviews();
    static GByte GetCompressionType(const char *pszCompressName);
    int SetupCompression(GDALDataType eType, const char *pszFilename);
    static void WriteTileJobFunc(void *pData);
    CPLErr InitCompressorData(char **papszParamList);
    CPLErr WriteTile(int nBlockXOff, int nBlockYOff, GByte *pabyData,
                     size_t nBytes, GUInt32 nRawXSize, GUInt32 nRawYSize);
    CPLErr WriteRawTile(int nBlockXOff, int nBlockYOff, GByte *pabyData,
                        size_t nBytes);
    CPLErr ReadTile(int nBlockXOff, int nBlockYOff, GByte *pabyData,
                    size_t nBytes, GUInt32 nRawXSize, GUInt32 nRawYSize,
                    bool &bNullTile);
    void SetupNBits();
};

/************************************************************************/
/*                            RMFRasterBand                             */
/************************************************************************/

class RMFRasterBand final : public GDALRasterBand
{
    friend class RMFDataset;

  private:
    GUInt32 nBytesPerPixel;
    GUInt32 nBlockSize;
    GUInt32 nBlockBytes;
    GUInt32 nLastTileWidth;
    GUInt32 nLastTileHeight;
    GUInt32 nDataSize;

  public:
    RMFRasterBand(RMFDataset *, int, GDALDataType);
    virtual ~RMFRasterBand();

    virtual CPLErr IReadBlock(int, int, void *) override;
    virtual CPLErr IWriteBlock(int, int, void *) override;
    virtual GDALSuggestedBlockAccessPattern
    GetSuggestedBlockAccessPattern() const override;
    virtual double GetNoDataValue(int *pbSuccess = nullptr) override;
    virtual CPLErr SetNoDataValue(double dfNoData) override;
    virtual const char *GetUnitType() override;
    virtual GDALColorInterp GetColorInterpretation() override;
    virtual GDALColorTable *GetColorTable() override;
    virtual CPLErr SetUnitType(const char *) override;
    virtual CPLErr SetColorTable(GDALColorTable *) override;
    virtual int GetOverviewCount() override;
    virtual GDALRasterBand *GetOverview(int i) override;
    virtual CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
                             int nXSize, int nYSize, void *pData, int nBufXSize,
                             int nBufYSize, GDALDataType eBufType,
                             GSpacing nPixelSpace, GSpacing nLineSpace,
                             GDALRasterIOExtraArg *psExtraArg) override;
};
