/******************************************************************************
 *
 * Project:  FIT Driver
 * Purpose:  Implement FIT Support - not using the SGI iflFIT library.
 * Author:   Philip Nemec, nemec@keyholecorp.com
 *
 ******************************************************************************
 * Copyright (c) 2001, Keyhole, Inc.
 * Copyright (c) 2007-2011, Even Rouault <even dot rouault at spatialys.com>
 *
 * 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_string.h"
#include "fit.h"
#include "gdal_frmts.h"
#include "gdal_pam.h"
#include "gstEndian.h"
#include "cpl_safemaths.hpp"

#include <algorithm>

CPL_CVSID("$Id$")

constexpr size_t FIT_PAGE_SIZE = 128;

using namespace gstEndian;

/************************************************************************/
/* ==================================================================== */
/*                              FITDataset                              */
/* ==================================================================== */
/************************************************************************/

class FITRasterBand;

class FITDataset final: public GDALPamDataset
{
    friend class FITRasterBand;

    VSILFILE    *fp;
    FITinfo     *info;
    double      adfGeoTransform[6];

  public:
    FITDataset();
    ~FITDataset();
    static GDALDataset *Open( GDALOpenInfo * );
    // virtual CPLErr GetGeoTransform( double * );
};

static GDALDataset *FITCreateCopy(const char * pszFilename,
                                  GDALDataset *poSrcDS,
                                  int bStrict, char ** papszOptions,
                                  GDALProgressFunc pfnProgress,
                                  void * pProgressData );

/************************************************************************/
/* ==================================================================== */
/*                            FITRasterBand                             */
/* ==================================================================== */
/************************************************************************/

class FITRasterBand final: public GDALPamRasterBand
{
    friend class FITDataset;

    unsigned long recordSize; // number of bytes of a single page/block/record
    unsigned long numXBlocks; // number of pages in the X direction
    unsigned long numYBlocks; // number of pages in the Y direction
    unsigned long bytesPerComponent;
    unsigned long bytesPerPixel;
    char *tmpImage;

public:
    FITRasterBand( FITDataset *, int nBandIn, int nBandsIn );
    ~FITRasterBand() override;

    // should override RasterIO eventually.

    CPLErr IReadBlock( int, int, void * ) override;
    // virtual CPLErr WriteBlock( int, int, void * );
    double GetMinimum( int *pbSuccess ) override;
    double GetMaximum( int *pbSuccess ) override;
    GDALColorInterp GetColorInterpretation() override;
};

/************************************************************************/
/*                           FITRasterBand()                            */
/************************************************************************/

FITRasterBand::FITRasterBand( FITDataset *poDSIn, int nBandIn, int nBandsIn ) :
    recordSize(0),
    numXBlocks(0),
    numYBlocks(0),
    bytesPerComponent(0),
    bytesPerPixel(0),
    tmpImage( nullptr )
{
    poDS = poDSIn;
    nBand = nBandIn;

/* -------------------------------------------------------------------- */
/*      Get the GDAL data type.                                         */
/* -------------------------------------------------------------------- */
    eDataType = fitDataType(poDSIn->info->dtype);

/* -------------------------------------------------------------------- */
/*      Get the page sizes.                                             */
/* -------------------------------------------------------------------- */
    nBlockXSize = poDSIn->info->xPageSize;
    nBlockYSize = poDSIn->info->yPageSize;

/* -------------------------------------------------------------------- */
/*      Calculate the values for record offset calculations.             */
/* -------------------------------------------------------------------- */
    bytesPerComponent = GDALGetDataTypeSizeBytes(eDataType);
    if( bytesPerComponent == 0 )
        return;
    bytesPerPixel = nBandsIn * bytesPerComponent;
    const auto knIntMax = std::numeric_limits<int>::max();
    if( nBlockXSize <= 0 || nBlockYSize <= 0 ||
        nBlockXSize > knIntMax / static_cast<int>(bytesPerPixel) ||
        nBlockYSize > knIntMax /
            (nBlockXSize * static_cast<int>(bytesPerPixel)) )
        return;
    recordSize = bytesPerPixel * nBlockXSize * nBlockYSize;
    numXBlocks =
        (unsigned long) ceil((double) poDSIn->info->xSize / nBlockXSize);
    numYBlocks =
        (unsigned long) ceil((double) poDSIn->info->ySize / nBlockYSize);

    tmpImage = (char *) VSI_MALLOC_VERBOSE(recordSize);
/* -------------------------------------------------------------------- */
/*      Set the access flag.  For now we set it the same as the         */
/*      whole dataset, but eventually this should take account of       */
/*      locked channels, or read-only secondary data files.             */
/* -------------------------------------------------------------------- */
    /* ... */
}

FITRasterBand::~FITRasterBand()
{
    VSIFree ( tmpImage );
}

/************************************************************************/
/*                            IReadBlock()                              */
/************************************************************************/

#define COPY_XFIRST(t) { \
                t *dstp = (t *) pImage; \
                t *srcp = (t *) tmpImage; \
                srcp += nBand-1; \
                long imacro = 0; \
                for(long y=ystart; y != ystop; y+= yinc) \
                    for(long x=xstart; x != xstop; x+= xinc, imacro++) { \
                        dstp[imacro] = srcp[(y * nBlockXSize + x) * \
                                       poFIT_DS->nBands]; \
                    } \
    }

