/******************************************************************************
 * Project:  TIFF Overview Builder
 * Purpose:  Library functions to maintain two rows of tiles or two strips
 *           of data for output overviews as an output cache.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2000, Frank Warmerdam
 *
 * 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 "tif_ovrcache.h"
#include "tiffiop.h"
#include <assert.h>

/************************************************************************/
/*                         TIFFCreateOvrCache()                         */
/*                                                                      */
/*      Create an overview cache to hold two rows of blocks from an     */
/*      existing TIFF directory.                                        */
/************************************************************************/

TIFFOvrCache *TIFFCreateOvrCache(TIFF *hTIFF, toff_t nDirOffset)

{
    TIFFOvrCache *psCache;
    toff_t nBaseDirOffset;
    int nRet;

    psCache = (TIFFOvrCache *)_TIFFmalloc(sizeof(TIFFOvrCache));
    psCache->nDirOffset = nDirOffset;
    psCache->hTIFF = hTIFF;

    /* -------------------------------------------------------------------- */
    /*      Get definition of this raster from the TIFF file itself.        */
    /* -------------------------------------------------------------------- */
    nBaseDirOffset = TIFFCurrentDirOffset(psCache->hTIFF);
    nRet = TIFFSetSubDirectory(hTIFF, nDirOffset);
    (void)nRet;
    assert(nRet == 1);

    TIFFGetField(hTIFF, TIFFTAG_IMAGEWIDTH, &(psCache->nXSize));
    TIFFGetField(hTIFF, TIFFTAG_IMAGELENGTH, &(psCache->nYSize));

    TIFFGetField(hTIFF, TIFFTAG_BITSPERSAMPLE, &(psCache->nBitsPerPixel));
    TIFFGetField(hTIFF, TIFFTAG_SAMPLESPERPIXEL, &(psCache->nSamples));
    TIFFGetField(hTIFF, TIFFTAG_PLANARCONFIG, &(psCache->nPlanarConfig));

    if (!TIFFIsTiled(hTIFF))
    {
        TIFFGetField(hTIFF, TIFFTAG_ROWSPERSTRIP, &(psCache->nBlockYSize));
        psCache->nBlockXSize = psCache->nXSize;
        psCache->nBytesPerBlock = TIFFStripSize(hTIFF);
        psCache->bTiled = FALSE;
    }
    else
    {
        TIFFGetField(hTIFF, TIFFTAG_TILEWIDTH, &(psCache->nBlockXSize));
        TIFFGetField(hTIFF, TIFFTAG_TILELENGTH, &(psCache->nBlockYSize));
        psCache->nBytesPerBlock = TIFFTileSize(hTIFF);
        psCache->bTiled = TRUE;
    }

    /* -------------------------------------------------------------------- */
    /*      Compute some values from this.                                  */
    /* -------------------------------------------------------------------- */

    psCache->nBlocksPerRow =
        (psCache->nXSize + psCache->nBlockXSize - 1) / psCache->nBlockXSize;
    psCache->nBlocksPerColumn =
        (psCache->nYSize + psCache->nBlockYSize - 1) / psCache->nBlockYSize;

    if (psCache->nPlanarConfig == PLANARCONFIG_SEPARATE)
        psCache->nBytesPerRow = psCache->nBytesPerBlock *
                                psCache->nBlocksPerRow * psCache->nSamples;
    else
        psCache->nBytesPerRow =
            psCache->nBytesPerBlock * psCache->nBlocksPerRow;

    /* -------------------------------------------------------------------- */
    /*      Allocate and initialize the data buffers.                       */
    /* -------------------------------------------------------------------- */

    psCache->pabyRow1Blocks =
        (unsigned char *)_TIFFmalloc(psCache->nBytesPerRow);
    psCache->pabyRow2Blocks =
        (unsigned char *)_TIFFmalloc(psCache->nBytesPerRow);

    if (psCache->pabyRow1Blocks == NULL || psCache->pabyRow2Blocks == NULL)
    {
        TIFFErrorExt(hTIFF->tif_clientdata, hTIFF->tif_name,
                     "Can't allocate memory for overview cache.");
        /* TODO: use of TIFFError is inconsistent with use of fprintf in
         * addtiffo.c, sort out */
        if (psCache->pabyRow1Blocks)
            _TIFFfree(psCache->pabyRow1Blocks);
        if (psCache->pabyRow2Blocks)
            _TIFFfree(psCache->pabyRow2Blocks);
        _TIFFfree(psCache);
        return NULL;
    }

    _TIFFmemset(psCache->pabyRow1Blocks, 0, psCache->nBytesPerRow);
    _TIFFmemset(psCache->pabyRow2Blocks, 0, psCache->nBytesPerRow);

    psCache->nBlockOffset = 0;

    nRet = TIFFSetSubDirectory(psCache->hTIFF, nBaseDirOffset);
    (void)nRet;
    assert(nRet == 1);

    return psCache;
}

