/******************************************************************************
 *
 * Project:  GDAL Core
 * Purpose:  Implementation of GDALRasterAttributeTable and related classes.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2005, Frank Warmerdam
 * Copyright (c) 2009, Even Rouault <even dot rouault at mines-paris dot org>
 *
 * 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"
#include "gdal.h"
#include "gdal_priv.h"
#include "gdal_rat.h"

#include <cmath>
#include <cstddef>
#include <cstdlib>

#include <algorithm>
#include <vector>

#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_string.h"
#include "cpl_vsi.h"


#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-pragmas"
#pragma clang diagnostic ignored "-Wdocumentation"
#endif
#include "json.h"
#ifdef __clang__
#pragma clang diagnostic pop
#endif
#include "ogrgeojsonwriter.h"

CPL_CVSID("$Id: gdal_rat.cpp 2519a7eb0e1649dbf8625ae8ffc7bb7c3ef9514b 2018-07-10 12:05:23 +0100 Robert Coup $")

/**
 * \class GDALRasterAttributeTable
 *
 * The GDALRasterAttributeTable (or RAT) class is used to encapsulate a table
 * used to provide attribute information about pixel values.  Each row
 * in the table applies to a range of pixel values (or a single value in
 * some cases), and might have attributes such as the histogram count for
 * that range, the color pixels of that range should be drawn names of classes
 * or any other generic information.
 *
 * Raster attribute tables can be used to represent histograms, color tables,
 * and classification information.
 *
 * Each column in a raster attribute table has a name, a type (integer,
 * floating point or string), and a GDALRATFieldUsage.  The usage distinguishes
 * columns with particular understood purposes (such as color, histogram
 * count, name) and columns that have specific purposes not understood by
 * the library (long label, suitability_for_growing_wheat, etc).
 *
 * In the general case each row has a column indicating the minimum pixel
 * values falling into that category, and a column indicating the maximum
 * pixel value.  These are indicated with usage values of GFU_Min, and
 * GFU_Max.  In other cases where each row is a discrete pixel value, one
 * column of usage GFU_MinMax can be used.
 *
 * In other cases all the categories are of equal size and regularly spaced
 * and the categorization information can be determine just by knowing the
 * value at which the categories start, and the size of a category.  This
 * is called "Linear Binning" and the information is kept specially on
 * the raster attribute table as a whole.
 *
 * RATs are normally associated with GDALRasterBands and be be queried
 * using the GDALRasterBand::GetDefaultRAT() method.
 */

/************************************************************************/
/*                  ~GDALRasterAttributeTable()                         */
/*                                                                      */
/*                      Virtual Destructor                              */
/************************************************************************/

GDALRasterAttributeTable::~GDALRasterAttributeTable() = default;

/************************************************************************/
/*                              ValuesIO()                              */
/*                                                                      */
/*                      Default Implementations                         */
/************************************************************************/

/**
 * \brief Read or Write a block of doubles to/from the Attribute Table.
 *
 * This method is the same as the C function GDALRATValuesIOAsDouble().
 *
 * @param eRWFlag Either GF_Read or GF_Write
 * @param iField column of the Attribute Table
 * @param iStartRow start row to start reading/writing (zero based)
 * @param iLength number of rows to read or write
 * @param pdfData pointer to array of doubles to read/write. Should be at least
 *   iLength long.
 *
 * @return CE_None or CE_Failure if iStartRow + iLength greater than number of
 *   rows in table.
 */

CPLErr GDALRasterAttributeTable::ValuesIO(
    GDALRWFlag eRWFlag, int iField, int iStartRow, int iLength, double *pdfData)
{
    if( (iStartRow + iLength) > GetRowCount() )
    {
        return CE_Failure;
    }

    if( eRWFlag == GF_Read )
    {
        for( int iIndex = iStartRow; iIndex < (iStartRow + iLength); iIndex++ )
        {
            pdfData[iIndex] = GetValueAsDouble(iIndex, iField);
        }
    }
    else
    {
        for( int iIndex = iStartRow; iIndex < (iStartRow + iLength); iIndex++ )
        {
            SetValue(iIndex, iField, pdfData[iIndex]);
        }
    }
    return CE_None;
}

/************************************************************************/
/*                       GDALRATValuesIOAsDouble()                      */
/************************************************************************/

/**
 * \brief Read or Write a block of doubles to/from the Attribute Table.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::ValuesIO()
 */
CPLErr CPL_STDCALL GDALRATValuesIOAsDouble(
    GDALRasterAttributeTableH hRAT, GDALRWFlag eRWFlag,
    int iField, int iStartRow, int iLength, double *pdfData )

{
    VALIDATE_POINTER1( hRAT, "GDALRATValuesIOAsDouble", CE_Failure );

    return GDALRasterAttributeTable::FromHandle(hRAT)->
        ValuesIO(eRWFlag, iField, iStartRow, iLength, pdfData);
}

/**
 * \brief Read or Write a block of integers to/from the Attribute Table.
 *
 * This method is the same as the C function GDALRATValuesIOAsInteger().
 *
 * @param eRWFlag Either GF_Read or GF_Write
 * @param iField column of the Attribute Table
 * @param iStartRow start row to start reading/writing (zero based)
 * @param iLength number of rows to read or write
 * @param pnData pointer to array of ints to read/write. Should be at least
 *     iLength long.
 *
 * @return CE_None or CE_Failure if iStartRow + iLength greater than number of
 *     rows in table.
 */

CPLErr GDALRasterAttributeTable::ValuesIO(
    GDALRWFlag eRWFlag, int iField, int iStartRow, int iLength, int *pnData )
{
    if( (iStartRow + iLength) > GetRowCount() )
    {
        return CE_Failure;
    }

    if( eRWFlag == GF_Read )
    {
        for( int iIndex = iStartRow; iIndex < (iStartRow + iLength); iIndex++ )
        {
            pnData[iIndex] = GetValueAsInt(iIndex, iField);
        }
    }
    else
    {
        for( int iIndex = iStartRow; iIndex < (iStartRow + iLength); iIndex++ )
        {
            SetValue(iIndex, iField, pnData[iIndex]);
        }
    }
    return CE_None;
}

/************************************************************************/
/*                       GDALRATValuesIOAsInteger()                     */
/************************************************************************/

/**
 * \brief Read or Write a block of ints to/from the Attribute Table.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::ValuesIO()
 */
CPLErr CPL_STDCALL GDALRATValuesIOAsInteger(
    GDALRasterAttributeTableH hRAT, GDALRWFlag eRWFlag,
    int iField, int iStartRow, int iLength, int *pnData )

{
    VALIDATE_POINTER1( hRAT, "GDALRATValuesIOAsInteger", CE_Failure );

    return GDALRasterAttributeTable::FromHandle(hRAT)->
        ValuesIO(eRWFlag, iField, iStartRow, iLength, pnData);
}

/**
 * \brief Read or Write a block of strings to/from the Attribute Table.
 *
 * This method is the same as the C function GDALRATValuesIOAsString().
 * When reading, papszStrList must be already allocated to the correct size.
 * The caller is expected to call CPLFree on each read string.
 *
 * @param eRWFlag Either GF_Read or GF_Write
 * @param iField column of the Attribute Table
 * @param iStartRow start row to start reading/writing (zero based)
 * @param iLength number of rows to read or write
 * @param papszStrList pointer to array of strings to read/write. Should be at
 *   least iLength long.
 *
 * @return CE_None or CE_Failure if iStartRow + iLength greater than number of
 *   rows in table.
 */

CPLErr GDALRasterAttributeTable::ValuesIO(
    GDALRWFlag eRWFlag, int iField, int iStartRow, int iLength,
    char **papszStrList )
{
    if( (iStartRow + iLength) > GetRowCount() )
    {
        return CE_Failure;
    }

    if( eRWFlag == GF_Read )
    {
        for( int iIndex = iStartRow; iIndex < (iStartRow + iLength); iIndex++ )
        {
            papszStrList[iIndex] = VSIStrdup(GetValueAsString(iIndex, iField));
        }
    }
    else
    {
        for( int iIndex = iStartRow; iIndex < (iStartRow + iLength); iIndex++ )
        {
            SetValue(iIndex, iField, papszStrList[iIndex]);
        }
    }
    return CE_None;
}