#define COPY_YFIRST(t) { \
                t *dstp = (t *) pImage; \
                t *srcp = (t *) tmpImage; \
                srcp += nBand-1; \
                long imacro = 0; \
                for(long x=xstart; x != xstop; x+= xinc, imacro++) \
                    for(long y=ystart; y != ystop; y+= yinc) { \
                        dstp[imacro] = srcp[(x * nBlockYSize + y) * \
                                       poFIT_DS->nBands]; \
                    } \
    }

CPLErr FITRasterBand::IReadBlock( int nBlockXOff, int nBlockYOff,
                                  void * pImage )

{
    FITDataset *poFIT_DS = (FITDataset *) poDS;

    uint64 tilenum = 0;

    switch (poFIT_DS->info->space) {
    case 1:
        // iflUpperLeftOrigin - from upper left corner
        // scan right then down
        tilenum = nBlockYOff * numXBlocks + nBlockXOff;
        break;
    case 2:
        // iflUpperRightOrigin - from upper right corner
        // scan left then down
        tilenum = numYBlocks * numXBlocks + (numXBlocks-1-nBlockXOff);
        break;
    case 3:
        // iflLowerRightOrigin - from lower right corner
        // scan left then up
        tilenum = (numYBlocks-1-nBlockYOff) * numXBlocks +
            (numXBlocks-1-nBlockXOff);
        break;
    case 4:
        // iflLowerLeftOrigin - from lower left corner
        // scan right then up
        tilenum = (numYBlocks-1-nBlockYOff) * numXBlocks + nBlockXOff;
        break;
    case 5:
        // iflLeftUpperOrigin -* from upper left corner
        // scan down then right
        tilenum = nBlockXOff * numYBlocks + nBlockYOff;
        break;
    case 6:
        // iflRightUpperOrigin - from upper right corner
        // scan down then left
        tilenum = (numXBlocks-1-nBlockXOff) * numYBlocks + nBlockYOff;
        break;
    case 7:
        // iflRightLowerOrigin - from lower right corner
        // scan up then left
        tilenum = nBlockXOff * numYBlocks + (numYBlocks-1-nBlockYOff);
        break;
    case 8:
        // iflLeftLowerOrigin -* from lower left corner
        // scan up then right
        tilenum = (numXBlocks-1-nBlockXOff) * numYBlocks +
            (numYBlocks-1-nBlockYOff);
        break;
    default:
        CPLError(CE_Failure, CPLE_NotSupported,
                 "FIT - unrecognized image space %i",
                 poFIT_DS->info->space);
        return CE_Failure;
    } // switch

    uint64 offset = poFIT_DS->info->dataOffset + recordSize * tilenum;
//     CPLDebug("FIT", "%i RasterBand::IReadBlock %i %i (out of %i %i) -- %i",
//              poFIT_DS->info->space,
//              nBlockXOff, nBlockYOff, numXBlocks, numYBlocks, tilenum);

    if ( VSIFSeekL( poFIT_DS->fp, offset, SEEK_SET ) == -1 ) {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "FIT - 64bit file seek failure, handle=%p", poFIT_DS->fp );
            return CE_Failure;
    }

    // XXX - should handle status
    // fast path is single component (ll?) - no copy needed
    int fastpath = FALSE;

    if ((poFIT_DS->nBands == 1) && (poFIT_DS->info->space == 1)) // upper left
        fastpath = TRUE;

    size_t nRead = 0;
    char *p = nullptr;
    if (! fastpath) {
        nRead = VSIFReadL( tmpImage, recordSize, 1, poFIT_DS->fp );
        // offset to correct component to swap
        p = (char *) tmpImage + nBand-1;
    }
    else {
        nRead = VSIFReadL( pImage, recordSize, 1, poFIT_DS->fp );
        p = (char *) pImage;
    }
    if( nRead != 1 )
    {
        CPLError(CE_Failure, CPLE_FileIO, "Cannot read record");
        return CE_Failure;
    }

#ifdef swapping
    unsigned long i = 0;

    switch(bytesPerComponent) {
    case 1:
        // do nothing
        break;
    case 2:
        for(i=0; i < recordSize; i+= bytesPerPixel)
            gst_swap16(p + i);
        break;
    case 4:
        for(i=0; i < recordSize; i+= bytesPerPixel)
            gst_swap32(p + i);
        break;
    case 8:
        for(i=0; i < recordSize; i+= bytesPerPixel)
            gst_swap64(p + i);
        break;
    default:
        CPLError(CE_Failure, CPLE_NotSupported,
                 "FITRasterBand::IReadBlock unsupported bytesPerPixel %lu",
                 bytesPerComponent);
    } // switch
#else
    (void) p; // avoid warnings.