/************************************************************************/
/*                          TIFFWriteOvrRow()                           */
/*                                                                      */
/*      Write one entire row of blocks (row 1) to the tiff file, and    */
/*      then rotate the block buffers, essentially moving things        */
/*      down by one block.                                              */
/************************************************************************/

static void TIFFWriteOvrRow(TIFFOvrCache *psCache)

{
    int nRet, iTileX, iTileY = psCache->nBlockOffset;
    unsigned char *pabyData;
    toff_t nBaseDirOffset;
    uint32_t RowsInStrip;

    /* -------------------------------------------------------------------- */
    /*      If the output cache is multi-byte per sample, and the file      */
    /*      being written to is of a different byte order than the current  */
    /*      platform, we will need to byte swap the data.                   */
    /* -------------------------------------------------------------------- */
    if (TIFFIsByteSwapped(psCache->hTIFF))
    {
        if (psCache->nBitsPerPixel == 16)
            TIFFSwabArrayOfShort((uint16_t *)psCache->pabyRow1Blocks,
                                 (psCache->nBytesPerBlock * psCache->nSamples) /
                                     2);

        else if (psCache->nBitsPerPixel == 32)
            TIFFSwabArrayOfLong((uint32_t *)psCache->pabyRow1Blocks,
                                (psCache->nBytesPerBlock * psCache->nSamples) /
                                    4);

        else if (psCache->nBitsPerPixel == 64)
            TIFFSwabArrayOfDouble(
                (double *)psCache->pabyRow1Blocks,
                (psCache->nBytesPerBlock * psCache->nSamples) / 8);
    }

    /* -------------------------------------------------------------------- */
    /*      Record original directory position, so we can restore it at     */
    /*      end.                                                            */
    /* -------------------------------------------------------------------- */
    nBaseDirOffset = TIFFCurrentDirOffset(psCache->hTIFF);
    nRet = TIFFSetSubDirectory(psCache->hTIFF, psCache->nDirOffset);
    (void)nRet;
    assert(nRet == 1);

    /* -------------------------------------------------------------------- */
    /*      Write blocks to TIFF file.                                      */
    /* -------------------------------------------------------------------- */
    for (iTileX = 0; iTileX < psCache->nBlocksPerRow; iTileX++)
    {
        int nTileID;

        if (psCache->nPlanarConfig == PLANARCONFIG_SEPARATE)
        {
            int iSample;

            for (iSample = 0; iSample < psCache->nSamples; iSample++)
            {
                pabyData = TIFFGetOvrBlock(psCache, iTileX, iTileY, iSample);

                if (psCache->bTiled)
                {
                    nTileID = TIFFComputeTile(
                        psCache->hTIFF, iTileX * psCache->nBlockXSize,
                        iTileY * psCache->nBlockYSize, 0, (tsample_t)iSample);
                    TIFFWriteEncodedTile(psCache->hTIFF, nTileID, pabyData,
                                         TIFFTileSize(psCache->hTIFF));
                }
                else
                {
                    nTileID = TIFFComputeStrip(psCache->hTIFF,
                                               iTileY * psCache->nBlockYSize,
                                               (tsample_t)iSample);
                    RowsInStrip = psCache->nBlockYSize;
                    if ((iTileY + 1) * psCache->nBlockYSize > psCache->nYSize)
                        RowsInStrip =
                            psCache->nYSize - iTileY * psCache->nBlockYSize;
                    TIFFWriteEncodedStrip(
                        psCache->hTIFF, nTileID, pabyData,
                        TIFFVStripSize(psCache->hTIFF, RowsInStrip));
                }
            }
        }
        else
        {
            pabyData = TIFFGetOvrBlock(psCache, iTileX, iTileY, 0);

            if (psCache->bTiled)
            {
                nTileID = TIFFComputeTile(psCache->hTIFF,
                                          iTileX * psCache->nBlockXSize,
                                          iTileY * psCache->nBlockYSize, 0, 0);
                TIFFWriteEncodedTile(psCache->hTIFF, nTileID, pabyData,
                                     TIFFTileSize(psCache->hTIFF));
            }
            else
            {
                nTileID = TIFFComputeStrip(psCache->hTIFF,
                                           iTileY * psCache->nBlockYSize, 0);
                RowsInStrip = psCache->nBlockYSize;
                if ((iTileY + 1) * psCache->nBlockYSize > psCache->nYSize)
                    RowsInStrip =
                        psCache->nYSize - iTileY * psCache->nBlockYSize;
                TIFFWriteEncodedStrip(
                    psCache->hTIFF, nTileID, pabyData,
                    TIFFVStripSize(psCache->hTIFF, RowsInStrip));
            }
        }
    }
    /* TODO: add checks on error status return of TIFFWriteEncodedTile and
     * TIFFWriteEncodedStrip */

    /* -------------------------------------------------------------------- */
    /*      Rotate buffers.                                                 */
    /* -------------------------------------------------------------------- */
    pabyData = psCache->pabyRow1Blocks;
    psCache->pabyRow1Blocks = psCache->pabyRow2Blocks;
    psCache->pabyRow2Blocks = pabyData;

    _TIFFmemset(pabyData, 0, psCache->nBytesPerRow);

    psCache->nBlockOffset++;

    /* -------------------------------------------------------------------- */
    /*      Restore access to original directory.                           */
    /* -------------------------------------------------------------------- */
    TIFFFlush(psCache->hTIFF);
    /* TODO: add checks on error status return of TIFFFlush */
    TIFFSetSubDirectory(psCache->hTIFF, nBaseDirOffset);
    /* TODO: add checks on error status return of TIFFSetSubDirectory */
}

