/******************************************************************************
 *
 * Project:  PCRaster Integration
 * Purpose:  PCRaster raster band implementation.
 * Author:   Kor de Jong, Oliver Schmitz
 *
 ******************************************************************************
 * Copyright (c) PCRaster owners
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "csf.h"
#include "csfimpl.h"
#include "pcrasterdataset.h"
#include "pcrasterrasterband.h"
#include "pcrasterutil.h"

/*!
  \file
  This file contains the implementation of the PCRasterRasterBand class.
*/

//------------------------------------------------------------------------------
// DEFINITION OF PCRRASTERBAND MEMBERS
//------------------------------------------------------------------------------

//! Constructor.
/*!
  \param     dataset The dataset we are a part of.
*/
PCRasterRasterBand::PCRasterRasterBand(PCRasterDataset *dataset)
    : GDALPamRasterBand(), d_dataset(dataset), d_noDataValue(),
      d_defaultNoDataValueOverridden(false), d_create_in(GDT_Unknown)
{
    poDS = dataset;
    nBand = 1;
    eDataType = cellRepresentation2GDALType(dataset->cellRepresentation());
    nBlockXSize = dataset->GetRasterXSize();
    nBlockYSize = 1;
}

//! Destructor.
/*!
 */
PCRasterRasterBand::~PCRasterRasterBand()
{
}

double PCRasterRasterBand::GetNoDataValue(int *success)
{
    if (success)
    {
        *success = 1;
    }

    return d_defaultNoDataValueOverridden ? d_noDataValue
                                          : d_dataset->defaultNoDataValue();
}

double PCRasterRasterBand::GetMinimum(int *success)
{
    double result;
    bool isValid;

    switch (d_dataset->cellRepresentation())
    {
        // CSF version 2.
        // ----------------------------------------------------------
        case CR_UINT1:
        {
            UINT1 min;
            isValid = CPL_TO_BOOL(RgetMinVal(d_dataset->map(), &min));
            result = static_cast<double>(min);
            break;
        }
        case CR_INT4:
        {
            INT4 min;
            isValid = CPL_TO_BOOL(RgetMinVal(d_dataset->map(), &min));
            result = static_cast<double>(min);
            break;
        }
        case CR_REAL4:
        {
            REAL4 min;
            isValid = CPL_TO_BOOL(RgetMinVal(d_dataset->map(), &min));
            result = static_cast<double>(min);
            break;
        }
        case CR_REAL8:
        {
            REAL8 min;
            isValid = CPL_TO_BOOL(RgetMinVal(d_dataset->map(), &min));
            result = static_cast<double>(min);
            break;
        }
        // CSF version 1.
        // ----------------------------------------------------------
        case CR_INT1:
        {
            INT1 min;
            isValid = CPL_TO_BOOL(RgetMinVal(d_dataset->map(), &min));
            result = static_cast<double>(min);
            break;
        }
        case CR_INT2:
        {
            INT2 min;
            isValid = CPL_TO_BOOL(RgetMinVal(d_dataset->map(), &min));
            result = static_cast<double>(min);
            break;
        }
        case CR_UINT2:
        {
            UINT2 min;
            isValid = CPL_TO_BOOL(RgetMinVal(d_dataset->map(), &min));
            result = static_cast<double>(min);
            break;
        }
        case CR_UINT4:
        {
            UINT4 min;
            isValid = CPL_TO_BOOL(RgetMinVal(d_dataset->map(), &min));
            result = static_cast<double>(min);
            break;
        }
        default:
        {
            result = 0.0;
            isValid = false;
            break;
        }
    }

    if (success)
    {
        *success = isValid ? 1 : 0;
    }

    return result;
}

double PCRasterRasterBand::GetMaximum(int *success)
{
    double result;
    bool isValid;

    switch (d_dataset->cellRepresentation())
    {
        case CR_UINT1:
        {
            UINT1 max;
            isValid = CPL_TO_BOOL(RgetMaxVal(d_dataset->map(), &max));
            result = static_cast<double>(max);
            break;
        }
        case CR_INT4:
        {
            INT4 max;
            isValid = CPL_TO_BOOL(RgetMaxVal(d_dataset->map(), &max));
            result = static_cast<double>(max);
            break;
        }
        case CR_REAL4:
        {
            REAL4 max;
            isValid = CPL_TO_BOOL(RgetMaxVal(d_dataset->map(), &max));
            result = static_cast<double>(max);
            break;
        }
        // CSF version 1.
        // ----------------------------------------------------------
        case CR_INT1:
        {
            INT1 max;
            isValid = CPL_TO_BOOL(RgetMaxVal(d_dataset->map(), &max));
            result = static_cast<double>(max);
            break;
        }
        case CR_INT2:
        {
            INT2 max;
            isValid = CPL_TO_BOOL(RgetMaxVal(d_dataset->map(), &max));
            result = static_cast<double>(max);
            break;
        }
        case CR_UINT2:
        {
            UINT2 max;
            isValid = CPL_TO_BOOL(RgetMaxVal(d_dataset->map(), &max));
            result = static_cast<double>(max);
            break;
        }
        case CR_UINT4:
        {
            UINT4 max;
            isValid = CPL_TO_BOOL(RgetMaxVal(d_dataset->map(), &max));
            result = static_cast<double>(max);
            break;
        }
        default:
        {
            result = 0.0;
            isValid = false;
            break;
        }
    }

    if (success)
    {
        *success = isValid ? 1 : 0;
    }

    return result;
}