#endif // swapping

    if (! fastpath) {
        long xinc, yinc, xstart, ystart, xstop, ystop;
        if (poFIT_DS->info->space <= 4) {
            // scan left/right first

            switch (poFIT_DS->info->space) {
            case 1:
                // iflUpperLeftOrigin - from upper left corner
                // scan right then down
                xinc = 1;
                yinc = 1;
                break;
            case 2:
                // iflUpperRightOrigin - from upper right corner
                // scan left then down
                xinc = -1;
                yinc = 1;
                break;
            case 3:
                // iflLowerRightOrigin - from lower right corner
                // scan left then up
                xinc = -1;
                yinc = -1;
                break;
            case 4:
                // iflLowerLeftOrigin - from lower left corner
                // scan right then up
                xinc = 1;
                yinc = -1;
               break;
            default:
                CPLError(CE_Failure, CPLE_NotSupported,
                         "FIT - unrecognized image space %i",
                         poFIT_DS->info->space);
                xinc = 1;
                yinc = 1;
            } // switch

            if (xinc == 1) {
                xstart = 0;
                xstop = nBlockXSize;
            }
            else {
                xstart = nBlockXSize-1;
                xstop = -1;
            }
            if (yinc == 1) {
                ystart = 0;
                ystop = nBlockYSize;
            }
            else {
                int localBlockYSize = nBlockYSize;
                long maxy_full =
                    (long) floor(poFIT_DS->info->ySize / (double) nBlockYSize);
                if (nBlockYOff >= maxy_full)
                    localBlockYSize = poFIT_DS->info->ySize % nBlockYSize;
                ystart = localBlockYSize-1;
                ystop = -1;
            }

            switch(bytesPerComponent) {
            case 1:
                COPY_XFIRST(char);
                break;
            case 2:
                COPY_XFIRST(uint16);
                break;
            case 4:
                COPY_XFIRST(uint32);
                break;
            case 8:
                COPY_XFIRST(uint64);
                break;
            default:
                CPLError(CE_Failure, CPLE_NotSupported,
                         "FITRasterBand::IReadBlock unsupported "
                         "bytesPerComponent %lu", bytesPerComponent);
            } // switch
        } // Scan left/right first.
        else
        {
            // Scan up/down first.
            switch (poFIT_DS->info->space)
            {
            case 5:
                // iflLeftUpperOrigin -* from upper left corner
                // scan down then right
                xinc = 1;
                yinc = 1;
                break;
            case 6:
                // iflRightUpperOrigin - from upper right corner
                // scan down then left
                xinc = -1;
                yinc = 1;
                break;
            case 7:
                // iflRightLowerOrigin - from lower right corner
                // scan up then left
                xinc = -1;
                yinc = -1;
                break;
            case 8:
                // iflLeftLowerOrigin -* from lower left corner
                // scan up then right
                xinc = 1;
                yinc = -1;
                break;
            default:
                CPLError(CE_Failure, CPLE_NotSupported,
                         "FIT - unrecognized image space %i",
                         poFIT_DS->info->space);
                xinc = 1;
                yinc = 1;
            } // switch

            if (xinc == 1) {
                xstart = 0;
                xstop = nBlockXSize;
            }
            else {
                int localBlockXSize = nBlockXSize;
                long maxx_full =
                    (long) floor(poFIT_DS->info->xSize / (double) nBlockXSize);
                if (nBlockXOff >= maxx_full)
                    localBlockXSize = poFIT_DS->info->xSize % nBlockXSize;
                xstart = localBlockXSize-1;
                xstop = -1;
            }
            if (yinc == 1) {
                ystart = 0;
                ystop = nBlockYSize;
            }
            else {
                ystart = nBlockYSize-1;
                ystop = -1;
            }

            switch(bytesPerComponent) {
            case 1:
                COPY_YFIRST(char);
                break;
            case 2:
                COPY_YFIRST(uint16);
                break;
            case 4:
                COPY_YFIRST(uint32);
                break;
            case 8:
                COPY_YFIRST(uint64);
                break;
            default:
                CPLError(CE_Failure, CPLE_NotSupported,
                         "FITRasterBand::IReadBlock unsupported "
                         "bytesPerComponent %lu", bytesPerComponent);
            } // switch
        } // Scan up/down first.
    } // !fastpath
    return CE_None;
}

#if 0
/************************************************************************/
/*                             ReadBlock()                              */
/************************************************************************/

CPLErr FITRasterBand::ReadBlock( int nBlockXOff, int nBlockYOff,
                                 void * pImage )

{
    FITDataset *poFIT_DS = (FITDataset *) poDS;

    return CE_None;
}

/************************************************************************/
/*                             WriteBlock()                             */
/************************************************************************/

CPLErr FITRasterBand::WriteBlock( int nBlockXOff, int nBlockYOff,
                                 void * pImage )

{
    FITDataset *poFIT_DS = (FITDataset *) poDS;

    return CE_None;
}
#endif

/************************************************************************/
/*                             GetMinimum()                             */
/************************************************************************/

double FITRasterBand::GetMinimum( int *pbSuccess )
{
    FITDataset *poFIT_DS = (FITDataset *) poDS;

    if ((! poFIT_DS) || (! poFIT_DS->info))
        return GDALRasterBand::GetMinimum( pbSuccess );

    if (pbSuccess)
        *pbSuccess = TRUE;

    if (poFIT_DS->info->version &&
        STARTS_WITH_CI((const char *) &(poFIT_DS->info->version), "02")) {
        return poFIT_DS->info->minValue;
    }

    return GDALRasterBand::GetMinimum( pbSuccess );
}

/************************************************************************/
/*                             GetMaximum()                             */
/************************************************************************/

double FITRasterBand::GetMaximum( int *pbSuccess )
{
    FITDataset *poFIT_DS = (FITDataset *) poDS;

    if ((! poFIT_DS) || (! poFIT_DS->info))
        return GDALRasterBand::GetMaximum( pbSuccess );

    if (pbSuccess)
        *pbSuccess = TRUE;

    if (STARTS_WITH_CI((const char *) &poFIT_DS->info->version, "02")) {
        return poFIT_DS->info->maxValue;
    }

    return GDALRasterBand::GetMaximum( pbSuccess );
}

/************************************************************************/
/*                       GetColorInterpretation()                       */
/************************************************************************/