/************************************************************************/
/*                       GDALRATValuesIOAsString()                      */
/************************************************************************/

/**
 * \brief Read or Write a block of strings to/from the Attribute Table.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::ValuesIO()
 */
CPLErr CPL_STDCALL GDALRATValuesIOAsString(
    GDALRasterAttributeTableH hRAT, GDALRWFlag eRWFlag,
    int iField, int iStartRow, int iLength, CSLConstList papszStrList )

{
    VALIDATE_POINTER1( hRAT, "GDALRATValuesIOAsString", CE_Failure );

    return GDALRasterAttributeTable::FromHandle(hRAT)->
        ValuesIO(eRWFlag, iField, iStartRow, iLength,
        const_cast<char**>(papszStrList));
}

/************************************************************************/
/*                            SetRowCount()                             */
/************************************************************************/

/**
 * \brief Set row count.
 *
 * Resizes the table to include the indicated number of rows.  Newly created
 * rows will be initialized to their default values - "" for strings,
 * and zero for numeric fields.
 *
 * This method is the same as the C function GDALRATSetRowCount().
 *
 * @param nNewCount the new number of rows.
 */

void GDALRasterAttributeTable::SetRowCount( CPL_UNUSED int nNewCount ) {}

/************************************************************************/
/*                         GDALRATSetRowCount()                         */
/************************************************************************/

/**
 * \brief Set row count.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::SetRowCount()
 *
 * @param hRAT RAT handle.
 * @param nNewCount the new number of rows.
 */
void CPL_STDCALL
GDALRATSetRowCount( GDALRasterAttributeTableH hRAT, int nNewCount )

{
    VALIDATE_POINTER0( hRAT, "GDALRATSetRowCount" );

    GDALRasterAttributeTable::FromHandle(hRAT)->SetRowCount( nNewCount );
}

/************************************************************************/
/*                           GetRowOfValue()                            */
/************************************************************************/

/**
 * \fn GDALRasterAttributeTable::GetRowOfValue(double) const
 * \brief Get row for pixel value.
 *
 * Given a raw pixel value, the raster attribute table is scanned to
 * determine which row in the table applies to the pixel value.  The
 * row index is returned.
 *
 * This method is the same as the C function GDALRATGetRowOfValue().
 *
 * @param dfValue the pixel value.
 *
 * @return the row index or -1 if no row is appropriate.
 */

/**/
/**/

int GDALRasterAttributeTable::GetRowOfValue( double /* dfValue */ ) const
{
    return -1;
}

/************************************************************************/
/*                        GDALRATGetRowOfValue()                        */
/************************************************************************/

/**
 * \brief Get row for pixel value.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::GetRowOfValue()
 */
int CPL_STDCALL
GDALRATGetRowOfValue( GDALRasterAttributeTableH hRAT, double dfValue )

{
    VALIDATE_POINTER1( hRAT, "GDALRATGetRowOfValue", 0 );

    return GDALRasterAttributeTable::FromHandle(hRAT)->
        GetRowOfValue( dfValue );
}

/************************************************************************/
/*                           GetRowOfValue()                            */
/************************************************************************/

/**
 * \brief Get row for pixel value.
 *
 * Given a raw pixel value, the raster attribute table is scanned to
 * determine which row in the table applies to the pixel value.  The
 * row index is returned.
 *
 * Int arg for now just converted to double.  Perhaps we will
 * handle this in a special way some day?
 *
 * This method is the same as the C function GDALRATGetRowOfValue().
 *
 * @param nValue the pixel value.
 *
 * @return the row index or -1 if no row is appropriate.
 */

int GDALRasterAttributeTable::GetRowOfValue( int nValue ) const

{
    return GetRowOfValue( static_cast<double>(nValue) );
}

/************************************************************************/
/*                            CreateColumn()                            */
/************************************************************************/

/**
 * \fn GDALRasterAttributeTable::CreateColumn(const char*, GDALRATFieldType, GDALRATFieldUsage)
 * \brief Create new column.
 *
 * If the table already has rows, all row values for the new column will
 * be initialized to the default value ("", or zero).  The new column is
 * always created as the last column, can will be column (field)
 * "GetColumnCount()-1" after CreateColumn() has completed successfully.
 *
 * This method is the same as the C function GDALRATCreateColumn().
 *
 * @param pszFieldName the name of the field to create.
 * @param eFieldType the field type (integer, double or string).
 * @param eFieldUsage the field usage, GFU_Generic if not known.
 *
 * @return CE_None on success or CE_Failure if something goes wrong.
 */

/**/
/**/

CPLErr GDALRasterAttributeTable::CreateColumn(
    const char * /* pszFieldName */, GDALRATFieldType /* eFieldType */,
    GDALRATFieldUsage /* eFieldUsage */ )
{
    return CE_Failure;
}

/************************************************************************/
/*                        GDALRATCreateColumn()                         */
/************************************************************************/

/**
 * \brief Create new column.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::CreateColumn()
 */
CPLErr CPL_STDCALL GDALRATCreateColumn( GDALRasterAttributeTableH hRAT,
                                        const char *pszFieldName,
                                        GDALRATFieldType eFieldType,
                                        GDALRATFieldUsage eFieldUsage )

{
    VALIDATE_POINTER1( hRAT, "GDALRATCreateColumn", CE_Failure );

    return GDALRasterAttributeTable::FromHandle(hRAT)->
        CreateColumn( pszFieldName, eFieldType, eFieldUsage );
}

/************************************************************************/
/*                          SetLinearBinning()                          */
/************************************************************************/

/**
 * \brief Set linear binning information.
 *
 * For RATs with equal sized categories (in pixel value space) that are
 * evenly spaced, this method may be used to associate the linear binning
 * information with the table.
 *
 * This method is the same as the C function GDALRATSetLinearBinning().
 *
 * @param dfRow0MinIn the lower bound (pixel value) of the first category.
 * @param dfBinSizeIn the width of each category (in pixel value units).
 *
 * @return CE_None on success or CE_Failure on failure.
 */

CPLErr GDALRasterAttributeTable::SetLinearBinning( CPL_UNUSED double dfRow0MinIn,
                                                   CPL_UNUSED double dfBinSizeIn )
{
    return CE_Failure;
}

/************************************************************************/
/*                      GDALRATSetLinearBinning()                       */
/************************************************************************/

/**
 * \brief Set linear binning information.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::SetLinearBinning()
 */
CPLErr CPL_STDCALL
GDALRATSetLinearBinning( GDALRasterAttributeTableH hRAT,
                         double dfRow0Min, double dfBinSize )

{
    VALIDATE_POINTER1( hRAT, "GDALRATSetLinearBinning", CE_Failure );

    return GDALRasterAttributeTable::FromHandle(hRAT)->
        SetLinearBinning( dfRow0Min, dfBinSize );
}

/************************************************************************/
/*                          GetLinearBinning()                          */
/************************************************************************/

/**
 * \brief Get linear binning information.
 *
 * Returns linear binning information if any is associated with the RAT.
 *
 * This method is the same as the C function GDALRATGetLinearBinning().
 *
 * @param pdfRow0Min (out) the lower bound (pixel value) of the first category.
 * @param pdfBinSize (out) the width of each category (in pixel value units).
 *
 * @return TRUE if linear binning information exists or FALSE if there is none.
 */

int GDALRasterAttributeTable::GetLinearBinning(
    CPL_UNUSED double * pdfRow0Min ,
    CPL_UNUSED double * pdfBinSize ) const
{
    return false;
}

/************************************************************************/
/*                      GDALRATGetLinearBinning()                       */
/************************************************************************/

/**
 * \brief Get linear binning information.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::GetLinearBinning()
 */
int CPL_STDCALL
GDALRATGetLinearBinning( GDALRasterAttributeTableH hRAT,
                         double *pdfRow0Min, double *pdfBinSize )