/************************************************************************/
/*                          TIFFGetOvrBlock()                           */
/************************************************************************/

/* TODO: make TIFF_Downsample handle iSample offset, so that we can
 * do with a single TIFFGetOvrBlock and no longer need
 * TIFFGetOvrBlock_Subsampled */
unsigned char *TIFFGetOvrBlock(TIFFOvrCache *psCache, int iTileX, int iTileY,
                               int iSample)

{
    long nRowOffset;

    if (iTileY > psCache->nBlockOffset + 1)
        TIFFWriteOvrRow(psCache);

    assert(iTileX >= 0 && iTileX < psCache->nBlocksPerRow);
    assert(iTileY >= 0 && iTileY < psCache->nBlocksPerColumn);
    assert(iTileY >= psCache->nBlockOffset &&
           iTileY < psCache->nBlockOffset + 2);
    assert(iSample >= 0 && iSample < psCache->nSamples);

    if (psCache->nPlanarConfig == PLANARCONFIG_SEPARATE)
        nRowOffset = ((((toff_t)iTileX * psCache->nSamples) + iSample) *
                      psCache->nBytesPerBlock);
    else
        nRowOffset = iTileX * psCache->nBytesPerBlock +
                     (psCache->nBitsPerPixel + 7) / 8 * iSample;

    if (iTileY == psCache->nBlockOffset)
        return psCache->pabyRow1Blocks + nRowOffset;
    else
        return psCache->pabyRow2Blocks + nRowOffset;
}

/************************************************************************/
/*                     TIFFGetOvrBlock_Subsampled()                     */
/************************************************************************/

unsigned char *TIFFGetOvrBlock_Subsampled(TIFFOvrCache *psCache, int iTileX,
                                          int iTileY)

{
    int nRowOffset;

    if (iTileY > psCache->nBlockOffset + 1)
        TIFFWriteOvrRow(psCache);

    assert(iTileX >= 0 && iTileX < psCache->nBlocksPerRow);
    assert(iTileY >= 0 && iTileY < psCache->nBlocksPerColumn);
    assert(iTileY >= psCache->nBlockOffset &&
           iTileY < psCache->nBlockOffset + 2);
    assert(psCache->nPlanarConfig != PLANARCONFIG_SEPARATE);

    nRowOffset = iTileX * psCache->nBytesPerBlock;

    if (iTileY == psCache->nBlockOffset)
        return psCache->pabyRow1Blocks + nRowOffset;
    else
        return psCache->pabyRow2Blocks + nRowOffset;
}

/************************************************************************/
/*                        TIFFDestroyOvrCache()                         */
/************************************************************************/

void TIFFDestroyOvrCache(TIFFOvrCache *psCache)

{
    while (psCache->nBlockOffset < psCache->nBlocksPerColumn)
        TIFFWriteOvrRow(psCache);

    _TIFFfree(psCache->pabyRow1Blocks);
    _TIFFfree(psCache->pabyRow2Blocks);
    _TIFFfree(psCache);
}