GDALColorInterp FITRasterBand::GetColorInterpretation()
{
    FITDataset *poFIT_DS = (FITDataset *) poDS;

    if ((! poFIT_DS) || (! poFIT_DS->info))
        return GCI_Undefined;

    switch(poFIT_DS->info->cm) {
    case 1: // iflNegative - inverted luminance (min value is white)
        CPLError( CE_Warning, CPLE_NotSupported,
                  "FIT - color model Negative not supported - ignoring model");
            return GCI_Undefined;

    case 2: // iflLuminance - luminance
        if (poFIT_DS->nBands != 1) {
            CPLError( CE_Failure, CPLE_NotSupported,
                      "FIT - color model Luminance mismatch with %i bands",
                      poFIT_DS->nBands);
            return GCI_Undefined;
        }
        switch (nBand) {
        case 1:
            return GCI_GrayIndex;
        default:
            CPLError( CE_Failure, CPLE_NotSupported,
                      "FIT - color model Luminance unknown band %i", nBand);
            return GCI_Undefined;
        } // switch nBand

    case 3: // iflRGB - full color (Red, Green, Blue triplets)
        if (poFIT_DS->nBands != 3) {
            CPLError( CE_Failure, CPLE_NotSupported,
                      "FIT - color model RGB mismatch with %i bands",
                      poFIT_DS->nBands);
            return GCI_Undefined;
        }
        switch (nBand) {
        case 1:
            return GCI_RedBand;
        case 2:
            return GCI_GreenBand;
        case 3:
            return GCI_BlueBand;
        default:
            CPLError( CE_Failure, CPLE_NotSupported,
                      "FIT - color model RGB unknown band %i", nBand);
            return GCI_Undefined;
        } // switch nBand

    case 4: // iflRGBPalette - color mapped values
        CPLError( CE_Warning, CPLE_NotSupported,
                  "FIT - color model  RGBPalette not supported - "
                  "ignoring model");
            return GCI_Undefined;

    case 5: // iflRGBA - full color with transparency (alpha channel)
        if (poFIT_DS->nBands != 4) {
            CPLError( CE_Failure, CPLE_NotSupported,
                      "FIT - color model RGBA mismatch with %i bands",
                      poFIT_DS->nBands);
            return GCI_Undefined;
        }
        switch (nBand) {
        case 1:
            return GCI_RedBand;
        case 2:
            return GCI_GreenBand;
        case 3:
            return GCI_BlueBand;
        case 4:
            return GCI_AlphaBand;
        default:
            CPLError( CE_Failure, CPLE_NotSupported,
                      "FIT - color model RGBA unknown band %i", nBand);
            return GCI_Undefined;
        } // switch nBand

    case 6: // iflHSV - Hue, Saturation, Value
        if (poFIT_DS->nBands != 3) {
            CPLError( CE_Failure, CPLE_NotSupported,
                      "FIT - color model HSV mismatch with %i bands",
                      poFIT_DS->nBands);
            return GCI_Undefined;
        }
        switch (nBand) {
        case 1:
            return GCI_HueBand;
        case 2:
            return GCI_SaturationBand;
        case 3:
            return GCI_LightnessBand;
        default:
            CPLError( CE_Failure, CPLE_NotSupported,
                      "FIT - color model HSV unknown band %i", nBand);
            return GCI_Undefined;
        } // switch nBand

    case 7: // iflCMY - Cyan, Magenta, Yellow
        if (poFIT_DS->nBands != 3) {
            CPLError( CE_Failure, CPLE_NotSupported,
                      "FIT - color model CMY mismatch with %i bands",
                      poFIT_DS->nBands);
            return GCI_Undefined;
        }
        switch (nBand) {
        case 1:
            return GCI_CyanBand;
        case 2:
            return GCI_MagentaBand;
        case 3:
            return GCI_YellowBand;
        default:
            CPLError( CE_Failure, CPLE_NotSupported,
                      "FIT - color model CMY unknown band %i", nBand);
            return GCI_Undefined;
        } // switch nBand

    case 8: // iflCMYK - Cyan, Magenta, Yellow, Black
        if (poFIT_DS->nBands != 4) {
            CPLError( CE_Failure, CPLE_NotSupported,
                      "FIT - color model CMYK mismatch with %i bands",
                      poFIT_DS->nBands);
            return GCI_Undefined;
        }
        switch (nBand) {
        case 1:
            return GCI_CyanBand;
        case 2:
            return GCI_MagentaBand;
        case 3:
            return GCI_YellowBand;
        case 4:
            return GCI_BlackBand;
        default:
            CPLError( CE_Failure, CPLE_NotSupported,
                      "FIT - color model CMYK unknown band %i", nBand);
            return GCI_Undefined;
        } // switch nBand

    case 9: // iflBGR - full color (ordered Blue, Green, Red)
        if (poFIT_DS->nBands != 3) {
            CPLError( CE_Failure, CPLE_NotSupported,
                      "FIT - color model BGR mismatch with %i bands",
                      poFIT_DS->nBands);
            return GCI_Undefined;
        }
        switch (nBand) {
        case 1:
            return GCI_BlueBand;
        case 2:
            return GCI_GreenBand;
        case 3:
            return GCI_RedBand;
        default:
            CPLError( CE_Failure, CPLE_NotSupported,
                      "FIT - color model BGR unknown band %i", nBand);
            return GCI_Undefined;
        } // switch nBand

    case 10: // iflABGR - Alpha, Blue, Green, Red (SGI frame buffers)
        if (poFIT_DS->nBands != 4) {
            CPLError( CE_Failure, CPLE_NotSupported,
                      "FIT - color model ABGR mismatch with %i bands",
                      poFIT_DS->nBands);
            return GCI_Undefined;
        }
        switch (nBand) {
        case 1:
            return GCI_AlphaBand;
        case 2:
            return GCI_BlueBand;
        case 3:
            return GCI_GreenBand;
        case 4:
            return GCI_RedBand;
        default:
            CPLError( CE_Failure, CPLE_NotSupported,
                      "FIT - color model ABGR unknown band %i", nBand);
            return GCI_Undefined;
        } // switch nBand

    case 11: // iflMultiSpectral - multi-spectral data, arbitrary number of
        // chans
        return GCI_Undefined;

    case 12: // iflYCC PhotoCD color model (Luminance, Chrominance)
        CPLError( CE_Warning, CPLE_NotSupported,
                  "FIT - color model YCC not supported - ignoring model");
            return GCI_Undefined;

    case 13: // iflLuminanceAlpha - Luminance plus alpha
        if (poFIT_DS->nBands != 2) {
            CPLError( CE_Failure, CPLE_NotSupported,
                      "FIT - color model LuminanceAlpha mismatch with "
                      "%i bands",
                      poFIT_DS->nBands);
            return GCI_Undefined;
        }
        switch (nBand) {
        case 1:
            return GCI_GrayIndex;
        case 2:
            return GCI_AlphaBand;
        default:
            CPLError( CE_Failure, CPLE_NotSupported,
                      "FIT - color model LuminanceAlpha unknown band %i",
                      nBand);
            return GCI_Undefined;
        } // switch nBand

    default:
        CPLError( CE_Warning, CPLE_NotSupported,
                  "FIT - unrecognized color model %i - ignoring model",
                  poFIT_DS->info->cm);
        return GCI_Undefined;
    } // switch
}