{
    VALIDATE_POINTER1( hRAT, "GDALRATGetLinearBinning", 0 );

    return GDALRasterAttributeTable::FromHandle(hRAT)->
        GetLinearBinning( pdfRow0Min, pdfBinSize );
}

/************************************************************************/
/*                        GDALRATGetTableType()                         */
/************************************************************************/

/**
 * \brief Get Rat Table Type
 *
 * @since GDAL 2.4
 *
 * This function is the same as the C++ method GDALRasterAttributeTable::GetTableType()
 */
GDALRATTableType CPL_STDCALL
GDALRATGetTableType( GDALRasterAttributeTableH hRAT)
{
    VALIDATE_POINTER1( hRAT, "GDALRATGetTableType", GRTT_THEMATIC );

    return GDALDefaultRasterAttributeTable::FromHandle(hRAT)->
        GetTableType();
}

/************************************************************************/
/*                        GDALRATSetTableType()                         */
/************************************************************************/

/**
 * \brief Set RAT Table Type
 *
 * @since GDAL 2.4
 *
 * This function is the same as the C++ method GDALRasterAttributeTable::SetTableType()
 */
CPLErr CPL_STDCALL
GDALRATSetTableType( GDALRasterAttributeTableH hRAT,
                         const GDALRATTableType eInTableType )

{
    VALIDATE_POINTER1( hRAT, "GDALRATSetTableType", CE_Failure );

    return GDALDefaultRasterAttributeTable::FromHandle(hRAT)->
        SetTableType( eInTableType );
}

/************************************************************************/
/*                             Serialize()                              */
/************************************************************************/

/** Serialize as a XML tree.
 * @return XML tree.
 */
CPLXMLNode *GDALRasterAttributeTable::Serialize() const

{
    if( ( GetColumnCount() == 0 ) && ( GetRowCount() == 0 ) )
        return nullptr;

    CPLXMLNode *psTree
        = CPLCreateXMLNode( nullptr, CXT_Element, "GDALRasterAttributeTable" );

/* -------------------------------------------------------------------- */
/*      Add attributes with regular binning info if appropriate.        */
/* -------------------------------------------------------------------- */
    char szValue[128] = { '\0' };
    double dfRow0Min = 0.0;
    double dfBinSize = 0.0;

    if( GetLinearBinning(&dfRow0Min, &dfBinSize) )
    {
        CPLsnprintf( szValue, sizeof(szValue), "%.16g", dfRow0Min );
        CPLCreateXMLNode(
            CPLCreateXMLNode( psTree, CXT_Attribute, "Row0Min" ),
            CXT_Text, szValue );

        CPLsnprintf( szValue, sizeof(szValue), "%.16g", dfBinSize );
        CPLCreateXMLNode(
            CPLCreateXMLNode( psTree, CXT_Attribute, "BinSize" ),
            CXT_Text, szValue );
    }

/* -------------------------------------------------------------------- */
/*      Store table type                                                */
/* -------------------------------------------------------------------- */
    const GDALRATTableType tableType = GetTableType();
    if (tableType == GRTT_ATHEMATIC)
    {
        CPLsnprintf( szValue, sizeof(szValue), "athematic" );
    }
    else
    {
        CPLsnprintf( szValue, sizeof(szValue), "thematic" );
    }
    CPLCreateXMLNode(
        CPLCreateXMLNode( psTree, CXT_Attribute, "tableType" ),
        CXT_Text, szValue );

/* -------------------------------------------------------------------- */
/*      Define each column.                                             */
/* -------------------------------------------------------------------- */
    const int iColCount = GetColumnCount();

    for( int iCol = 0; iCol < iColCount; iCol++ )
    {
        CPLXMLNode *psCol
            = CPLCreateXMLNode( psTree, CXT_Element, "FieldDefn" );

        snprintf( szValue, sizeof(szValue), "%d", iCol );
        CPLCreateXMLNode(
            CPLCreateXMLNode( psCol, CXT_Attribute, "index" ),
            CXT_Text, szValue );

        CPLCreateXMLElementAndValue( psCol, "Name",
                                     GetNameOfCol(iCol) );

        snprintf( szValue, sizeof(szValue),
                  "%d", static_cast<int>(GetTypeOfCol(iCol)) );
        CPLCreateXMLElementAndValue( psCol, "Type", szValue );

        snprintf( szValue, sizeof(szValue),
                  "%d", static_cast<int>(GetUsageOfCol(iCol)) );
        CPLCreateXMLElementAndValue( psCol, "Usage", szValue );
    }

/* -------------------------------------------------------------------- */
/*      Write out each row.                                             */
/* -------------------------------------------------------------------- */
    const int iRowCount = GetRowCount();
    CPLXMLNode *psTail = nullptr;
    CPLXMLNode *psRow = nullptr;

    for( int iRow = 0; iRow < iRowCount; iRow++ )
    {
        psRow = CPLCreateXMLNode( nullptr, CXT_Element, "Row" );
        if( psTail == nullptr )
            CPLAddXMLChild( psTree, psRow );
        else
            psTail->psNext = psRow;
        psTail = psRow;

        snprintf( szValue, sizeof(szValue), "%d", iRow );
        CPLCreateXMLNode(
            CPLCreateXMLNode( psRow, CXT_Attribute, "index" ),
            CXT_Text, szValue );

        for( int iCol = 0; iCol < iColCount; iCol++ )
        {
            const char *pszValue = szValue;

            if( GetTypeOfCol(iCol) == GFT_Integer )
                snprintf( szValue, sizeof(szValue),
                          "%d", GetValueAsInt(iRow, iCol) );
            else if( GetTypeOfCol(iCol) == GFT_Real )
                CPLsnprintf( szValue, sizeof(szValue),
                             "%.16g", GetValueAsDouble(iRow, iCol) );
            else
                pszValue = GetValueAsString(iRow, iCol);

            CPLCreateXMLElementAndValue( psRow, "F", pszValue );
        }
    }

    return psTree;
}

/************************************************************************/
/*                             SerializeJSON()                           */
/************************************************************************/

/** Serialize as a JSON object.
 * @return JSON object (of type json_object*)
 */
void *GDALRasterAttributeTable::SerializeJSON() const