CPLErr PCRasterRasterBand::IReadBlock(CPL_UNUSED int nBlockXoff, int nBlockYoff,
                                      void *buffer)
{
    size_t nrCellsRead = RgetRow(d_dataset->map(), nBlockYoff, buffer);

    // Now we have raw values, missing values are set according to the CSF
    // conventions. This means that floating points should not be evaluated.
    // Since this is done by the GDal library we replace these with valid
    // values. Non-MV values are not touched.

    // Replace in-file MV with in-app MV which may be different.
    alterFromStdMV(buffer, nrCellsRead, d_dataset->cellRepresentation(),
                   GetNoDataValue());

    return CE_None;
}

CPLErr PCRasterRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
                                     int nXSize, int nYSize, void *pData,
                                     int nBufXSize, int nBufYSize,
                                     GDALDataType eBufType,
                                     GSpacing nPixelSpace, GSpacing nLineSpace,
                                     GDALRasterIOExtraArg *psExtraArg)
{
    if (eRWFlag == GF_Read)
    {
        // read should just be the default
        return GDALRasterBand::IRasterIO(GF_Read, nXOff, nYOff, nXSize, nYSize,
                                         pData, nBufXSize, nBufYSize, eBufType,
                                         nPixelSpace, nLineSpace, psExtraArg);
    }
    else
    {
        // the datatype of the incoming data can be of different type than the
        // cell representation used in the raster
        // 'remember' the GDAL type to distinguish it later on in iWriteBlock
        d_create_in = eBufType;
        return GDALRasterBand::IRasterIO(GF_Write, nXOff, nYOff, nXSize, nYSize,
                                         pData, nBufXSize, nBufYSize, eBufType,
                                         nPixelSpace, nLineSpace, psExtraArg);
    }
}

CPLErr PCRasterRasterBand::IWriteBlock(CPL_UNUSED int nBlockXoff,
                                       int nBlockYoff, void *source)
{
    CSF_VS valuescale = d_dataset->valueScale();

    if (valuescale == VS_LDD)
    {
        if ((d_create_in == GDT_Byte) || (d_create_in == GDT_Float32) ||
            (d_create_in == GDT_Float64))
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "PCRaster driver: "
                     "conversion from %s to LDD not supported",
                     GDALGetDataTypeName(d_create_in));
            return CE_Failure;
        }
    }

    // set new location attributes to the header
    if (d_dataset->location_changed())
    {
        REAL8 west = 0.0;
        REAL8 north = 0.0;
        REAL8 cellSize = 1.0;
        double transform[6];
        if (this->poDS->GetGeoTransform(transform) == CE_None)
        {
            if (transform[2] == 0.0 && transform[4] == 0.0)
            {
                west = static_cast<REAL8>(transform[0]);
                north = static_cast<REAL8>(transform[3]);
                cellSize = static_cast<REAL8>(transform[1]);
            }
        }
        (void)RputXUL(d_dataset->map(), west);
        (void)RputYUL(d_dataset->map(), north);
        (void)RputCellSize(d_dataset->map(), cellSize);
    }

    const int nr_cols = this->poDS->GetRasterXSize();

    // new maps from create() set min/max to MV
    // in case of reopening that map the min/max
    // value tracking is disabled (MM_WRONGVALUE)
    // reactivate it again to ensure that the output will
    // get the correct values when values are written to map
    d_dataset->map()->minMaxStatus = MM_KEEPTRACK;

    // allocate memory for row
    void *buffer = Rmalloc(d_dataset->map(), nr_cols);
    memcpy(buffer, source, nr_cols * 4);

    // convert source no_data values to MV in dest
    switch (valuescale)
    {
        case VS_BOOLEAN:
        case VS_LDD:
        {
            alterToStdMV(buffer, nr_cols, CR_UINT1, GetNoDataValue());
            break;
        }
        case VS_NOMINAL:
        case VS_ORDINAL:
        {
            alterToStdMV(buffer, nr_cols, CR_INT4, GetNoDataValue());
            break;
        }
        case VS_SCALAR:
        case VS_DIRECTION:
        {
            alterToStdMV(buffer, nr_cols, CR_REAL4, GetNoDataValue());
            break;
        }
        default:
        {
            break;
        }
    }

    // conversion of values according to value scale
    switch (valuescale)
    {
        case VS_BOOLEAN:
        {
            castValuesToBooleanRange(buffer, nr_cols, CR_UINT1);
            break;
        }
        case VS_LDD:
        {
            castValuesToLddRange(buffer, nr_cols);
            break;
        }
        case VS_DIRECTION:
        {
            castValuesToDirectionRange(buffer, nr_cols);
            break;
        }
        default:
        {
            break;
        }
    }

    RputRow(d_dataset->map(), nBlockYoff, buffer);
    free(buffer);

    return CE_None;
}

CPLErr PCRasterRasterBand::SetNoDataValue(double nodata)
{
    d_noDataValue = nodata;
    d_defaultNoDataValueOverridden = true;

    return CE_None;
}
