/******************************************************************************
 *
 * Project:  MSG Driver
 * Purpose:  GDALDataset driver for MSG translator for read support.
 * Author:   Bas Retsios, retsios@itc.nl
 *
 ******************************************************************************
 * Copyright (c) 2004, ITC
 * Copyright (c) 2009, 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_port.h"  // Must be first.

#include "gdal_frmts.h"
#include "msgdataset.h"
#include "msgdrivercore.h"
#include "prologue.h"
#include "xritheaderparser.h"
#include "reflectancecalculator.h"

#include "PublicDecompWT_headers.h"

#include <memory>
#include <vector>

#if _MSC_VER > 1000
#include <io.h>
#else
#include <stdio.h>
#endif

const double MSGDataset::rCentralWvl[12] = {0.635,  0.810,  1.640,  3.900,
                                            6.250,  7.350,  8.701,  9.660,
                                            10.800, 12.000, 13.400, 0.750};
const double MSGDataset::rVc[12] = {-1,       -1,       -1,       2569.094,
                                    1598.566, 1362.142, 1149.083, 1034.345,
                                    930.659,  839.661,  752.381,  -1};
const double MSGDataset::rA[12] = {-1,     -1,     -1,     0.9959,
                                   0.9963, 0.9991, 0.9996, 0.9999,
                                   0.9983, 0.9988, 0.9981, -1};
const double MSGDataset::rB[12] = {-1,    -1,    -1,    3.471, 2.219, 0.485,
                                   0.181, 0.060, 0.627, 0.397, 0.576, -1};
const int MSGDataset::iCentralPixelVIS_IR = 1856;  // center pixel VIS and IR
const int MSGDataset::iCentralPixelHRV = 5566;     // center pixel HRV
int MSGDataset::iCurrentSatelliteHint =
    1;  // satellite number hint 1,2,3,4 for MSG1, MSG2, MSG3 and MSG4
const char *MSGDataset::metadataDomain = "msg";  // the metadata domain

#define MAX_SATELLITES 4

/************************************************************************/
/*                    MSGDataset()                                     */
/************************************************************************/

MSGDataset::MSGDataset()

{
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    adfGeoTransform[0] = 0.0;
    adfGeoTransform[1] = 1.0;
    adfGeoTransform[2] = 0.0;
    adfGeoTransform[3] = 0.0;
    adfGeoTransform[4] = 0.0;
    adfGeoTransform[5] = 1.0;
}

/************************************************************************/
/*                    ~MSGDataset()                                     */
/************************************************************************/

MSGDataset::~MSGDataset()

{
    delete poTransform;
}

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

CPLErr MSGDataset::GetGeoTransform(double *padfTransform)

{
    memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
    return CE_None;
}

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

GDALDataset *MSGDataset::Open(GDALOpenInfo *poOpenInfo)