/************************************************************************/
/*                             FITDataset()                             */
/************************************************************************/

FITDataset::FITDataset() :
    fp( nullptr ),
    info( nullptr )
{
    adfGeoTransform[0] = 0.0; // x origin (top left corner)
    adfGeoTransform[1] = 1.0; // x pixel size
    adfGeoTransform[2] = 0.0;
    adfGeoTransform[3] = 0.0; // y origin (top left corner)
    adfGeoTransform[4] = 0.0;
    adfGeoTransform[5] = 1.0; // y pixel size
}

/************************************************************************/
/*                             ~FITDataset()                             */
/************************************************************************/

FITDataset::~FITDataset()
{
    FlushCache(true);
    if( info )
        delete(info);
    if( fp )
    {
        if( VSIFCloseL(fp) != 0 )
        {
            CPLError(CE_Failure, CPLE_FileIO, "I/O error");
        }
    }
}

// simple guard object to delete memory
// when the guard goes out of scope
template< class T >
class DeleteGuard
{
public:
    explicit DeleteGuard( T *p ) : _ptr( p ) { }
    ~DeleteGuard()
    {
        delete _ptr;
    }

    T *take()
    {
        T *tmp = _ptr;
        _ptr = nullptr;
        return tmp;
    }

private:
    T *_ptr;
    // prevent default copy constructor and assignment operator
    DeleteGuard( const DeleteGuard & );
    DeleteGuard &operator=( const DeleteGuard & );
};

// simple guard object to free memory
// when the guard goes out of scope
template< class T >
class FreeGuard
{
public:
    explicit FreeGuard( T *p ) : _ptr( p ) { }
    ~FreeGuard()
    {
        if ( _ptr )
            free( _ptr );
    }

    T *take()
    {
        T *tmp = _ptr;
        _ptr = NULL;
        return tmp;
    }

private:
    T *_ptr;
    // prevent default copy constructor and assignment operator
    FreeGuard( const FreeGuard & );
    FreeGuard &operator=( const FreeGuard & );
};

/************************************************************************/
/*                                Open()                                */
/************************************************************************/

GDALDataset *FITDataset::Open( GDALOpenInfo * poOpenInfo )
{
/* -------------------------------------------------------------------- */
/*      First we check to see if the file has the expected header       */
/*      bytes.                                                          */
/* -------------------------------------------------------------------- */

    if( poOpenInfo->nHeaderBytes < 5 || poOpenInfo->fpL == nullptr)
        return nullptr;

    if( !STARTS_WITH_CI((const char *) poOpenInfo->pabyHeader, "IT01") &&
        !STARTS_WITH_CI((const char *) poOpenInfo->pabyHeader, "IT02") )
        return nullptr;

    if( poOpenInfo->eAccess == GA_Update )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "The FIT driver does not support update access to existing"
                  " files.\n" );
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Create a corresponding GDALDataset.                             */
/* -------------------------------------------------------------------- */
    FITDataset *poDS = new FITDataset();
    DeleteGuard<FITDataset> guard( poDS );
    poDS->eAccess = poOpenInfo->eAccess;
    poDS->fp = poOpenInfo->fpL;
    poOpenInfo->fpL = nullptr;

    poDS->info = new FITinfo;
    FITinfo *info = poDS->info;