{
    json_object *poRAT = json_object_new_object();

    if( ( GetColumnCount() == 0 ) && ( GetRowCount() == 0 ) )
        return poRAT;

/* -------------------------------------------------------------------- */
/*      Add attributes with regular binning info if appropriate.        */
/* -------------------------------------------------------------------- */
    double dfRow0Min = 0.0;
    double dfBinSize = 0.0;
    json_object *poRow0Min = nullptr;
    json_object *poBinSize = nullptr;
    json_object *poTableType = nullptr;

    if( GetLinearBinning(&dfRow0Min, &dfBinSize) )
    {
        poRow0Min = json_object_new_double_with_precision( dfRow0Min, 16 );
        json_object_object_add( poRAT, "row0Min", poRow0Min );

        poBinSize = json_object_new_double_with_precision( dfBinSize, 16 );
        json_object_object_add( poRAT, "binSize", poBinSize );
    }

/* -------------------------------------------------------------------- */
/*      Table Type                                                      */
/* -------------------------------------------------------------------- */
    const GDALRATTableType tableType = GetTableType();
    if (tableType == GRTT_ATHEMATIC)
    {
        poTableType = json_object_new_string( "athematic" );
    }
    else
    {
        poTableType = json_object_new_string( "thematic" );
    }
    json_object_object_add( poRAT, "tableType", poTableType);

/* -------------------------------------------------------------------- */
/*      Define each column.                                             */
/* -------------------------------------------------------------------- */
    const int iColCount = GetColumnCount();
    json_object *poFieldDefnArray = json_object_new_array();

    for( int iCol = 0; iCol < iColCount; iCol++ )
    {
        json_object * const poFieldDefn = json_object_new_object();

        json_object * const poColumnIndex = json_object_new_int( iCol );
        json_object_object_add( poFieldDefn, "index", poColumnIndex );

        json_object * const poName =
            json_object_new_string( GetNameOfCol(iCol) );
        json_object_object_add( poFieldDefn, "name", poName );

        json_object * const poType =
            json_object_new_int( static_cast<int>( GetTypeOfCol(iCol) ) );
        json_object_object_add( poFieldDefn, "type", poType );

        json_object * const poUsage =
            json_object_new_int( static_cast<int>( GetUsageOfCol(iCol) ) );
        json_object_object_add( poFieldDefn, "usage", poUsage );

        json_object_array_add( poFieldDefnArray, poFieldDefn );
    }

    json_object_object_add( poRAT, "fieldDefn", poFieldDefnArray );

/* -------------------------------------------------------------------- */
/*      Write out each row.                                             */
/* -------------------------------------------------------------------- */
    const int iRowCount = GetRowCount();
    json_object *poRowArray = json_object_new_array();

    for( int iRow = 0; iRow < iRowCount; iRow++ )
    {
        json_object * const poRow = json_object_new_object();

        json_object * const poRowIndex = json_object_new_int(iRow);
        json_object_object_add( poRow, "index", poRowIndex );

        json_object * const poFArray = json_object_new_array();

        for( int iCol = 0; iCol < iColCount; iCol++ )
        {
            json_object *poF = nullptr;
            if( GetTypeOfCol(iCol) == GFT_Integer )
                poF = json_object_new_int( GetValueAsInt(iRow, iCol) );
            else if( GetTypeOfCol(iCol) == GFT_Real )
                poF = json_object_new_double_with_precision(
                    GetValueAsDouble(iRow, iCol), 16 );
            else
                poF = json_object_new_string( GetValueAsString(iRow, iCol) );

            json_object_array_add( poFArray, poF );
        }
        json_object_object_add( poRow, "f", poFArray );
        json_object_array_add( poRowArray, poRow );
    }
    json_object_object_add( poRAT, "row", poRowArray );

    return poRAT;
}

/************************************************************************/
/*                              XMLInit()                               */
/************************************************************************/

/** Deserialize from XML.
 * @param psTree XML tree
 * @return error code.
 */
CPLErr GDALRasterAttributeTable::XMLInit( CPLXMLNode *psTree,
                                          const char * /*pszVRTPath*/ )

{
    CPLAssert( GetRowCount() == 0 && GetColumnCount() == 0 );

/* -------------------------------------------------------------------- */
/*      Linear binning.                                                 */
/* -------------------------------------------------------------------- */
    if( CPLGetXMLValue( psTree, "Row0Min", nullptr )
        && CPLGetXMLValue( psTree, "BinSize", nullptr ) )
    {
        SetLinearBinning( CPLAtof(CPLGetXMLValue( psTree, "Row0Min","" )),
                          CPLAtof(CPLGetXMLValue( psTree, "BinSize","" )) );
    }

/* -------------------------------------------------------------------- */
/*      Table Type                                                      */
/* -------------------------------------------------------------------- */
    if( CPLGetXMLValue( psTree, "tableType", nullptr ) )
    {
        const char* pszValue = CPLGetXMLValue(psTree, "tableType", "thematic");
        if (EQUAL(pszValue, "athematic"))
        {
            SetTableType(GRTT_ATHEMATIC);
        }
        else
        {
            SetTableType(GRTT_THEMATIC);
        }
    }


/* -------------------------------------------------------------------- */
/*      Column definitions                                              */
/* -------------------------------------------------------------------- */

    for( CPLXMLNode *psChild = psTree->psChild;
         psChild != nullptr;
         psChild = psChild->psNext)
    {
        if( psChild->eType == CXT_Element
            && EQUAL(psChild->pszValue,"FieldDefn") )
        {
            CreateColumn(
                CPLGetXMLValue( psChild, "Name", "" ),
                static_cast<GDALRATFieldType>(
                    atoi(CPLGetXMLValue( psChild, "Type", "1" )) ),
                static_cast<GDALRATFieldUsage>(
                    atoi(CPLGetXMLValue( psChild, "Usage","0"))) );
        }
    }

/* -------------------------------------------------------------------- */
/*      Row data.                                                       */
/* -------------------------------------------------------------------- */
    for( CPLXMLNode *psChild = psTree->psChild;
         psChild != nullptr;
         psChild = psChild->psNext)
    {
        if( psChild->eType == CXT_Element
            && EQUAL(psChild->pszValue,"Row") )
        {
            const int iRow = atoi(CPLGetXMLValue(psChild,"index","0"));
            int iField = 0;

            for( CPLXMLNode *psF = psChild->psChild;
                 psF != nullptr;
                 psF = psF->psNext )
            {
                if( psF->eType != CXT_Element || !EQUAL(psF->pszValue,"F") )
                    continue;

                if( psF->psChild != nullptr && psF->psChild->eType == CXT_Text )
                    SetValue( iRow, iField++, psF->psChild->pszValue );
                else
                    SetValue( iRow, iField++, "" );
            }
        }
    }

    return CE_None;
}

/************************************************************************/
/*                      InitializeFromColorTable()                      */
/************************************************************************/

/**
 * \brief Initialize from color table.
 *
 * This method will setup a whole raster attribute table based on the
 * contents of the passed color table.  The Value (GFU_MinMax),
 * Red (GFU_Red), Green (GFU_Green), Blue (GFU_Blue), and Alpha (GFU_Alpha)
 * fields are created, and a row is set for each entry in the color table.
 *
 * The raster attribute table must be empty before calling
 * InitializeFromColorTable().
 *
 * The Value fields are set based on the implicit assumption with color
 * tables that entry 0 applies to pixel value 0, 1 to 1, etc.
 *
 * This method is the same as the C function GDALRATInitializeFromColorTable().
 *
 * @param poTable the color table to copy from.
 *
 * @return CE_None on success or CE_Failure if something goes wrong.
 */

CPLErr GDALRasterAttributeTable::InitializeFromColorTable(
    const GDALColorTable *poTable )

{
    if( GetRowCount() > 0 || GetColumnCount() > 0 )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Raster Attribute Table not empty in "
                  "InitializeFromColorTable()" );
        return CE_Failure;
    }

    SetLinearBinning( 0.0, 1.0 );
    CreateColumn( "Value", GFT_Integer, GFU_MinMax );
    CreateColumn( "Red", GFT_Integer, GFU_Red );
    CreateColumn( "Green", GFT_Integer, GFU_Green );
    CreateColumn( "Blue", GFT_Integer, GFU_Blue );
    CreateColumn( "Alpha", GFT_Integer, GFU_Alpha );

    SetRowCount( poTable->GetColorEntryCount() );

    for( int iRow = 0; iRow < poTable->GetColorEntryCount(); iRow++ )
    {
        GDALColorEntry sEntry;

        poTable->GetColorEntryAsRGB( iRow, &sEntry );

        SetValue( iRow, 0, iRow );
        SetValue( iRow, 1, sEntry.c1 );
        SetValue( iRow, 2, sEntry.c2 );
        SetValue( iRow, 3, sEntry.c3 );
        SetValue( iRow, 4, sEntry.c4 );
    }

    return CE_None;
}

/************************************************************************/
/*                  GDALRATInitializeFromColorTable()                   */
/************************************************************************/

/**
 * \brief Initialize from color table.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::InitializeFromColorTable()
 */
CPLErr CPL_STDCALL
GDALRATInitializeFromColorTable( GDALRasterAttributeTableH hRAT,
                                 GDALColorTableH hCT )

{
    VALIDATE_POINTER1( hRAT, "GDALRATInitializeFromColorTable", CE_Failure );

    return GDALRasterAttributeTable::FromHandle(hRAT)->
        InitializeFromColorTable( GDALColorTable::FromHandle(hCT) );
}

/************************************************************************/
/*                       TranslateToColorTable()                        */
/************************************************************************/