{
    /* -------------------------------------------------------------------- */
    /*     Does this look like a MSG file                                   */
    /* -------------------------------------------------------------------- */
    // if( poOpenInfo->fp == nullptr)
    //   return nullptr;
    //  Do not touch the fp .. it will close by itself if not null after we
    //  return (whether it is recognized as HRIT or not)

    std::string command_line(poOpenInfo->pszFilename);

    MSGCommand command;
    std::string sErr = command.parse(command_line);
    if (sErr.length() > 0)
    {
        if (sErr.compare("-") !=
            0)  // this driver does not recognize this format .. be silent and
            // return false so that another driver can try
            CPLError(CE_Failure, CPLE_AppDefined, "%s", (sErr + "\n").c_str());
        return nullptr;
    }

    /* -------------------------------------------------------------------- */
    /*      Read the prologue.                                              */
    /* -------------------------------------------------------------------- */
    Prologue pp;

    int iCurrentSatellite =
        iCurrentSatelliteHint;  // Start with the hint. It is nice to have but
                                // don't rely on it...

    std::string sPrologueFileName =
        command.sPrologueFileName(iCurrentSatellite, 1);
    bool fPrologueExists = (access(sPrologueFileName.c_str(), 0) == 0);

    // Make sure we're testing for MSG1,2,3 or 4 exactly once, start with the
    // hint which is the most recently used.
    int iTries = 1;
    while (!fPrologueExists && (iTries < MAX_SATELLITES))
    {
        iCurrentSatellite = 1 + iCurrentSatellite % MAX_SATELLITES;
        sPrologueFileName = command.sPrologueFileName(iCurrentSatellite, 1);
        fPrologueExists = (access(sPrologueFileName.c_str(), 0) == 0);
        ++iTries;
    }

    if (fPrologueExists)
    {
        iCurrentSatelliteHint = iCurrentSatellite;  // set the hint
        std::ifstream p_file(sPrologueFileName.c_str(),
                             std::ios::in | std::ios::binary);
        XRITHeaderParser xhp(p_file);
        if (xhp.isValid() && xhp.isPrologue())
            pp.read(p_file);
        p_file.close();
    }
    else
    {
        std::string l_sErr = "The prologue of the data set could not be found "
                             "at the location specified:\n" +
                             sPrologueFileName + "\n";
        CPLError(CE_Failure, CPLE_AppDefined, "%s", l_sErr.c_str());
        return nullptr;
    }

    // We're confident the string is formatted as an MSG command_line

    /* -------------------------------------------------------------------- */
    /*      Create a corresponding GDALDataset.                             */
    /* -------------------------------------------------------------------- */

    MSGDataset *poDS = new MSGDataset();
    poDS->command = command;  // copy it

    /* -------------------------------------------------------------------- */
    /*      Set the current satellite for this DS                           */
    /* -------------------------------------------------------------------- */

    poDS->iCurrentSatellite = iCurrentSatellite;  // copy it

    /* -------------------------------------------------------------------- */
    /*      Capture raster size from MSG prologue and submit it to GDAL     */
    /* -------------------------------------------------------------------- */

    if (command.channel[11] != 0)  // the HRV band
    {
        poDS->nRasterXSize = pp.idr()->ReferenceGridHRV->NumberOfColumns;
        poDS->nRasterYSize =
            abs(pp.idr()->PlannedCoverageHRV->UpperNorthLinePlanned -
                pp.idr()->PlannedCoverageHRV->LowerSouthLinePlanned) +
            1;
    }
    else
    {
        poDS->nRasterXSize =
            abs(pp.idr()->PlannedCoverageVIS_IR->WesternColumnPlanned -
                pp.idr()->PlannedCoverageVIS_IR->EasternColumnPlanned) +
            1;
        poDS->nRasterYSize =
            abs(pp.idr()->PlannedCoverageVIS_IR->NorthernLinePlanned -
                pp.idr()->PlannedCoverageVIS_IR->SouthernLinePlanned) +
            1;
    }

    /* -------------------------------------------------------------------- */
    /*      Set Georeference Information                                    */
    /* -------------------------------------------------------------------- */

    double rPixelSizeX;
    double rPixelSizeY;
    double rMinX;
    double rMaxY;

    if (command.channel[11] != 0)
    {
        rPixelSizeX = 1000 * pp.idr()->ReferenceGridHRV->ColumnDirGridStep;
        rPixelSizeY = 1000 * pp.idr()->ReferenceGridHRV->LineDirGridStep;
        // The MSG Level 1.5 Image Data Format Description (page 22f) defines
        // the center of pixel (5566,5566) from SE as sub satellite point for
        // the HRV channel (0°,0° for operational MSG).
        rMinX = -rPixelSizeX * (pp.idr()->ReferenceGridHRV->NumberOfColumns -
                                iCentralPixelHRV + 0.5);
        rMaxY = rPixelSizeY *
                (pp.idr()->ReferenceGridHRV->NumberOfLines - iCentralPixelHRV +
                 0.5);  // scan direction south -> north
    }
    else
    {
        rPixelSizeX = 1000 * pp.idr()->ReferenceGridVIS_IR->ColumnDirGridStep;
        rPixelSizeY = 1000 * pp.idr()->ReferenceGridVIS_IR->LineDirGridStep;
        // The MSG Level 1.5 Image Data Format Description (page 22f) defines
        // the center of pixel (1856,1856) from SE as sub satellite point for
        // the VIS_IR channels (0°,0° for operational MSG).
        rMinX = -rPixelSizeX * (pp.idr()->ReferenceGridVIS_IR->NumberOfColumns -
                                iCentralPixelVIS_IR + 0.5);
        rMaxY = rPixelSizeY *
                (pp.idr()->ReferenceGridVIS_IR->NumberOfLines -
                 iCentralPixelVIS_IR +
                 0.5);  // The y scan direction is always south -> north
    }
    poDS->adfGeoTransform[0] = rMinX;
    poDS->adfGeoTransform[3] = rMaxY;
    poDS->adfGeoTransform[1] = rPixelSizeX;
    poDS->adfGeoTransform[5] = -rPixelSizeY;
    poDS->adfGeoTransform[2] = 0.0;
    poDS->adfGeoTransform[4] = 0.0;

    /* -------------------------------------------------------------------- */
    /*      Set Projection Information                                      */
    /* -------------------------------------------------------------------- */

    poDS->m_oSRS.SetGEOS(0, 35785831, 0, 0);
    poDS->m_oSRS.SetWellKnownGeogCS(
        "WGS84");  // Temporary line to satisfy ERDAS (otherwise the ellips is
                   // "unnamed"). Eventually this should become the custom a and
                   // b ellips (CGMS).

    // The following are 3 different try-outs for also setting the ellips a and
    // b parameters. We leave them out for now however because this does not
    // work. In gdalwarp, when choosing some specific target SRS, the result is
    // an error message:
    //
    // ERROR 1: geocentric transformation missing z or ellps
    // ERROR 1: GDALWarperOperation::ComputeSourceWindow() failed because
    // the pfnTransformer failed.
    //
    // I can't explain the reason for the message at this time (could be a
    // problem in the way the SRS is set here, but also a bug in Proj.4 or GDAL.
    /*
    poDS->m_oSRS.SetGeogCS( NULL, NULL, NULL, 6378169, 295.488065897, NULL, 0,
    NULL, 0 );

    poDS->m_oSRS.SetGeogCS( "unnamed ellipse", "unknown", "unnamed", 6378169,
    295.488065897, "Greenwich", 0.0);

    poDS->m_oSRS.importFromProj4("+proj=geos +h=35785831 +a=6378169
    +b=6356583.8");
    */

    /* -------------------------------------------------------------------- */
    /*   Create a transformer to LatLon (only for Reflectance calculation)  */
    /* -------------------------------------------------------------------- */

    OGRSpatialReference *poSRSLongLat = poDS->m_oSRS.CloneGeogCS();
    if (poSRSLongLat)
    {
        poSRSLongLat->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
        poDS->poTransform =
            OGRCreateCoordinateTransformation(&(poDS->m_oSRS), poSRSLongLat);
        delete poSRSLongLat;
    }
    /* -------------------------------------------------------------------- */
    /*      Set the radiometric calibration parameters.                     */
    /* -------------------------------------------------------------------- */

    memcpy(poDS->rCalibrationOffset, pp.rpr()->Cal_Offset, sizeof(double) * 12);
    memcpy(poDS->rCalibrationSlope, pp.rpr()->Cal_Slope, sizeof(double) * 12);

    /* -------------------------------------------------------------------- */
    /*      Create band information objects.                                */
    /* -------------------------------------------------------------------- */
    poDS->nBands = command.iNrChannels() * command.iNrCycles;
    for (int iBand = 0; iBand < poDS->nBands; iBand++)
    {
        poDS->SetBand(iBand + 1, new MSGRasterBand(poDS, iBand + 1));
    }

    /* -------------------------------------------------------------------- */
    /*      Confirm the requested access is supported.                      */
    /* -------------------------------------------------------------------- */
    if (poOpenInfo->eAccess == GA_Update)
    {
        delete poDS;
        CPLError(CE_Failure, CPLE_NotSupported,
                 "The MSG driver does not support update access to existing"
                 " datasets.\n");
        return nullptr;
    }

    /* -------------------------------------------------------------------- */
    /*                 Set DataSet metadata information                    */
    /* -------------------------------------------------------------------- */
    CPLString metadataValue;
    metadataValue.Printf("%d", poDS->iCurrentSatellite);
    poDS->SetMetadataItem("satellite_number", metadataValue.c_str(),
                          metadataDomain);

    return poDS;
}