/* -------------------------------------------------------------------- */
/*      Read other header values.                                       */
/* -------------------------------------------------------------------- */
    FIThead02 *head = (FIThead02 *) poOpenInfo->pabyHeader;

    // extract the image attributes from the file header
    if (STARTS_WITH_CI((const char *) &head->version, "02")) {
        // incomplete header
        if( poOpenInfo->nHeaderBytes < (signed) sizeof(FIThead02) )
            return nullptr;

        CPLDebug("FIT", "Loading file with header version 02");

        gst_swapb(head->minValue);
        info->minValue = head->minValue;
        gst_swapb(head->maxValue);
        info->maxValue = head->maxValue;
        gst_swapb(head->dataOffset);
        info->dataOffset = head->dataOffset;

        info->userOffset = sizeof(FIThead02);
    }
    else if (STARTS_WITH_CI((const char *) &head->version, "01")) {
        // incomplete header
        if( poOpenInfo->nHeaderBytes < (signed) sizeof(FIThead01) )
            return nullptr;

        CPLDebug("FIT", "Loading file with header version 01");

        // map old style header into new header structure
        FIThead01* head01 = (FIThead01*)head;
        gst_swapb(head->dataOffset);
        info->dataOffset = head01->dataOffset;

        info->userOffset = sizeof(FIThead01);
    }
    else {
        // unrecognized header version
        CPLError( CE_Failure, CPLE_NotSupported,
                  "FIT - unsupported header version %.2s\n",
                  (const char*) &head->version);
        return nullptr;
    }

    CPLDebug("FIT", "userOffset %i, dataOffset %i",
             info->userOffset, info->dataOffset);

    info->magic = head->magic;
    info->version = head->version;

    gst_swapb(head->xSize);
    info->xSize = head->xSize;
    gst_swapb(head->ySize);
    info->ySize = head->ySize;
    gst_swapb(head->zSize);
    info->zSize = head->zSize;
    gst_swapb(head->cSize);
    info->cSize = head->cSize;
    gst_swapb(head->dtype);
    info->dtype = head->dtype;
    gst_swapb(head->order);
    info->order = head->order;
    gst_swapb(head->space);
    info->space = head->space;
    gst_swapb(head->cm);
    info->cm = head->cm;
    gst_swapb(head->xPageSize);
    info->xPageSize = head->xPageSize;
    gst_swapb(head->yPageSize);
    info->yPageSize = head->yPageSize;
    gst_swapb(head->zPageSize);
    info->zPageSize = head->zPageSize;
    gst_swapb(head->cPageSize);
    info->cPageSize = head->cPageSize;

    CPLDebug("FIT", "size %i %i %i %i, pageSize %i %i %i %i",
             info->xSize, info->ySize, info->zSize, info->cSize,
             info->xPageSize, info->yPageSize, info->zPageSize,
             info->cPageSize);

    CPLDebug("FIT", "dtype %i order %i space %i cm %i",
             info->dtype, info->order, info->space, info->cm);

    /**************************/

    poDS->nRasterXSize = head->xSize;
    poDS->nRasterYSize = head->ySize;

    if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize) ||
        !GDALCheckBandCount(head->cSize, FALSE) ||
        head->xPageSize == 0 ||
        head->yPageSize == 0)
    {
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Verify all "unused" header values.                              */
/* -------------------------------------------------------------------- */

    if( info->zSize != 1 )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "FIT driver - unsupported zSize %i\n", info->zSize);
        return nullptr;
    }

    if( info->order != 1 ) // interleaved - RGBRGB
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "FIT driver - unsupported order %i\n", info->order);
        return nullptr;
    }

    if( info->zPageSize != 1 )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "FIT driver - unsupported zPageSize %i\n", info->zPageSize);
        return nullptr;
    }

    if( info->cPageSize != info->cSize )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "FIT driver - unsupported cPageSize %i (!= %i)\n",
                  info->cPageSize, info->cSize);
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Create band information objects.                                */
/* -------------------------------------------------------------------- */
    // Verified by above GDALCheckBandCount()
    // coverity[tainted_data]
    for( int i = 0; i < (int)head->cSize; i++ )
    {
        FITRasterBand* poBand = new FITRasterBand( poDS, i+1, (int)head->cSize );
        poDS->SetBand( i+1,  poBand);
        if( poBand->tmpImage == nullptr )
            return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Initialize any PAM information.                                 */
/* -------------------------------------------------------------------- */
    poDS->SetDescription( poOpenInfo->pszFilename );
    poDS->TryLoadXML();

/* -------------------------------------------------------------------- */
/*      Check for external overviews.                                   */
/* -------------------------------------------------------------------- */
    poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename, poOpenInfo->GetSiblingFiles() );

    return guard.take();
}

/************************************************************************/
/*                           FITCreateCopy()                            */
/************************************************************************/

