/*
* Copyright (c) 2002-2012, California Institute of Technology.
* All rights reserved.  Based on Government Sponsored Research under contracts NAS7-1407 and/or NAS7-03001.
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
* that the following conditions are met:
*   1. Redistributions of source code must retain the above copyright notice, this list of conditions and
*      the following disclaimer.
*   2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
*      the following disclaimer in the documentation and/or other materials provided with the distribution.
*   3. Neither the name of the California Institute of Technology (Caltech), its operating division the
*      Jet Propulsion Laboratory (JPL), the National Aeronautics and Space Administration (NASA),
*      nor the names of its contributors may be used to endorse or promote products derived from this software
*      without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE CALIFORNIA INSTITUTE OF TECHNOLOGY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Copyright 2014-2021 Esri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* TIFF band
* TIFF page compression and decompression functions
*
* Author:  Lucian Plesea, lplesea esri.com
*
*/

#include "marfa.h"
NAMESPACE_MRF_START

// Returns a string in /vsimem/ + prefix + count that doesn't exist when this function gets called
// It is not thread safe, open the result as soon as possible
static CPLString uniq_memfname(const char* prefix) {
    // Define MRF_LOCAL_TMP to use local files instead of RAM
    // #define MRF_LOCAL_TMP
#if defined(MRF_LOCAL_TMP)
    return CPLGenerateTempFilename(prefix);
#else
    CPLString fname;
    VSIStatBufL statb;
    static unsigned int cnt = 0;
    do {
        fname.Printf("/vsimem/%s_%08x", prefix, cnt++);
    } while (!VSIStatL(fname, &statb));
    return fname;
#endif
}

//
// Uses GDAL to create a temporary TIF file, using the band create options
// copies the content to the destination buffer then erases the temp TIF
//
static CPLErr CompressTIF(buf_mgr& dst, buf_mgr& src, const ILImage& img, char** papszOptions)
{
    CPLErr ret;
    GDALDriver* poTiffDriver = GetGDALDriverManager()->GetDriverByName("GTiff");
    VSIStatBufL statb;
    CPLString fname = uniq_memfname("mrf_tif_write");

    GDALDataset* poTiff = poTiffDriver->Create(fname, img.pagesize.x, img.pagesize.y,
        img.pagesize.c, img.dt, papszOptions);

    if (nullptr == poTiff)
        return CE_Failure;

    // Write directly to avoid double caching in GDAL
    // Unfortunately not possible for multiple bands
    if (img.pagesize.c == 1)
        ret = poTiff->GetRasterBand(1)->WriteBlock(0, 0, src.buffer);
    else
        ret = poTiff->RasterIO(GF_Write, 0, 0, img.pagesize.x, img.pagesize.y, src.buffer,
            img.pagesize.x, img.pagesize.y, img.dt, img.pagesize.c, nullptr, 0, 0, 0, nullptr);
    if (CE_None != ret) return ret;
    GDALClose(poTiff);

    // Check that we can read the file
    if (VSIStatL(fname, &statb)) {
        CPLError(CE_Failure, CPLE_AppDefined,
            "MRF: TIFF, can't stat %s", fname.c_str());
        return CE_Failure;
    }

    if (static_cast<size_t>(statb.st_size) > dst.size) {
        CPLError(CE_Failure, CPLE_AppDefined,
            "MRF: TIFF, Tiff generated is too large");
        return CE_Failure;
    }

    VSILFILE* pf = VSIFOpenL(fname, "rb");
    if (pf == nullptr) {
        CPLError(CE_Failure, CPLE_AppDefined,
            "MRF: TIFF, can't open %s", fname.c_str());
        return CE_Failure;
    }

    VSIFReadL(dst.buffer, static_cast<size_t>(statb.st_size), 1, pf);
    dst.size = static_cast<size_t>(statb.st_size);
    VSIFCloseL(pf);
    VSIUnlink(fname);

    return CE_None;
}

