/******************************************************************************
 * $Id: jpgdataset.cpp 37340 2017-02-11 18:28:02Z goatbar
 *
 * Project:  JPEG JFIF Driver
 * Purpose:  Implement GDAL JPEG Support based on IJG libjpeg.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2000, Frank Warmerdam
 * Copyright (c) 2007-2014, Even Rouault <even dot rouault at spatialys.com>
 *
 * Portions Copyright (c) Her majesty the Queen in right of Canada as
 * represented by the Minister of National Defence, 2006.
 *
 * 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 "cpl_port.h"

// TODO(schwehr): Run IWYU.
#include <cerrno>
#include <climits>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#if HAVE_FCNTL_H
#  include <fcntl.h>
#endif
#include <setjmp.h>

#include <algorithm>
#include <string>

#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_progress.h"
#include "cpl_string.h"
#include "cpl_vsi.h"
#include "gdal.h"
#include "gdal_frmts.h"
#include "gdal_pam.h"
#include "gdal_priv.h"
CPL_C_START
#ifdef LIBJPEG_12_PATH
#  include LIBJPEG_12_PATH
#else
#  include "jpeglib.h"
#endif
CPL_C_END
#include "memdataset.h"
#include "vsidataio.h"

// TIFF header.
typedef struct
{
    GUInt16 tiff_magic;    // Magic number (defines byte order).
    GUInt16 tiff_version;  // TIFF version number.
    GUInt32 tiff_diroff;   // byte offset to first directory.
} TIFFHeader;

// Ok to use setjmp().
#ifdef _MSC_VER
#  pragma warning(disable:4611)
#endif

typedef struct
{
    const char *pszFilename;
    VSILFILE *fpLin;
    char **papszSiblingFiles;
    int nScaleFactor;
    bool bDoPAMInitialize;
    bool bUseInternalOverviews;
} JPGDatasetOpenArgs;

class JPGDatasetCommon;

#if defined(JPEG_DUAL_MODE_8_12) && !defined(JPGDataset)
JPGDatasetCommon *JPEGDataset12Open(JPGDatasetOpenArgs *psArgs);
GDALDataset *JPEGDataset12CreateCopy( const char *pszFilename,
                                      GDALDataset *poSrcDS,
                                      int bStrict, char **papszOptions,
                                      GDALProgressFunc pfnProgress,
                                      void *pProgressData );
#endif

// Do we want to do special processing suitable for when JSAMPLE is a
// 16bit value?
#if defined(JPEG_LIB_MK1)
#  define JPEG_LIB_MK1_OR_12BIT 1
#elif BITS_IN_JSAMPLE == 12
#  define JPEG_LIB_MK1_OR_12BIT 1
#endif

GDALRasterBand *JPGCreateBand(JPGDatasetCommon *poDS, int nBand);

typedef void (*my_jpeg_write_m_header)(void *cinfo, int marker,
                                       unsigned int datalen);
typedef void (*my_jpeg_write_m_byte)(void *cinfo, int val);

CPLErr JPGAppendMask( const char *pszJPGFilename, GDALRasterBand *poMask,
                      GDALProgressFunc pfnProgress, void *pProgressData );
void   JPGAddEXIF        ( GDALDataType eWorkDT,
                           GDALDataset *poSrcDS, char** papszOptions,
                           void *cinfo,
                           my_jpeg_write_m_header p_jpeg_write_m_header,
                           my_jpeg_write_m_byte p_jpeg_write_m_byte,
                           GDALDataset *(pCreateCopy)(
                               const char *, GDALDataset *,
                               int, char **,
                               GDALProgressFunc pfnProgress,
                               void *pProgressData ) );
void JPGAddICCProfile( void *pInfo,
                       const char *pszICCProfile,
                       my_jpeg_write_m_header p_jpeg_write_m_header,
                       my_jpeg_write_m_byte p_jpeg_write_m_byte);

class GDALJPEGUserData
{
public:
    jmp_buf     setjmp_buffer;
    bool        bNonFatalErrorEncountered = false;
    void      (*p_previous_emit_message)(j_common_ptr cinfo, int msg_level) = nullptr;
    int         nMaxScans;

    GDALJPEGUserData() :
        nMaxScans(atoi(
            CPLGetConfigOption("GDAL_JPEG_MAX_ALLOWED_SCAN_NUMBER", "100")))
    {
        memset(&setjmp_buffer, 0, sizeof(setjmp_buffer));
    }
};

/************************************************************************/
/* ==================================================================== */
/*                         JPGDatasetCommon                             */
/* ==================================================================== */
/************************************************************************/