static GDALDataset *FITCreateCopy(const char * pszFilename,
                                  GDALDataset *poSrcDS,
                                  int bStrict, char ** papszOptions,
                                  GDALProgressFunc pfnProgress,
                                  void * pProgressData )
{
    CPLDebug("FIT", "CreateCopy %s - %i", pszFilename, bStrict);

    int nBands = poSrcDS->GetRasterCount();
    if (nBands == 0)
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "FIT driver does not support source dataset with zero band.\n");
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Create the dataset.                                             */
/* -------------------------------------------------------------------- */
    if( !pfnProgress( 0.0, nullptr, pProgressData ) )
    {
        CPLError( CE_Failure, CPLE_UserInterrupt, "User terminated" );
        return nullptr;
    }

    VSILFILE *fpImage = VSIFOpenL( pszFilename, "wb" );
    if( fpImage == nullptr )
    {
        CPLError( CE_Failure, CPLE_OpenFailed,
                  "FIT - unable to create file %s.\n",
                  pszFilename );
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Generate header.                                                */
/* -------------------------------------------------------------------- */
    // XXX - should FIT_PAGE_SIZE be based on file page size ??

    const size_t size = std::max(sizeof(FIThead02), FIT_PAGE_SIZE);
    FIThead02 *head = (FIThead02 *) malloc(size);
    FreeGuard<FIThead02> guardHead( head );

    // clean header so padding (past real header) is all zeros
    memset( head, 0, size );

    memcpy((char *) &head->magic, "IT", 2);
    memcpy((char *) &head->version, "02", 2);

    head->xSize = poSrcDS->GetRasterXSize();
    gst_swapb(head->xSize);
    head->ySize = poSrcDS->GetRasterYSize();
    gst_swapb(head->ySize);
    head->zSize = 1;
    gst_swapb(head->zSize);

    head->cSize = nBands;
    gst_swapb(head->cSize);

    GDALRasterBand *firstBand = poSrcDS->GetRasterBand(1);
    if (! firstBand) {
        CPL_IGNORE_RET_VAL(VSIFCloseL(fpImage));
        return nullptr;
    }

    head->dtype = fitGetDataType(firstBand->GetRasterDataType());
    if (! head->dtype) {
        CPL_IGNORE_RET_VAL(VSIFCloseL(fpImage));
        return nullptr;
    }
    gst_swapb(head->dtype);
    head->order = 1; // interleaved - RGBRGB
    gst_swapb(head->order);
    head->space = 1; // upper left
    gst_swapb(head->space);

    // XXX - need to check all bands
    head->cm = fitGetColorModel(firstBand->GetColorInterpretation(), nBands);
    gst_swapb(head->cm);

    int blockX, blockY;
    firstBand->GetBlockSize(&blockX, &blockY);
    blockX = std::min(blockX, poSrcDS->GetRasterXSize());
    blockY = std::min(blockY, poSrcDS->GetRasterYSize());
    int nDTSize = GDALGetDataTypeSizeBytes(firstBand->GetRasterDataType());
    try
    {
        CPL_IGNORE_RET_VAL(
            CPLSM(blockX) * CPLSM(blockY) * CPLSM(nDTSize) * CPLSM(nBands));
        CPLDebug("FIT write", "inherited block size %ix%i", blockX, blockY);
    }
    catch( ... )
    {
        blockX = std::min(256, poSrcDS->GetRasterXSize());
        blockY = std::min(256, poSrcDS->GetRasterYSize());
    }

    if( CSLFetchNameValue(papszOptions,"PAGESIZE") != nullptr )
    {
        const char *str = CSLFetchNameValue(papszOptions,"PAGESIZE");
        int newBlockX, newBlockY;
        sscanf(str, "%i,%i", &newBlockX, &newBlockY);
        if (newBlockX > 0 && newBlockY > 0) {
            blockX = newBlockX;
            blockY = newBlockY;
            try
            {
                CPL_IGNORE_RET_VAL(
                    CPLSM(blockX) * CPLSM(blockY) * CPLSM(nDTSize) * CPLSM(nBands));
            }
            catch( ... )
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Too big values in PAGESIZE");
                CPL_IGNORE_RET_VAL(VSIFCloseL(fpImage));
                return nullptr;
            }
        }
        else {
            CPLError(CE_Failure, CPLE_OpenFailed,
                     "FIT - Unable to parse option PAGESIZE values [%s]", str);
        }
    }

    // XXX - need to do lots of checking of block size
    // * provide ability to override block size with options
    // * handle non-square block size (like scanline)
    //   - probably default from non-tiled image - have default block size
    // * handle block size bigger than image size
    // * undesirable block size (non power of 2, others?)
    // * mismatched block sizes for different bands
    // * image that isn't even pages (i.e. partially empty pages at edge)
    CPLDebug("FIT write", "using block size %ix%i", blockX, blockY);

    head->xPageSize = blockX;
    gst_swapb(head->xPageSize);
    head->yPageSize = blockY;
    gst_swapb(head->yPageSize);
    head->zPageSize = 1;
    gst_swapb(head->zPageSize);
    head->cPageSize = nBands;
    gst_swapb(head->cPageSize);

    // XXX - need to check all bands
    head->minValue = firstBand->GetMinimum();
    gst_swapb(head->minValue);
    // XXX - need to check all bands
    head->maxValue = firstBand->GetMaximum();
    gst_swapb(head->maxValue);
    head->dataOffset = static_cast<unsigned int>(size);
    gst_swapb(head->dataOffset);

    CPL_IGNORE_RET_VAL(VSIFWriteL(head, size, 1, fpImage));