/**
 * \brief Translate to a color table.
 *
 * This method will attempt to create a corresponding GDALColorTable from
 * this raster attribute table.
 *
 * This method is the same as the C function GDALRATTranslateToColorTable().
 *
 * @param nEntryCount The number of entries to produce (0 to nEntryCount-1),
 * or -1 to auto-determine the number of entries.
 *
 * @return the generated color table or NULL on failure.
 */

GDALColorTable *GDALRasterAttributeTable::TranslateToColorTable(
    int nEntryCount )

{
/* -------------------------------------------------------------------- */
/*      Establish which fields are red, green, blue and alpha.          */
/* -------------------------------------------------------------------- */
    const int iRed = GetColOfUsage( GFU_Red );
    const int iGreen = GetColOfUsage( GFU_Green );
    const int iBlue = GetColOfUsage( GFU_Blue );

    if( iRed == -1 || iGreen == -1 || iBlue == -1 )
        return nullptr;

    const int iAlpha = GetColOfUsage( GFU_Alpha );

/* -------------------------------------------------------------------- */
/*      If we aren't given an explicit number of values to scan for,    */
/*      search for the maximum "max" value.                             */
/* -------------------------------------------------------------------- */
    if( nEntryCount == -1 )
    {
        int iMaxCol = GetColOfUsage( GFU_Max );
        if( iMaxCol == -1 )
            iMaxCol = GetColOfUsage( GFU_MinMax );

        if( iMaxCol == -1 || GetRowCount() == 0 )
            return nullptr;

        for( int iRow = 0; iRow < GetRowCount(); iRow++ )
            nEntryCount =
                std::max(nEntryCount, GetValueAsInt(iRow, iMaxCol) + 1);

        if( nEntryCount < 0 )
            return nullptr;

        // Restrict our number of entries to something vaguely sensible.
        nEntryCount = std::min(65535, nEntryCount);
    }

/* -------------------------------------------------------------------- */
/*      Assign values to color table.                                   */
/* -------------------------------------------------------------------- */
    GDALColorTable *poCT = new GDALColorTable();

    for( int iEntry = 0; iEntry < nEntryCount; iEntry++ )
    {
        GDALColorEntry sColor = { 0, 0, 0, 0 };
        const int iRow = GetRowOfValue( iEntry );

        if( iRow != -1 )
        {
            sColor.c1 = static_cast<short>( GetValueAsInt( iRow, iRed ) );
            sColor.c2 = static_cast<short>( GetValueAsInt( iRow, iGreen ) );
            sColor.c3 = static_cast<short>( GetValueAsInt( iRow, iBlue ) );
            if( iAlpha == -1 )
                sColor.c4 = 255;
            else
                sColor.c4 = static_cast<short>( GetValueAsInt( iRow, iAlpha ) );
        }

        poCT->SetColorEntry( iEntry, &sColor );
    }

    return poCT;
}

/************************************************************************/
/*                  GDALRATInitializeFromColorTable()                   */
/************************************************************************/

/**
 * \brief Translate to a color table.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::TranslateToColorTable()
 */
GDALColorTableH CPL_STDCALL
GDALRATTranslateToColorTable( GDALRasterAttributeTableH hRAT,
                              int nEntryCount )

{
    VALIDATE_POINTER1( hRAT, "GDALRATTranslateToColorTable", nullptr );

    return GDALRasterAttributeTable::FromHandle(hRAT)->
        TranslateToColorTable( nEntryCount );
}

/************************************************************************/
/*                            DumpReadable()                            */
/************************************************************************/

/**
 * \brief Dump RAT in readable form.
 *
 * Currently the readable form is the XML encoding ... only barely
 * readable.
 *
 * This method is the same as the C function GDALRATDumpReadable().
 *
 * @param fp file to dump to or NULL for stdout.
 */

void GDALRasterAttributeTable::DumpReadable( FILE * fp )

{
    CPLXMLNode *psTree = Serialize();
    char * const pszXMLText = CPLSerializeXMLTree( psTree );

    CPLDestroyXMLNode( psTree );

    if( fp == nullptr )
        fp = stdout;

    fprintf( fp, "%s\n", pszXMLText );

    CPLFree( pszXMLText );
}

/************************************************************************/
/*                        GDALRATDumpReadable()                         */
/************************************************************************/

/**
 * \brief Dump RAT in readable form.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::DumpReadable()
 */
void CPL_STDCALL
GDALRATDumpReadable( GDALRasterAttributeTableH hRAT, FILE *fp )

{
    VALIDATE_POINTER0( hRAT, "GDALRATDumpReadable" );

    GDALRasterAttributeTable::FromHandle(hRAT)->DumpReadable( fp );
}

/* \class GDALDefaultRasterAttributeTable
 *
 * An implementation of GDALRasterAttributeTable that keeps
 * all data in memory. This is the same as the implementation
 * of GDALRasterAttributeTable in GDAL <= 1.10.
 */

/************************************************************************/
/*                  GDALDefaultRasterAttributeTable()                   */
/*                                                                      */
/*      Simple initialization constructor.                              */
/************************************************************************/

//! Construct empty table.

GDALDefaultRasterAttributeTable::GDALDefaultRasterAttributeTable() :
    bLinearBinning(false),
    dfRow0Min(-0.5),
    dfBinSize(1.0),
    eTableType(GRTT_THEMATIC),
    bColumnsAnalysed(false),
    nMinCol(-1),
    nMaxCol(-1),
    nRowCount(0)
{}

/************************************************************************/
/*                   GDALCreateRasterAttributeTable()                   */
/************************************************************************/

/**
 * \brief Construct empty table.
 *
 * This function is the same as the C++ method
 * GDALDefaultRasterAttributeTable::GDALDefaultRasterAttributeTable()
 */
GDALRasterAttributeTableH CPL_STDCALL GDALCreateRasterAttributeTable()

{
    return new GDALDefaultRasterAttributeTable();
}

/************************************************************************/
/*                 ~GDALDefaultRasterAttributeTable()                   */
/*                                                                      */
/*      All magic done by magic by the container destructors.           */
/************************************************************************/

GDALDefaultRasterAttributeTable::~GDALDefaultRasterAttributeTable() = default;

/************************************************************************/
/*                  GDALDestroyRasterAttributeTable()                   */
/************************************************************************/

/**
 * \brief Destroys a RAT.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::~GDALRasterAttributeTable()
 */
void CPL_STDCALL
GDALDestroyRasterAttributeTable( GDALRasterAttributeTableH hRAT )

{
    if( hRAT != nullptr )
        delete GDALRasterAttributeTable::FromHandle(hRAT);
}

/************************************************************************/
/*                           AnalyseColumns()                           */
/*                                                                      */
/*      Internal method to work out which column to use for various     */
/*      tasks.                                                          */
/************************************************************************/

void GDALDefaultRasterAttributeTable::AnalyseColumns()

{
    bColumnsAnalysed = true;

    nMinCol = GetColOfUsage( GFU_Min );
    if( nMinCol == -1 )
        nMinCol = GetColOfUsage( GFU_MinMax );

    nMaxCol = GetColOfUsage( GFU_Max );
    if( nMaxCol == -1 )
        nMaxCol = GetColOfUsage( GFU_MinMax );
}

/************************************************************************/
/*                           GetColumnCount()                           */
/************************************************************************/

int GDALDefaultRasterAttributeTable::GetColumnCount() const

{
    return static_cast<int>(aoFields.size());
}

/************************************************************************/
/*                       GDALRATGetColumnCount()                        */
/************************************************************************/

/**
 * \brief Fetch table column count.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::GetColumnCount()
 */
int CPL_STDCALL GDALRATGetColumnCount( GDALRasterAttributeTableH hRAT )

{
    VALIDATE_POINTER1( hRAT, "GDALRATGetColumnCount", 0 );

    return GDALRasterAttributeTable::FromHandle(hRAT)->GetColumnCount();
}

/************************************************************************/
/*                            GetNameOfCol()                            */
/************************************************************************/

/** \brief Fetch name of indicated column.
 * @param iCol column index.
 * @return name.
 */
const char *GDALDefaultRasterAttributeTable::GetNameOfCol( int iCol ) const