class JPGRasterBand;
class JPGMaskBand;

class JPGDatasetCommon CPL_NON_FINAL: public GDALPamDataset
{
  protected:
    friend class JPGDataset;
    friend class JPGRasterBand;
    friend class JPGMaskBand;

    int           nScaleFactor;
    bool          bHasInitInternalOverviews;
    int           nInternalOverviewsCurrent;
    int           nInternalOverviewsToFree;
    GDALDataset **papoInternalOverviews;
    JPGDatasetCommon* poActiveDS = nullptr; /* only valid in parent DS */
    JPGDatasetCommon** ppoActiveDS = nullptr; /* &poActiveDS of poActiveDS from parentDS */
    void          InitInternalOverviews();
    GDALDataset  *InitEXIFOverview();

    OGRSpatialReference m_oSRS{};
    bool   bGeoTransformValid;
    double adfGeoTransform[6];
    int    nGCPCount;
    GDAL_GCP *pasGCPList;

    VSILFILE   *m_fpImage;
    GUIntBig nSubfileOffset;

    int    nLoadedScanline;
    GByte  *m_pabyScanline;

    bool   bHasReadEXIFMetadata;
    bool   bHasReadXMPMetadata;
    bool   bHasReadICCMetadata;
    bool   bHasReadFLIRMetadata = false;
    char   **papszMetadata;
    int    nExifOffset;
    int    nInterOffset;
    int    nGPSOffset;
    bool   bSwabflag;
    int    nTiffDirStart;
    int    nTIFFHEADER;
    bool   bHasDoneJpegCreateDecompress;
    bool   bHasDoneJpegStartDecompress;

    int    m_nSubdatasetCount = 0;

    // FLIR raw thermal image
    bool   m_bRawThermalLittleEndian = false;
    int    m_nRawThermalImageWidth = 0;
    int    m_nRawThermalImageHeight = 0;
    std::vector<GByte> m_abyRawThermalImage{};

    virtual CPLErr LoadScanline(int, GByte* outBuffer = nullptr) = 0;
    virtual void   StopDecompress() = 0;
    virtual CPLErr Restart() = 0;

    virtual int GetDataPrecision() = 0;
    virtual int GetOutColorSpace() = 0;

    bool   EXIFInit(VSILFILE *);
    void   ReadICCProfile();

    void   CheckForMask();
    void   DecompressMask();

    void   LoadForMetadataDomain( const char *pszDomain );

    void   ReadEXIFMetadata();
    void   ReadXMPMetadata();
    void   ReadFLIRMetadata();
    GDALDataset* OpenFLIRRawThermalImage();

    bool   bHasCheckedForMask;
    JPGMaskBand *poMaskBand;
    GByte  *pabyBitMask;
    bool   bMaskLSBOrder;

    GByte  *pabyCMask;
    int    nCMaskSize;

    // Color space exposed by GDAL.  Not necessarily the in_color_space nor
    // the out_color_space of JPEG library.
    /*J_COLOR_SPACE*/ int eGDALColorSpace;

    bool   bIsSubfile;
    bool   bHasTriedLoadWorldFileOrTab;
    void   LoadWorldFileOrTab();
    CPLString osWldFilename;

    virtual int         CloseDependentDatasets() override;

    virtual CPLErr IBuildOverviews( const char *,
                                    int, const int *,
                                    int, const int *,
                                    GDALProgressFunc, void *,
                                    CSLConstList papszOptions ) override;

  public:
                 JPGDatasetCommon();
    virtual ~JPGDatasetCommon();

    virtual CPLErr      IRasterIO( GDALRWFlag, int, int, int, int,
                                   void *, int, int, GDALDataType,
                                   int, int *,
                                   GSpacing nPixelSpace, GSpacing nLineSpace,
                                   GSpacing nBandSpace,
                                   GDALRasterIOExtraArg* psExtraArg ) override;

    virtual CPLErr GetGeoTransform( double * ) override;

    virtual int    GetGCPCount() override;
    const OGRSpatialReference* GetGCPSpatialRef() const override;
    virtual const GDAL_GCP *GetGCPs() override;

    virtual char  **GetMetadataDomainList() override;
    virtual char  **GetMetadata( const char *pszDomain = "" ) override;
    virtual const char *GetMetadataItem( const char *pszName,
                                         const char *pszDomain = "" ) override;