/************************************************************************/
/*                         MSGRasterBand()                              */
/************************************************************************/

const double MSGRasterBand::rRTOA[12] = {20.76, 23.24, 19.85, -1, -1, -1,
                                         -1,    -1,    -1,    -1, -1, 25.11};

MSGRasterBand::MSGRasterBand(MSGDataset *poDSIn, int nBandIn)
    : fScanNorth(false), iLowerShift(0), iSplitLine(0),
      iLowerWestColumnPlanned(0)

{
    this->poDS = poDSIn;
    this->nBand = nBandIn;

    // Find if we're dealing with MSG1, MSG2, MSG3 or MSG4
    // Doing this per band is the only way to guarantee time-series when the
    // satellite is changed

    int iCurrentSatellite =
        poDSIn->iCurrentSatellite;  // Start with the satellite selected by the
                                    // dataset

    std::string sPrologueFileName =
        poDSIn->command.sPrologueFileName(iCurrentSatellite, 1);
    bool fPrologueExists = (access(sPrologueFileName.c_str(), 0) == 0);

    // Make sure we're testing for MSG1,2,3 or 4 exactly once, start with the
    // one of the dataset
    int iTries = 1;
    while (!fPrologueExists && (iTries < MAX_SATELLITES))
    {
        iCurrentSatellite = 1 + iCurrentSatellite % MAX_SATELLITES;
        sPrologueFileName =
            poDSIn->command.sPrologueFileName(iCurrentSatellite, 1);
        fPrologueExists = (access(sPrologueFileName.c_str(), 0) == 0);
        ++iTries;
    }

    iSatellite =
        iCurrentSatellite;  // From here on, the satellite that corresponds to
                            // this band is settled to the current satellite

    nBlockXSize = poDSIn->GetRasterXSize();
    nBlockYSize = poDSIn->GetRasterYSize();

    /* -------------------------------------------------------------------- */
    /*      Open an input file and capture the header for the nr. of bits.  */
    /* -------------------------------------------------------------------- */
    int iStrip = 1;
    int iChannel = poDSIn->command.iChannel(
        1 + ((nBand - 1) % poDSIn->command.iNrChannels()));
    std::string input_file =
        poDSIn->command.sFileName(iSatellite, nBand, iStrip);
    while ((access(input_file.c_str(), 0) != 0) &&
           (iStrip <= poDSIn->command.iNrStrips(
                          iChannel)))  // compensate for missing strips
        input_file = poDSIn->command.sFileName(iSatellite, nBand, ++iStrip);

    if (iStrip <= poDSIn->command.iNrStrips(iChannel))
    {
        std::ifstream i_file(input_file.c_str(),
                             std::ios::in | std::ios::binary);

        if (i_file.good())
        {
            XRITHeaderParser xhp(i_file);

            if (xhp.isValid())
            {
                // Data type is either 8 or 16 bits .. we tell this to GDAL here
                eDataType = GDT_Byte;  // default .. always works
                if (xhp.nrBitsPerPixel() > 8)
                {
                    if (poDSIn->command.cDataConversion == 'N')
                        eDataType =
                            GDT_UInt16;  // normal case: MSG 10 bits data
                    else if (poDSIn->command.cDataConversion == 'B')
                        eDataType = GDT_Byte;  // output data type Byte
                    else
                        eDataType = GDT_Float32;  // Radiometric calibration
                }

                // make IReadBlock be called once per file
                nBlockYSize = xhp.nrRows();

                // remember the scan direction

                fScanNorth = xhp.isScannedNorth();
            }
        }

        i_file.close();
    }
    else if (nBand > 1)
    {
        // missing entire band .. take data from first band
        MSGRasterBand *pFirstRasterBand =
            (MSGRasterBand *)poDSIn->GetRasterBand(1);
        eDataType = pFirstRasterBand->eDataType;
        nBlockYSize = pFirstRasterBand->nBlockYSize;
        fScanNorth = pFirstRasterBand->fScanNorth;
    }
    else  // also first band is missing .. do something for fail-safety
    {
        if (poDSIn->command.cDataConversion == 'N')
            eDataType = GDT_UInt16;  // normal case: MSG 10 bits data
        else if (poDSIn->command.cDataConversion == 'B')
            eDataType = GDT_Byte;  // output data type Byte
        else
            eDataType = GDT_Float32;  // Radiometric calibration

        // nBlockYSize : default
        // fScanNorth : default
    }
    /* -------------------------------------------------------------------- */
    /*      For the HRV band, read the prologue for shift and splitline.    */
    /* -------------------------------------------------------------------- */

    if (iChannel == 12)
    {
        if (fPrologueExists)
        {
            std::ifstream p_file(sPrologueFileName.c_str(),
                                 std::ios::in | std::ios::binary);
            XRITHeaderParser xhp(p_file);
            Prologue pp;
            if (xhp.isValid() && xhp.isPrologue())
                pp.read(p_file);
            p_file.close();

            iLowerShift = pp.idr()->PlannedCoverageHRV->UpperWestColumnPlanned -
                          pp.idr()->PlannedCoverageHRV->LowerWestColumnPlanned;
            iSplitLine =
                abs(pp.idr()->PlannedCoverageHRV->UpperNorthLinePlanned -
                    pp.idr()->PlannedCoverageHRV->LowerNorthLinePlanned) +
                1;  // without the "+ 1" the image of 1-Jan-2005 splits
                    // incorrectly
            iLowerWestColumnPlanned =
                pp.idr()->PlannedCoverageHRV->LowerWestColumnPlanned;
        }
    }

    /* -------------------------------------------------------------------- */
    /*  Initialize the ReflectanceCalculator with the band-dependent info.  */
    /* -------------------------------------------------------------------- */

    int iCycle = 1 + (nBand - 1) / poDSIn->command.iNrChannels();
    std::string sTimeStamp = poDSIn->command.sCycle(iCycle);

    m_rc = new ReflectanceCalculator(sTimeStamp, rRTOA[iChannel - 1]);

    /* -------------------------------------------------------------------- */
    /*  Set DataSet metadata information                                   */
    /* -------------------------------------------------------------------- */
    CPLString metadataValue;
    metadataValue.Printf("%.10f", poDSIn->rCalibrationOffset[iChannel - 1]);
    SetMetadataItem("calibration_offset", metadataValue.c_str(),
                    poDSIn->metadataDomain);
    metadataValue.Printf("%.10f", poDSIn->rCalibrationSlope[iChannel - 1]);
    SetMetadataItem("calibration_slope", metadataValue.c_str(),
                    poDSIn->metadataDomain);
    metadataValue.Printf("%d", iChannel);
    SetMetadataItem("channel_number", metadataValue.c_str(),
                    poDSIn->metadataDomain);
}