{
    if( iCol < 0 || iCol >= static_cast<int>(aoFields.size()) )
        return "";

    return aoFields[iCol].sName;
}

/************************************************************************/
/*                        GDALRATGetNameOfCol()                         */
/************************************************************************/

/**
 * \brief Fetch name of indicated column.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::GetNameOfCol()
 * @param hRAT RAT handle.
 * @param iCol column index.
 * @return name.
 */
const char *CPL_STDCALL GDALRATGetNameOfCol( GDALRasterAttributeTableH hRAT,
                                             int iCol )

{
    VALIDATE_POINTER1( hRAT, "GDALRATGetNameOfCol", nullptr );

    return GDALRasterAttributeTable::FromHandle(hRAT)->GetNameOfCol( iCol );
}

/************************************************************************/
/*                           GetUsageOfCol()                            */
/************************************************************************/

/**
 * \brief Fetch column usage value.
 *
 * @param iCol column index.
 * @return usage.
 */
GDALRATFieldUsage GDALDefaultRasterAttributeTable::GetUsageOfCol(
    int iCol ) const

{
    if( iCol < 0 || iCol >= static_cast<int>(aoFields.size()) )
        return GFU_Generic;

    return aoFields[iCol].eUsage;
}

/************************************************************************/
/*                        GDALRATGetUsageOfCol()                        */
/************************************************************************/

/**
 * \brief Fetch column usage value.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::GetUsageOfCol()
 * @param hRAT RAT handle.
 * @param iCol column index.
 * @return usage.
 */
GDALRATFieldUsage CPL_STDCALL
GDALRATGetUsageOfCol( GDALRasterAttributeTableH hRAT, int iCol )

{
    VALIDATE_POINTER1( hRAT, "GDALRATGetUsageOfCol", GFU_Generic );

    return GDALRasterAttributeTable::FromHandle(hRAT)->GetUsageOfCol( iCol );
}

/************************************************************************/
/*                            GetTypeOfCol()                            */
/************************************************************************/

/**
 * \brief Fetch column type.
 *
 * @param iCol column index.
 * @return type.
 */
GDALRATFieldType GDALDefaultRasterAttributeTable::GetTypeOfCol( int iCol ) const

{
    if( iCol < 0 || iCol >= static_cast<int>(aoFields.size()) )
        return GFT_Integer;

    return aoFields[iCol].eType;
}

/************************************************************************/
/*                        GDALRATGetTypeOfCol()                         */
/************************************************************************/

/**
 * \brief Fetch column type.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::GetTypeOfCol()
 * @param hRAT RAT handle.
 * @param iCol column index.
 * @return type.
 */
GDALRATFieldType CPL_STDCALL
GDALRATGetTypeOfCol( GDALRasterAttributeTableH hRAT, int iCol )

{
    VALIDATE_POINTER1( hRAT, "GDALRATGetTypeOfCol", GFT_Integer );

    return GDALRasterAttributeTable::FromHandle(hRAT)->GetTypeOfCol( iCol );
}

/************************************************************************/
/*                           GetColOfUsage()                            */
/************************************************************************/

/** Return the index of the column that corresponds to the passed usage.
 * @param eUsage usage.
 * @return column index, or -1 in case of error.
 */
int GDALDefaultRasterAttributeTable::GetColOfUsage(
    GDALRATFieldUsage eUsage ) const

{
    for( unsigned int i = 0; i < aoFields.size(); i++ )
    {
        if( aoFields[i].eUsage == eUsage )
            return i;
    }

    return -1;
}

/************************************************************************/
/*                        GDALRATGetColOfUsage()                        */
/************************************************************************/

/**
 * \brief Fetch column index for given usage.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::GetColOfUsage()
 */
int CPL_STDCALL
GDALRATGetColOfUsage( GDALRasterAttributeTableH hRAT, GDALRATFieldUsage eUsage )

{
    VALIDATE_POINTER1( hRAT, "GDALRATGetColOfUsage", 0 );

    return GDALRasterAttributeTable::FromHandle(hRAT)->
        GetColOfUsage( eUsage );
}

/************************************************************************/
/*                            GetRowCount()                             */
/************************************************************************/

int GDALDefaultRasterAttributeTable::GetRowCount() const

{
    return static_cast<int>( nRowCount );
}

/************************************************************************/
/*                        GDALRATGetUsageOfCol()                        */
/************************************************************************/
/**
 * \brief Fetch row count.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::GetRowCount()
 */
int CPL_STDCALL
GDALRATGetRowCount( GDALRasterAttributeTableH hRAT )

{
    VALIDATE_POINTER1( hRAT, "GDALRATGetRowCount", 0 );

    return GDALRasterAttributeTable::FromHandle(hRAT)->GetRowCount();
}

/************************************************************************/
/*                          GetValueAsString()                          */
/************************************************************************/

const char *
GDALDefaultRasterAttributeTable::GetValueAsString( int iRow, int iField ) const

{
    if( iField < 0 || iField >= static_cast<int>(aoFields.size()) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iField (%d) out of range.", iField );

        return "";
    }

    if( iRow < 0 || iRow >= nRowCount )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iRow (%d) out of range.", iRow );

        return "";
    }

    switch( aoFields[iField].eType )
    {
      case GFT_Integer:
      {
          const_cast<GDALDefaultRasterAttributeTable *>(this)->
              osWorkingResult.Printf( "%d", aoFields[iField].anValues[iRow] );
          return osWorkingResult;
      }

      case GFT_Real:
      {
          const_cast<GDALDefaultRasterAttributeTable *>(this)->
             osWorkingResult.Printf( "%.16g", aoFields[iField].adfValues[iRow]);
          return osWorkingResult;
      }

      case GFT_String:
      {
          return aoFields[iField].aosValues[iRow];
      }
    }

    return "";
}

/************************************************************************/
/*                      GDALRATGetValueAsString()                       */
/************************************************************************/
/**
 * \brief Fetch field value as a string.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::GetValueAsString()
 */
const char * CPL_STDCALL
GDALRATGetValueAsString( GDALRasterAttributeTableH hRAT, int iRow, int iField )

{
    VALIDATE_POINTER1( hRAT, "GDALRATGetValueAsString", nullptr );

    return GDALRasterAttributeTable::FromHandle(hRAT)->
        GetValueAsString(iRow, iField);
}

/************************************************************************/
/*                           GetValueAsInt()                            */
/************************************************************************/

int
GDALDefaultRasterAttributeTable::GetValueAsInt( int iRow, int iField ) const

{
    if( iField < 0 || iField >= static_cast<int>(aoFields.size()) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iField (%d) out of range.", iField );

        return 0;
    }

    if( iRow < 0 || iRow >= nRowCount )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iRow (%d) out of range.", iRow );

        return 0;
    }

    switch( aoFields[iField].eType )
    {
      case GFT_Integer:
        return aoFields[iField].anValues[iRow];

      case GFT_Real:
        return static_cast<int>( aoFields[iField].adfValues[iRow] );

      case GFT_String:
        return atoi( aoFields[iField].aosValues[iRow].c_str() );
    }

    return 0;
}

/************************************************************************/
/*                        GDALRATGetValueAsInt()                        */
/************************************************************************/

/**
 * \brief Fetch field value as a integer.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::GetValueAsInt()
 */
int CPL_STDCALL
GDALRATGetValueAsInt( GDALRasterAttributeTableH hRAT, int iRow, int iField )

{
    VALIDATE_POINTER1( hRAT, "GDALRATGetValueAsInt", 0 );

    return GDALRasterAttributeTable::FromHandle(hRAT)->
        GetValueAsInt( iRow, iField );
}

/************************************************************************/
/*                          GetValueAsDouble()                          */
/************************************************************************/

double
GDALDefaultRasterAttributeTable::GetValueAsDouble( int iRow, int iField ) const