    virtual char **GetFileList(void) override;

    virtual void FlushCache(bool bAtClosing) override;

    static int          Identify( GDALOpenInfo * );
    static GDALDataset *Open( GDALOpenInfo * );
};

/************************************************************************/
/* ==================================================================== */
/*                              JPGDataset                              */
/* ==================================================================== */
/************************************************************************/

class JPGDataset final: public JPGDatasetCommon
{
    GDALJPEGUserData sUserData;

    bool ErrorOutOnNonFatalError();

    static void EmitMessage(j_common_ptr cinfo, int msg_level);
    static void ProgressMonitor (j_common_ptr cinfo );

    struct jpeg_decompress_struct sDInfo;
    struct jpeg_error_mgr sJErr;
    struct jpeg_progress_mgr sJProgress;

    virtual CPLErr LoadScanline(int, GByte* outBuffer) override;
    CPLErr         StartDecompress();
    virtual void   StopDecompress() override;
    virtual CPLErr Restart() override;
    virtual int GetDataPrecision() override { return sDInfo.data_precision; }
    virtual int GetOutColorSpace() override { return sDInfo.out_color_space; }

    int    nQLevel;
#if !defined(JPGDataset)
    void   LoadDefaultTables(int);
#endif
    void   SetScaleNumAndDenom();

    static JPGDatasetCommon *OpenStage2( JPGDatasetOpenArgs *psArgs,
                                    JPGDataset *&poDS );

  public:
                 JPGDataset();
    virtual ~JPGDataset();

    static JPGDatasetCommon *Open( JPGDatasetOpenArgs *psArgs );
    static GDALDataset *CreateCopy( const char *pszFilename,
                                    GDALDataset *poSrcDS,
                                    int bStrict, char ** papszOptions,
                                    GDALProgressFunc pfnProgress,
                                    void *pProgressData );
    static GDALDataset *CreateCopyStage2(
        const char *pszFilename, GDALDataset *poSrcDS, char **papszOptions,
        GDALProgressFunc pfnProgress, void *pProgressData, VSILFILE *fpImage,
        GDALDataType eDT, int nQuality, bool bAppendMask,
        GDALJPEGUserData &sUserData, struct jpeg_compress_struct &sCInfo,
        struct jpeg_error_mgr &sJErr, GByte *&pabyScanline);
    static void ErrorExit(j_common_ptr cinfo);
};

/************************************************************************/
/* ==================================================================== */
/*                            JPGRasterBand                             */
/* ==================================================================== */
/************************************************************************/

class JPGRasterBand final: public GDALPamRasterBand
{
    friend class JPGDatasetCommon;

    // We have to keep a pointer to the JPGDataset that this JPGRasterBand
    // belongs to. In some case, we may have this->poGDS != this->poDS
    // For example for a JPGRasterBand that is set to a NITFDataset.
    // In other words, this->poDS doesn't necessary point to a JPGDataset
    // See ticket #1807.
    JPGDatasetCommon *poGDS;

  public:
    JPGRasterBand(JPGDatasetCommon *, int);
    virtual ~JPGRasterBand() {}

    virtual CPLErr IReadBlock( int, int, void * ) override;
    virtual GDALColorInterp GetColorInterpretation() override;

    virtual GDALSuggestedBlockAccessPattern GetSuggestedBlockAccessPattern()
        const override { return GSBAP_TOP_TO_BOTTOM; }

    virtual GDALRasterBand *GetMaskBand() override;
    virtual int             GetMaskFlags() override;

    virtual GDALRasterBand *GetOverview(int i) override;
    virtual int             GetOverviewCount() override;
};

#if !defined(JPGDataset)

/************************************************************************/
/* ==================================================================== */
/*                             JPGMaskBand                              */
/* ==================================================================== */
/************************************************************************/

class JPGMaskBand final: public GDALRasterBand
{
  protected:
    virtual CPLErr IReadBlock( int, int, void * ) override;

  public:
    explicit JPGMaskBand( JPGDatasetCommon *poDS );
    virtual ~JPGMaskBand() {}
};

/************************************************************************/
/*                         GDALRegister_JPEG()                          */
/************************************************************************/

class GDALJPGDriver final: public GDALDriver
{
  public:
    GDALJPGDriver() {}

    char      **GetMetadata( const char *pszDomain = "" ) override;
    const char *GetMetadataItem( const char *pszName,
                                 const char *pszDomain = "" ) override;
};

#endif  // !defined(JPGDataset)