/************************************************************************/
/*                           ~MSGRasterBand()                           */
/************************************************************************/
MSGRasterBand::~MSGRasterBand()
{
    delete m_rc;
}

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

{

    MSGDataset *poGDS = (MSGDataset *)poDS;

    int iBytesPerPixel = 1;
    if (eDataType == GDT_UInt16)
        iBytesPerPixel = 2;
    else if (eDataType == GDT_Float32)
        iBytesPerPixel = 4;
    /* -------------------------------------------------------------------- */
    /*      Calculate the correct input file name based on nBlockYOff       */
    /* -------------------------------------------------------------------- */

    int strip_number;
    int iChannel = poGDS->command.iChannel(
        1 + ((nBand - 1) % poGDS->command.iNrChannels()));

    if (fScanNorth)
        strip_number = nBlockYOff + 1;
    else
        strip_number = poGDS->command.iNrStrips(iChannel) - nBlockYOff;

    std::string strip_input_file =
        poGDS->command.sFileName(iSatellite, nBand, strip_number);

    /* -------------------------------------------------------------------- */
    /*      Open the input file                                             */
    /* -------------------------------------------------------------------- */
    if (access(strip_input_file.c_str(), 0) == 0)  // does it exist?
    {
        std::ifstream i_file(strip_input_file.c_str(),
                             std::ios::in | std::ios::binary);

        if (i_file.good())
        {
            XRITHeaderParser xhp(i_file);

            if (xhp.isValid())
            {
                std::vector<short> QualityInfo;
                unsigned short chunk_height =
                    static_cast<unsigned short>(xhp.nrRows());
                unsigned short chunk_bpp =
                    static_cast<unsigned short>(xhp.nrBitsPerPixel());
                unsigned short chunk_width =
                    static_cast<unsigned short>(xhp.nrColumns());
                unsigned __int8 NR = (unsigned __int8)chunk_bpp;
                unsigned int nb_ibytes =
                    static_cast<unsigned int>(xhp.dataSize());
                int iShift = 0;
                bool fSplitStrip =
                    false;  // in the split strip the "shift" only happens
                            // before the split "row"
                int iSplitRow = 0;
                if (iChannel == 12)
                {
                    iSplitRow = iSplitLine % xhp.nrRows();
                    int iSplitBlock = iSplitLine / xhp.nrRows();
                    fSplitStrip =
                        (nBlockYOff ==
                         iSplitBlock);  // in the split strip the "shift" only
                                        // happens before the split "row"

                    // When iLowerShift > 0, the lower HRV image is shifted to
                    // the right When iLowerShift < 0, the lower HRV image is
                    // shifted to the left The available raster may be wider
                    // than needed, so that time series don't fall outside the
                    // raster.

                    if (nBlockYOff <= iSplitBlock)
                        iShift = -iLowerShift;
                    // iShift < 0 means upper image moves to the left
                    // iShift > 0 means upper image moves to the right
                }

                unsigned char *ibuf =
                    new (std::nothrow) unsigned char[nb_ibytes];
                if (ibuf == nullptr)
                {
                    CPLError(
                        CE_Failure, CPLE_AppDefined,
                        "Not enough memory to perform wavelet decompression\n");
                    return CE_Failure;
                }

                i_file.read((char *)ibuf, nb_ibytes);

                Util::CDataFieldCompressedImage img_compressed(
                    ibuf, nb_ibytes * 8, (unsigned char)chunk_bpp, chunk_width,
                    chunk_height);

                Util::CDataFieldUncompressedImage img_uncompressed;

                //****************************************************
                //*** Here comes the wavelets decompression routine
                COMP::DecompressWT(img_compressed, NR, img_uncompressed,
                                   QualityInfo);
                //****************************************************

                // convert:
                // Depth:
                // 8 bits -> 8 bits
                // 10 bits -> 16 bits (img_uncompressed contains the 10 bits
                // data in packed form) Geometry: chunk_width x chunk_height to
                // nBlockXSize x nBlockYSize

                // cases:
                // combination of the following:
                // - scan direction can be north or south
                // - eDataType can be GDT_Byte, GDT_UInt16 or GDT_Float32
                // - nBlockXSize == chunk_width or nBlockXSize > chunk_width
                // - when nBlockXSize > chunk_width, fSplitStrip can be true or
                // false we won't distinguish the following cases:
                // - NR can be == 8 or != 8
                // - when nBlockXSize > chunk_width, iShift iMinCOff-iMaxCOff <=
                // iShift <= 0

                int nBlockSize = nBlockXSize * nBlockYSize;
                int y = chunk_width * chunk_height;
                int iStep = -1;
                if (fScanNorth)  // image is the other way around
                {
                    y = -1;  // See how y is used below: += happens first, the
                             // result is used in the []
                    iStep = 1;
                }

                COMP::CImage cimg(img_uncompressed);  // unpack
                if (eDataType == GDT_Byte)
                {
                    if (nBlockXSize == chunk_width)  // optimized version
                    {
                        if (poGDS->command.cDataConversion == 'B')
                        {
                            for (int i = 0; i < nBlockSize; ++i)
                                ((GByte *)pImage)[i] = static_cast<GByte>(
                                    cimg.Get()[y += iStep] / 4);
                        }
                        else
                        {
                            for (int i = 0; i < nBlockSize; ++i)
                                ((GByte *)pImage)[i] =
                                    static_cast<GByte>(cimg.Get()[y += iStep]);
                        }
                    }
                    else
                    {
                        // initialize to 0's (so that it does not have to be
                        // done in an 'else' statement <performance>)
                        memset(pImage, 0,
                               nBlockXSize * nBlockYSize * iBytesPerPixel);
                        if (poGDS->command.cDataConversion == 'B')
                        {
                            for (int j = 0; j < chunk_height;
                                 ++j)  // assumption: nBlockYSize ==
                                       // chunk_height
                            {
                                int iXOffset = j * nBlockXSize + iShift;
                                iXOffset += nBlockXSize -
                                            iLowerWestColumnPlanned -
                                            1;  // Position the HRV part in the
                                                // frame; -1 to compensate the
                                                // pre-increment in the for-loop
                                if (fSplitStrip &&
                                    (j >= iSplitRow))  // In splitstrip, below
                                                       // splitline, thus do not
                                                       // shift!!
                                    iXOffset -= iShift;
                                for (int i = 0; i < chunk_width; ++i)
                                    ((GByte *)pImage)[++iXOffset] =
                                        static_cast<GByte>(
                                            cimg.Get()[y += iStep] / 4);
                            }
                        }
                        else
                        {
                            for (int j = 0; j < chunk_height;
                                 ++j)  // assumption: nBlockYSize ==
                                       // chunk_height
                            {
                                int iXOffset = j * nBlockXSize + iShift;
                                iXOffset += nBlockXSize -
                                            iLowerWestColumnPlanned -
                                            1;  // Position the HRV part in the
                                                // frame; -1 to compensate the
                                                // pre-increment in the for-loop
                                if (fSplitStrip &&
                                    (j >= iSplitRow))  // In splitstrip, below
                                                       // splitline, thus do not
                                                       // shift!!
                                    iXOffset -= iShift;
                                for (int i = 0; i < chunk_width; ++i)
                                    ((GByte *)pImage)[++iXOffset] =
                                        static_cast<GByte>(
                                            cimg.Get()[y += iStep]);
                            }
                        }
                    }
                }
                else if (eDataType ==
                         GDT_UInt16)  // this is our "normal case" if scan
                                      // direction is South: 10 bit MSG data
                                      // became 2 bytes per pixel
                {
                    if (nBlockXSize == chunk_width)  // optimized version
                    {
                        for (int i = 0; i < nBlockSize; ++i)
                            ((GUInt16 *)pImage)[i] = cimg.Get()[y += iStep];
                    }
                    else
                    {
                        // initialize to 0's (so that it does not have to be
                        // done in an 'else' statement <performance>)
                        memset(pImage, 0,
                               nBlockXSize * nBlockYSize * iBytesPerPixel);
                        for (int j = 0; j < chunk_height;
                             ++j)  // assumption: nBlockYSize == chunk_height
                        {
                            int iXOffset = j * nBlockXSize + iShift;
                            iXOffset += nBlockXSize - iLowerWestColumnPlanned -
                                        1;  // Position the HRV part in the
                                            // frame; -1 to compensate the
                                            // pre-increment in the for-loop
                            if (fSplitStrip &&
                                (j >=
                                 iSplitRow))  // In splitstrip, below splitline,
                                              // thus do not shift!!
                                iXOffset -= iShift;
                            for (int i = 0; i < chunk_width; ++i)
                                ((GUInt16 *)pImage)[++iXOffset] =
                                    cimg.Get()[y += iStep];
                        }
                    }
                }
                else if (eDataType ==
                         GDT_Float32)  // radiometric calibration is requested
                {
                    if (nBlockXSize == chunk_width)  // optimized version
                    {
                        for (int i = 0; i < nBlockSize; ++i)
                            ((float *)pImage)[i] =
                                (float)rRadiometricCorrection(
                                    cimg.Get()[y += iStep], iChannel,
                                    nBlockYOff * nBlockYSize + i / nBlockXSize,
                                    i % nBlockXSize, poGDS);
                    }
                    else
                    {
                        // initialize to 0's (so that it does not have to be
                        // done in an 'else' statement <performance>)
                        memset(pImage, 0,
                               nBlockXSize * nBlockYSize * iBytesPerPixel);
                        for (int j = 0; j < chunk_height;
                             ++j)  // assumption: nBlockYSize == chunk_height
                        {
                            int iXOffset = j * nBlockXSize + iShift;
                            iXOffset += nBlockXSize - iLowerWestColumnPlanned -
                                        1;  // Position the HRV part in the
                                            // frame; -1 to compensate the
                                            // pre-increment in the for-loop
                            if (fSplitStrip &&
                                (j >=
                                 iSplitRow))  // In splitstrip, below splitline,
                                              // thus do not shift!!
                                iXOffset -= iShift;
                            int iXFrom =
                                nBlockXSize - iLowerWestColumnPlanned +
                                iShift;  // i is used as the iCol parameter in
                                         // rRadiometricCorrection
                            int iXTo = nBlockXSize - iLowerWestColumnPlanned +
                                       chunk_width + iShift;
                            for (int i = iXFrom; i < iXTo;
                                 ++i)  // range always equal to chunk_width ..
                                       // this is to utilize i to get iCol
                                ((float *)pImage)[++iXOffset] =
                                    (float)rRadiometricCorrection(
                                        cimg.Get()[y += iStep], iChannel,
                                        nBlockYOff * nBlockYSize + j,
                                        (fSplitStrip && (j >= iSplitRow))
                                            ? (i - iShift)
                                            : i,
                                        poGDS);
                        }
                    }
                }
            }
            else  // header could not be opened .. make sure block contains 0's
                memset(pImage, 0, nBlockXSize * nBlockYSize * iBytesPerPixel);
        }
        else  // file could not be opened .. make sure block contains 0's
            memset(pImage, 0, nBlockXSize * nBlockYSize * iBytesPerPixel);

        i_file.close();
    }
    else  // file does not exist .. make sure block contains 0's
        memset(pImage, 0, nBlockXSize * nBlockYSize * iBytesPerPixel);

    return CE_None;
}