// Read from a RAM Tiff. This is rather generic
static CPLErr DecompressTIF(buf_mgr& dst, buf_mgr& src, const ILImage& img) {
    CPLString fname = uniq_memfname("mrf_tif_read");
    VSILFILE* fp = VSIFileFromMemBuffer(fname, (GByte*)(src.buffer), src.size, false);
    // Comes back opened, but we can't use it
    if (nullptr == fp) {
        CPLError(CE_Failure, CPLE_AppDefined,
            "MRF: TIFF, can't open %s as a temp file", fname.c_str());
        return CE_Failure;
    }
    VSIFCloseL(fp);

    static const char* const apszAllowedDrivers[] = {"GTiff", nullptr};
    GDALDataset* poTiff = reinterpret_cast<GDALDataset*>(GDALOpenEx(fname, GDAL_OF_RASTER, apszAllowedDrivers, nullptr, nullptr));
    
    if (poTiff == nullptr || 0 == poTiff->GetRasterCount()) {
        CPLError(CE_Failure, CPLE_AppDefined,
            "MRF: Can't open page as a raster Tiff");
        GDALClose(poTiff); // safe to call on nullptr
        VSIUnlink(fname);
        return CE_Failure;
    }

    const GDALDataType eGTiffDT = poTiff->GetRasterBand(1)->GetRasterDataType();
    const int nDTSize = GDALGetDataTypeSizeBytes(eGTiffDT);
    if (poTiff->GetRasterXSize() != img.pagesize.x || poTiff->GetRasterYSize() != img.pagesize.y ||
        poTiff->GetRasterCount() != img.pagesize.c || img.dt != eGTiffDT ||
        static_cast<size_t>(img.pagesize.x) * img.pagesize.y * nDTSize * img.pagesize.c != dst.size)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "MRF: TIFF tile inconsistent with MRF parameters");
        GDALClose(poTiff);
        VSIUnlink(fname);
        return CE_Failure;
    }

    CPLErr ret;
    // Bypass the GDAL caching if single band and block size is right
    int nBlockXSize = 0, nBlockYSize = 0;
    poTiff->GetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);

    // Allow for TIFF blocks to be larger than MRF page size, but not in
    // huge proportion, to avoid later attempts at allocating a lot of memory
    if ((nBlockXSize > 4096 && nBlockXSize > img.pagesize.x) ||
        (nBlockYSize > 4096 && nBlockYSize > img.pagesize.y))
    {
        CPLError(CE_Failure, CPLE_AppDefined,
            "MRF: TIFF block size inconsistent with MRF parameters");
        GDALClose(poTiff);
        VSIUnlink(fname);
        return CE_Failure;
    }

    if (img.pagesize.c == 1 && nBlockXSize == img.pagesize.x && nBlockYSize == img.pagesize.y)
        ret = poTiff->GetRasterBand(1)->ReadBlock(0, 0, dst.buffer);
    else
        ret = poTiff->RasterIO(GF_Read, 0, 0, img.pagesize.x, img.pagesize.y, dst.buffer,
            img.pagesize.x, img.pagesize.y, img.dt, img.pagesize.c, nullptr,
            nDTSize * img.pagesize.c, nDTSize * img.pagesize.c * img.pagesize.x, nDTSize, nullptr);

    GDALClose(poTiff);
    VSIUnlink(fname);
    return ret;
}

CPLErr TIF_Band::Decompress(buf_mgr& dst, buf_mgr& src) {
    return DecompressTIF(dst, src, img);
}

CPLErr TIF_Band::Compress(buf_mgr& dst, buf_mgr& src) {
    return CompressTIF(dst, src, img, papszOptions);
}

TIF_Band::TIF_Band(MRFDataset* pDS, const ILImage& image, int b, int level) :
    MRFRasterBand(pDS, image, b, int(level))
{
    // Increase the page buffer by 1K in case Tiff expands data
    pDS->SetPBufferSize(image.pageSizeBytes + 1024);

    // Static create options for TIFF tiles
    papszOptions = CSLAddNameValue(nullptr, "COMPRESS", "DEFLATE");
    papszOptions = CSLAddNameValue(papszOptions, "TILED", "Yes");
    papszOptions = CSLAddNameValue(papszOptions, "BLOCKXSIZE", CPLOPrintf("%d", img.pagesize.x));
    papszOptions = CSLAddNameValue(papszOptions, "BLOCKYSIZE", CPLOPrintf("%d", img.pagesize.y));
    int q = img.quality / 10;
    // Move down so the default 85 quality maps to ZLEVEL 6.  This makes the max ZLEVEL 8, which is fine.
    if (q > 2) q -= 2;
    papszOptions = CSLAddNameValue(papszOptions, "ZLEVEL", CPLOPrintf("%d", q));
}

TIF_Band::~TIF_Band() {
    CSLDestroy(papszOptions);
}

NAMESPACE_MRF_END