{
    if( iField < 0 || iField >= static_cast<int>(aoFields.size()) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iField (%d) out of range.", iField );

        return 0;
    }

    if( iRow < 0 || iRow >= nRowCount )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iRow (%d) out of range.", iRow );

        return 0;
    }

    switch( aoFields[iField].eType )
    {
      case GFT_Integer:
        return aoFields[iField].anValues[iRow];

      case GFT_Real:
        return aoFields[iField].adfValues[iRow];

      case GFT_String:
        return CPLAtof( aoFields[iField].aosValues[iRow].c_str() );
    }

    return 0;
}

/************************************************************************/
/*                      GDALRATGetValueAsDouble()                       */
/************************************************************************/

/**
 * \brief Fetch field value as a double.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::GetValueAsDouble()
 */
double CPL_STDCALL
GDALRATGetValueAsDouble( GDALRasterAttributeTableH hRAT, int iRow, int iField )

{
    VALIDATE_POINTER1( hRAT, "GDALRATGetValueAsDouble", 0 );

    return GDALRasterAttributeTable::FromHandle(hRAT)->
        GetValueAsDouble(iRow,iField);
}

/************************************************************************/
/*                            SetRowCount()                             */
/************************************************************************/

/** Set row count.
 * @param nNewCount new count.
 */
void GDALDefaultRasterAttributeTable::SetRowCount( int nNewCount )

{
    if( nNewCount == nRowCount )
        return;

    for( auto& oField: aoFields )
    {
        switch( oField.eType )
        {
          case GFT_Integer:
            oField.anValues.resize( nNewCount );
            break;

          case GFT_Real:
            oField.adfValues.resize( nNewCount );
            break;

          case GFT_String:
            oField.aosValues.resize( nNewCount );
            break;
        }
    }

    nRowCount = nNewCount;
}

/************************************************************************/
/*                              SetValue()                              */
/************************************************************************/

/** Set value
 * @param iRow row index.
 * @param iField field index.
 * @param pszValue value.
 */
void GDALDefaultRasterAttributeTable::SetValue( int iRow, int iField,
                                                const char *pszValue )

{
    if( iField < 0 || iField >= static_cast<int>(aoFields.size()) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iField (%d) out of range.", iField );

        return;
    }

    if( iRow == nRowCount )
        SetRowCount( nRowCount + 1 );

    if( iRow < 0 || iRow >= nRowCount )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iRow (%d) out of range.", iRow );

        return;
    }

    switch( aoFields[iField].eType )
    {
      case GFT_Integer:
        aoFields[iField].anValues[iRow] = atoi(pszValue);
        break;

      case GFT_Real:
        aoFields[iField].adfValues[iRow] = CPLAtof(pszValue);
        break;

      case GFT_String:
        aoFields[iField].aosValues[iRow] = pszValue;
        break;
    }
}

/************************************************************************/
/*                      GDALRATSetValueAsString()                       */
/************************************************************************/

/**
 * \brief Set field value from string.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::SetValue()
 * @param hRAT RAT handle.
 * @param iRow row index.
 * @param iField field index.
 * @param pszValue value.
 */
void CPL_STDCALL
GDALRATSetValueAsString( GDALRasterAttributeTableH hRAT, int iRow, int iField,
                         const char *pszValue )

{
    VALIDATE_POINTER0( hRAT, "GDALRATSetValueAsString" );

    GDALRasterAttributeTable::FromHandle(hRAT)->
        SetValue( iRow, iField, pszValue );
}

/************************************************************************/
/*                              SetValue()                              */
/************************************************************************/

void GDALDefaultRasterAttributeTable::SetValue( int iRow, int iField,
                                                int nValue )

{
    if( iField < 0 || iField >= static_cast<int>( aoFields.size() ) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iField (%d) out of range.", iField );

        return;
    }

    if( iRow == nRowCount )
        SetRowCount( nRowCount+1 );

    if( iRow < 0 || iRow >= nRowCount )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iRow (%d) out of range.", iRow );

        return;
    }

    switch( aoFields[iField].eType )
    {
      case GFT_Integer:
        aoFields[iField].anValues[iRow] = nValue;
        break;

      case GFT_Real:
        aoFields[iField].adfValues[iRow] = nValue;
        break;

      case GFT_String:
      {
          char szValue[100];

          snprintf( szValue, sizeof(szValue), "%d", nValue );
          aoFields[iField].aosValues[iRow] = szValue;
      }
      break;
    }
}

/************************************************************************/
/*                        GDALRATSetValueAsInt()                        */
/************************************************************************/

/**
 * \brief Set field value from integer.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::SetValue()
 */
void CPL_STDCALL
GDALRATSetValueAsInt( GDALRasterAttributeTableH hRAT, int iRow, int iField,
                      int nValue )

{
    VALIDATE_POINTER0( hRAT, "GDALRATSetValueAsInt" );

    GDALRasterAttributeTable::FromHandle(hRAT)->
        SetValue( iRow, iField, nValue);
}

/************************************************************************/
/*                              SetValue()                              */
/************************************************************************/

void GDALDefaultRasterAttributeTable::SetValue( int iRow, int iField,
                                                double dfValue )

{
    if( iField < 0 || iField >= static_cast<int>(aoFields.size()) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iField (%d) out of range.", iField );

        return;
    }

    if( iRow == nRowCount )
        SetRowCount( nRowCount+1 );

    if( iRow < 0 || iRow >= nRowCount )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iRow (%d) out of range.", iRow );

        return;
    }

    switch( aoFields[iField].eType )
    {
      case GFT_Integer:
        aoFields[iField].anValues[iRow] = static_cast<int>(dfValue);
        break;

      case GFT_Real:
        aoFields[iField].adfValues[iRow] = dfValue;
        break;

      case GFT_String:
      {
          char szValue[100] = { '\0' };

          CPLsnprintf( szValue, sizeof(szValue), "%.15g", dfValue );
          aoFields[iField].aosValues[iRow] = szValue;
      }
      break;
    }
}

/************************************************************************/
/*                      GDALRATSetValueAsDouble()                       */
/************************************************************************/

/**
 * \brief Set field value from double.
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::SetValue()
 */
void CPL_STDCALL
GDALRATSetValueAsDouble( GDALRasterAttributeTableH hRAT, int iRow, int iField,
                         double dfValue )

{
    VALIDATE_POINTER0( hRAT, "GDALRATSetValueAsDouble" );

    GDALRasterAttributeTable::FromHandle(hRAT)->
        SetValue( iRow, iField, dfValue );
}

/************************************************************************/
/*                       ChangesAreWrittenToFile()                      */
/************************************************************************/

int GDALDefaultRasterAttributeTable::ChangesAreWrittenToFile()
{
    // GDALRasterBand.SetDefaultRAT needs to be called on instances of
    // GDALDefaultRasterAttributeTable since changes are just in-memory
    return false;
}

/************************************************************************/
/*                   GDALRATChangesAreWrittenToFile()                   */
/************************************************************************/

/**
 * \brief Determine whether changes made to this RAT are reflected directly in
 * the dataset
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::ChangesAreWrittenToFile()
 */
int CPL_STDCALL
GDALRATChangesAreWrittenToFile( GDALRasterAttributeTableH hRAT )
{
    VALIDATE_POINTER1( hRAT, "GDALRATChangesAreWrittenToFile", false );

    return GDALRasterAttributeTable::FromHandle(hRAT)->
        ChangesAreWrittenToFile();
}

/************************************************************************/
/*                           GetRowOfValue()                            */
/************************************************************************/

int GDALDefaultRasterAttributeTable::GetRowOfValue( double dfValue ) const