double MSGRasterBand::rRadiometricCorrection(unsigned int iDN, int iChannel,
                                             int iRow, int iCol,
                                             MSGDataset *poGDS) const
{
    int iIndex = iChannel - 1;  // just for speed optimization

    double rSlope = poGDS->rCalibrationSlope[iIndex];
    double rOffset = poGDS->rCalibrationOffset[iIndex];

    // Reflectance for visual bands, temperature for IR bands.
    if (poGDS->command.cDataConversion == 'T')
    {
        double rRadiance = rOffset + (iDN * rSlope);

        if (iChannel >= 4 &&
            iChannel <= 11)  // Channels 4 to 11 (infrared): Temperature
        {
            const double rC1 = 1.19104e-5;
            const double rC2 = 1.43877e+0;

            double cc2 = rC2 * poGDS->rVc[iIndex];
            double cc1 = rC1 * pow(poGDS->rVc[iIndex], 3) / rRadiance;
            double rTemperature =
                ((cc2 / log1p(cc1)) - poGDS->rB[iIndex]) / poGDS->rA[iIndex];
            return rTemperature;
        }
        else  // Channels 1,2,3 and 12 (visual): Reflectance
        {
            double rLon =
                poGDS->adfGeoTransform[0] +
                iCol * poGDS->adfGeoTransform[1];  // X, in "geos" meters
            double rLat =
                poGDS->adfGeoTransform[3] +
                iRow * poGDS->adfGeoTransform[5];  // Y, in "geos" meters
            if ((poGDS->poTransform != nullptr) &&
                poGDS->poTransform->Transform(1, &rLon,
                                              &rLat))  // transform it to latlon
                return m_rc->rGetReflectance(rRadiance, rLat, rLon);
            else
                return 0;
        }
    }
    else  // radiometric
    {
        if (poGDS->command.cDataConversion == 'R')
            return rOffset + (iDN * rSlope);
        else
        {
            double rFactor = 10 / pow(poGDS->rCentralWvl[iIndex], 2);
            return rFactor * (rOffset + (iDN * rSlope));
        }
    }
}

/************************************************************************/
/*                      GDALRegister_MSG()                              */
/************************************************************************/

void GDALRegister_MSG()

{
    if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
        return;

    GDALDriver *poDriver = new GDALDriver();
    MSGDriverSetCommonMetadata(poDriver);

    poDriver->pfnOpen = MSGDataset::Open;

    GetGDALDriverManager()->RegisterDriver(poDriver);
}