/* -------------------------------------------------------------------- */
/*      Loop over image, copying image data.                            */
/* -------------------------------------------------------------------- */
    unsigned long bytesPerPixel = nBands * nDTSize;

    size_t pageBytes = blockX * blockY * bytesPerPixel;
    char *output = (char *) calloc(1, pageBytes);
    if (! output)
    {
        CPLError(CE_Failure, CPLE_OutOfMemory,
                 "FITRasterBand couldn't allocate %lu bytes",
                 static_cast<unsigned long>(pageBytes));
        CPL_IGNORE_RET_VAL(VSIFCloseL(fpImage));
        return nullptr;
    }
    FreeGuard<char> guardOutput( output );

    long maxx = (long) ceil(poSrcDS->GetRasterXSize() / (double) blockX);
    long maxy = (long) ceil(poSrcDS->GetRasterYSize() / (double) blockY);
    long maxx_full = (long) floor(poSrcDS->GetRasterXSize() / (double) blockX);
    long maxy_full = (long) floor(poSrcDS->GetRasterYSize() / (double) blockY);

    CPLDebug("FIT", "about to write %ld x %ld blocks", maxx, maxy);

    for(long y=0; y < maxy; y++)
        for(long x=0; x < maxx; x++) {
            long readX = blockX;
            long readY = blockY;
            int do_clean = FALSE;

            // handle cases where image size isn't an exact multiple
            // of page size
            if (x >= maxx_full) {
                readX = poSrcDS->GetRasterXSize() % blockX;
                do_clean = TRUE;
            }
            if (y >= maxy_full) {
                readY = poSrcDS->GetRasterYSize() % blockY;
                do_clean = TRUE;
            }

            // clean out image if only doing partial reads
            if (do_clean)
                memset( output, 0, pageBytes );

            for( int iBand = 0; iBand < nBands; iBand++ ) {
                GDALRasterBand * poBand = poSrcDS->GetRasterBand( iBand+1 );
                CPLErr eErr =
                    poBand->RasterIO( GF_Read, // eRWFlag
                                      static_cast<int>(x * blockX), // nXOff
                                      static_cast<int>(y * blockY), // nYOff
                                      static_cast<int>(readX), // nXSize
                                      static_cast<int>(readY), // nYSize
                                      output + iBand * nDTSize,
                                      // pData
                                      blockX, // nBufXSize
                                      blockY, // nBufYSize
                                      firstBand->GetRasterDataType(),
                                      // eBufType
                                      bytesPerPixel, // nPixelSpace
                                      bytesPerPixel * blockX, nullptr); // nLineSpace
                if (eErr != CE_None)
                {
                    CPLError(CE_Failure, CPLE_FileIO,
                             "FIT write - CreateCopy got read error %i", eErr);
                    CPL_IGNORE_RET_VAL(VSIFCloseL( fpImage ));
                    VSIUnlink( pszFilename );
                    return nullptr;
                }
            } // for iBand

#ifdef swapping
            char *p = output;
            unsigned long i;
            switch(nDTSize) {
            case 1:
                // do nothing
                break;
            case 2:
                for(i=0; i < pageBytes; i+= nDTSize)
                    gst_swap16(p + i);
                break;
            case 4:
                for(i=0; i < pageBytes; i+= nDTSize)
                    gst_swap32(p + i);
                break;
            case 8:
                for(i=0; i < pageBytes; i+= nDTSize)
                    gst_swap64(p + i);
                break;
            default:
                CPLError(CE_Failure, CPLE_NotSupported,
                         "FIT write - unsupported bytesPerPixel %d",
                         nDTSize);
            } // switch
#endif // swapping

            if( VSIFWriteL(output, 1, pageBytes, fpImage) != pageBytes )
            {
                CPLError( CE_Failure, CPLE_FileIO, "Write failed" );
                CPL_IGNORE_RET_VAL(VSIFCloseL( fpImage ));
                VSIUnlink( pszFilename );
                return nullptr;
            }

            double perc = ((double) (y * maxx + x)) / (maxx * maxy);
            if( !pfnProgress( perc, nullptr, pProgressData ) )
            {
                CPLError( CE_Failure, CPLE_UserInterrupt, "User terminated" );
                //free(output);
                CPL_IGNORE_RET_VAL(VSIFCloseL( fpImage ));
                VSIUnlink( pszFilename );
                return nullptr;
            }
        } // for x

    //free(output);

    CPL_IGNORE_RET_VAL(VSIFCloseL( fpImage ));

    pfnProgress( 1.0, nullptr, pProgressData );

/* -------------------------------------------------------------------- */
/*      Re-open dataset, and copy any auxiliary pam information.         */
/* -------------------------------------------------------------------- */
    GDALPamDataset *poDS = (GDALPamDataset *)
        GDALOpen( pszFilename, GA_ReadOnly );

    if( poDS )
        poDS->CloneInfo( poSrcDS, GCIF_PAM_DEFAULT );

    return poDS;
}

/************************************************************************/
/*                           GetGeoTransform()                          */
/************************************************************************/

// CPLErr FITDataset::GetGeoTransform( double * padfTransform )
// {
//     CPLDebug("FIT", "FITDataset::GetGeoTransform");
//     memcpy( padfTransform, adfGeoTransform, sizeof(double) * 6 );
//     return CE_None;
// }

/************************************************************************/
/*                          GDALRegister_FIT()                          */
/************************************************************************/

void GDALRegister_FIT()

{
    if( GDALGetDriverByName( "FIT" ) != nullptr )
        return;

    GDALDriver *poDriver = new GDALDriver();

    poDriver->SetDescription( "FIT" );
    poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
    poDriver->SetMetadataItem( GDAL_DMD_LONGNAME, "FIT Image" );
    poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "drivers/raster/fit.html" );
    poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "" );
    poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );

    poDriver->pfnOpen = FITDataset::Open;
    poDriver->pfnCreateCopy = FITCreateCopy;
    poDriver->SetMetadataItem( GDAL_DMD_CREATIONDATATYPES,
                               "Byte UInt16 Int16 UInt32 Int32 "
                               "Float32 Float64" );

    GetGDALDriverManager()->RegisterDriver( poDriver );
}