{
/* -------------------------------------------------------------------- */
/*      Handle case of regular binning.                                 */
/* -------------------------------------------------------------------- */
    if( bLinearBinning )
    {
        const int iBin = static_cast<int>(
            floor((dfValue - dfRow0Min) / dfBinSize) );
        if( iBin < 0 || iBin >= nRowCount )
            return -1;

        return iBin;
    }

/* -------------------------------------------------------------------- */
/*      Do we have any information?                                     */
/* -------------------------------------------------------------------- */
    if( !bColumnsAnalysed )
        const_cast<GDALDefaultRasterAttributeTable *>(this)->AnalyseColumns();

    if( nMinCol == -1 && nMaxCol == -1 )
        return -1;

    const GDALRasterAttributeField *poMin = nullptr;
    if( nMinCol != -1 )
        poMin = &(aoFields[nMinCol]);
    else
        poMin = nullptr;

    const GDALRasterAttributeField *poMax = nullptr;
    if( nMaxCol != -1 )
        poMax = &(aoFields[nMaxCol]);
    else
        poMax = nullptr;

/* -------------------------------------------------------------------- */
/*      Search through rows for match.                                  */
/* -------------------------------------------------------------------- */
    for( int iRow = 0; iRow < nRowCount; iRow++ )
    {
        if( poMin != nullptr )
        {
            if( poMin->eType == GFT_Integer )
            {
                while( iRow < nRowCount && dfValue < poMin->anValues[iRow] )
                    iRow++;
            }
            else if( poMin->eType == GFT_Real )
            {
                while( iRow < nRowCount && dfValue < poMin->adfValues[iRow] )
                    iRow++;
            }

            if( iRow == nRowCount )
                break;
        }

        if( poMax != nullptr )
        {
            if( (poMax->eType == GFT_Integer
                 && dfValue > poMax->anValues[iRow] ) ||
                (poMax->eType == GFT_Real
                 && dfValue > poMax->adfValues[iRow] ) )
                continue;
        }

        return iRow;
    }

    return -1;
}

/************************************************************************/
/*                           GetRowOfValue()                            */
/*                                                                      */
/*      Int arg for now just converted to double.  Perhaps we will      */
/*      handle this in a special way some day?                          */
/************************************************************************/

int GDALDefaultRasterAttributeTable::GetRowOfValue( int nValue ) const

{
    return GetRowOfValue( static_cast<double>( nValue ) );
}

/************************************************************************/
/*                          SetLinearBinning()                          */
/************************************************************************/

CPLErr GDALDefaultRasterAttributeTable::SetLinearBinning( double dfRow0MinIn,
                                                          double dfBinSizeIn )

{
    bLinearBinning = true;
    dfRow0Min = dfRow0MinIn;
    dfBinSize = dfBinSizeIn;

    return CE_None;
}

/************************************************************************/
/*                          GetLinearBinning()                          */
/************************************************************************/

int GDALDefaultRasterAttributeTable::GetLinearBinning(
    double *pdfRow0Min, double *pdfBinSize ) const

{
    if( !bLinearBinning )
        return false;

    *pdfRow0Min = dfRow0Min;
    *pdfBinSize = dfBinSize;

    return true;
}

/************************************************************************/
/*                          GetTableType()                              */
/************************************************************************/

/**
 * \brief Get RAT Table Type
 *
 * Returns whether table type is thematic or athematic
 *
 * This method is the same as the C function GDALRATGetTableType().
 *
 * @since GDAL 2.4
 *
 * @return GRTT_THEMATIC or GRTT_ATHEMATIC
 */

GDALRATTableType GDALDefaultRasterAttributeTable::GetTableType() const
{
    return eTableType;
}

/************************************************************************/
/*                          SetTableType()                              */
/************************************************************************/

/**
 * \brief Set RAT Table Type
 *
 * Set whether table type is thematic or athematic
 *
 * This method is the same as the C function GDALRATSetTableType().
 *
 * @param eInTableType the new RAT table type (GRTT_THEMATIC or GRTT_ATHEMATIC)
 *
 * @since GDAL 2.4
 *
 * @return CE_None on success or CE_Failure on failure.
 */

CPLErr GDALDefaultRasterAttributeTable::SetTableType(const GDALRATTableType eInTableType)
{
    eTableType = eInTableType;
    return CE_None;
}


/************************************************************************/
/*                            CreateColumn()                            */
/************************************************************************/

CPLErr GDALDefaultRasterAttributeTable::CreateColumn(
    const char *pszFieldName,
    GDALRATFieldType eFieldType,
    GDALRATFieldUsage eFieldUsage )

{
    const size_t iNewField = aoFields.size();

    aoFields.resize( iNewField+1 );

    aoFields[iNewField].sName = pszFieldName;

    // color columns should be int 0..255
    if( ( eFieldUsage == GFU_Red ) || ( eFieldUsage == GFU_Green ) ||
        ( eFieldUsage == GFU_Blue ) || ( eFieldUsage == GFU_Alpha ) )
    {
        eFieldType = GFT_Integer;
    }
    aoFields[iNewField].eType = eFieldType;
    aoFields[iNewField].eUsage = eFieldUsage;

    if( eFieldType == GFT_Integer )
        aoFields[iNewField].anValues.resize( nRowCount );
    else if( eFieldType == GFT_Real )
        aoFields[iNewField].adfValues.resize( nRowCount );
    else if( eFieldType == GFT_String )
        aoFields[iNewField].aosValues.resize( nRowCount );

    return CE_None;
}

/************************************************************************/
/*                            RemoveStatistics()                        */
/************************************************************************/

/**
 * \brief Remove Statistics from RAT
 *
 * Remove statistics (such as histogram) from the RAT. This is important
 * if these have been invalidated, for example by cropping the image.
 *
 * This method is the same as the C function GDALRATRemoveStatistics().
 *
 * @since GDAL 2.4
 */

void GDALDefaultRasterAttributeTable::RemoveStatistics()

{
    // since we are storing the fields in a vector it will generally
    // be faster to create a new vector and replace the old one
    // rather than actually erasing columns.
    std::vector<GDALRasterAttributeField> aoNewFields;
    for ( const auto& field : aoFields )
    {
        switch (field.eUsage)
        {
            case GFU_PixelCount:
            case GFU_Min:
            case GFU_Max:
            case GFU_RedMin:
            case GFU_GreenMin:
            case GFU_BlueMin:
            case GFU_AlphaMin:
            case GFU_RedMax:
            case GFU_GreenMax:
            case GFU_BlueMax:
            case GFU_AlphaMax:
            {
                break;
            }

            default:
                if (field.sName != "Histogram")
                {
                    aoNewFields.push_back(field);
                }
        }
    }
    aoFields = aoNewFields;
}

/************************************************************************/
/*                               Clone()                                */
/************************************************************************/

GDALDefaultRasterAttributeTable *GDALDefaultRasterAttributeTable::Clone() const

{
    return new GDALDefaultRasterAttributeTable( *this );
}

/************************************************************************/
/*                            GDALRATClone()                            */
/************************************************************************/

/**
 * \brief Copy Raster Attribute Table
 *
 * This function is the same as the C++ method GDALRasterAttributeTable::Clone()
 */
GDALRasterAttributeTableH CPL_STDCALL
GDALRATClone( const GDALRasterAttributeTableH hRAT )

{
    VALIDATE_POINTER1( hRAT, "GDALRATClone", nullptr );

    return GDALRasterAttributeTable::FromHandle(hRAT)->Clone();
}

/************************************************************************/
/*                            GDALRATSerializeJSON()                    */
/************************************************************************/

/**
 * \brief Serialize Raster Attribute Table in Json format
 *
 * This function is the same as the C++ method
 * GDALRasterAttributeTable::SerializeJSON()
 */
void* CPL_STDCALL
GDALRATSerializeJSON( GDALRasterAttributeTableH hRAT )

{
    VALIDATE_POINTER1( hRAT, "GDALRATSerializeJSON", nullptr );

    return GDALRasterAttributeTable::FromHandle(hRAT)->SerializeJSON();
}


/************************************************************************/
/*                        GDALRATRemoveStatistics()                     */
/************************************************************************/

/**
 * \brief Remove Statistics from RAT
 *
 * This function is the same as the C++ method GDALRasterAttributeTable::RemoveStatistics()
 *
 * @since GDAL 2.4
 */
void CPL_STDCALL
GDALRATRemoveStatistics( GDALRasterAttributeTableH hRAT )

{
    VALIDATE_POINTER0( hRAT, "GDALRATRemoveStatistics" );

    GDALRasterAttributeTable::FromHandle(hRAT)->RemoveStatistics();
}
