/*
 *  kearat.cpp
 *
 *  Created by Pete Bunting on 01/08/2012.
 *  Copyright 2012 LibKEA. All rights reserved.
 *
 *  This file is part of LibKEA.
 *
 *  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 "kearat.h"

KEARasterAttributeTable::KEARasterAttributeTable(
    kealib::KEAAttributeTable *poKEATable, KEARasterBand *poBand)
{
    this->m_hMutex = CPLCreateMutex();
    CPLReleaseMutex(this->m_hMutex);
    for (size_t nColumnIndex = 0;
         nColumnIndex < poKEATable->getMaxGlobalColIdx(); nColumnIndex++)
    {
        kealib::KEAATTField sKEAField;
        try
        {
            sKEAField = poKEATable->getField(nColumnIndex);
        }
        catch (const kealib::KEAATTException &)
        {
            // pKEATable->getField raised exception because we have a missing
            // column
            continue;
        }
        m_aoFields.push_back(sKEAField);
    }
    m_poKEATable = poKEATable;
    m_poBand = poBand;
}

KEARasterAttributeTable::~KEARasterAttributeTable()
{
    // can't just delete thanks to Windows
    kealib::KEAAttributeTable::destroyAttributeTable(m_poKEATable);
    CPLDestroyMutex(m_hMutex);
    m_hMutex = nullptr;
}

GDALDefaultRasterAttributeTable *KEARasterAttributeTable::Clone() const
{
    if ((GetRowCount() * GetColumnCount()) > RAT_MAX_ELEM_FOR_CLONE)
        return nullptr;

    GDALDefaultRasterAttributeTable *poRAT =
        new GDALDefaultRasterAttributeTable();

    for (int iCol = 0; iCol < (int)m_aoFields.size(); iCol++)
    {
        CPLString sName = m_aoFields[iCol].name;
        CPLString sUsage = m_aoFields[iCol].usage;
        GDALRATFieldUsage eGDALUsage;
        if (sUsage == "PixelCount")
            eGDALUsage = GFU_PixelCount;
        else if (sUsage == "Name")
            eGDALUsage = GFU_Name;
        else if (sUsage == "Red")
            eGDALUsage = GFU_Red;
        else if (sUsage == "Green")
            eGDALUsage = GFU_Green;
        else if (sUsage == "Blue")
            eGDALUsage = GFU_Blue;
        else if (sUsage == "Alpha")
            eGDALUsage = GFU_Alpha;
        else
        {
            // don't recognise any other special names - generic column
            eGDALUsage = GFU_Generic;
        }

        GDALRATFieldType eGDALType;
        switch (m_aoFields[iCol].dataType)
        {
            case kealib::kea_att_bool:
            case kealib::kea_att_int:
                eGDALType = GFT_Integer;
                break;
            case kealib::kea_att_float:
                eGDALType = GFT_Real;
                break;
            case kealib::kea_att_string:
                eGDALType = GFT_String;
                break;
            default:
                eGDALType = GFT_Integer;
                break;
        }
        poRAT->CreateColumn(sName, eGDALType, eGDALUsage);
        poRAT->SetRowCount(static_cast<int>(m_poKEATable->getSize()));

        if (m_poKEATable->getSize() == 0)
            continue;

        if (eGDALType == GFT_Integer)
        {
            int *panColData = (int *)VSI_MALLOC2_VERBOSE(
                sizeof(int), m_poKEATable->getSize());
            if (panColData == nullptr)
            {
                delete poRAT;
                return nullptr;
            }

            if ((const_cast<KEARasterAttributeTable *>(this))
                    ->ValuesIO(GF_Read, iCol, 0,
                               static_cast<int>(m_poKEATable->getSize()),
                               panColData) != CE_None)
            {
                CPLFree(panColData);
                delete poRAT;
                return nullptr;
            }

            for (int iRow = 0; iRow < (int)m_poKEATable->getSize(); iRow++)
            {
                poRAT->SetValue(iRow, iCol, panColData[iRow]);
            }
            CPLFree(panColData);
        }
        if (eGDALType == GFT_Real)
        {
            double *padfColData = (double *)VSI_MALLOC2_VERBOSE(
                sizeof(double), m_poKEATable->getSize());
            if (padfColData == nullptr)
            {
                delete poRAT;
                return nullptr;
            }
            if ((const_cast<KEARasterAttributeTable *>(this))
                    ->ValuesIO(GF_Read, iCol, 0,
                               static_cast<int>(m_poKEATable->getSize()),
                               padfColData) != CE_None)
            {
                CPLFree(padfColData);
                delete poRAT;
                return nullptr;
            }

            for (int iRow = 0; iRow < (int)m_poKEATable->getSize(); iRow++)
            {
                poRAT->SetValue(iRow, iCol, padfColData[iRow]);
            }
            CPLFree(padfColData);
        }
        if (eGDALType == GFT_String)
        {
            char **papszColData = (char **)VSI_MALLOC2_VERBOSE(
                sizeof(char *), m_poKEATable->getSize());
            if (papszColData == nullptr)
            {
                delete poRAT;
                return nullptr;
            }

            if ((const_cast<KEARasterAttributeTable *>(this))
                    ->ValuesIO(GF_Read, iCol, 0,
                               static_cast<int>(m_poKEATable->getSize()),
                               papszColData) != CE_None)
            {
                CPLFree(papszColData);
                delete poRAT;
                return nullptr;
            }

            for (int iRow = 0; iRow < (int)m_poKEATable->getSize(); iRow++)
            {
                poRAT->SetValue(iRow, iCol, papszColData[iRow]);
                CPLFree(papszColData[iRow]);
            }
            CPLFree(papszColData);
        }
    }

    poRAT->SetTableType(this->GetTableType());

    return poRAT;
}

int KEARasterAttributeTable::GetColumnCount() const
{
    return (int)m_aoFields.size();
}

const char *KEARasterAttributeTable::GetNameOfCol(int nCol) const
{
    if ((nCol < 0) || (nCol >= (int)m_aoFields.size()))
        return nullptr;

    return m_aoFields[nCol].name.c_str();
}

GDALRATFieldUsage KEARasterAttributeTable::GetUsageOfCol(int nCol) const
{
    if ((nCol < 0) || (nCol >= (int)m_aoFields.size()))
        return GFU_Generic;

    GDALRATFieldUsage eGDALUsage;
    std::string keausage = m_aoFields[nCol].usage;

    if (keausage == "PixelCount")
        eGDALUsage = GFU_PixelCount;
    else if (keausage == "Name")
        eGDALUsage = GFU_Name;
    else if (keausage == "Red")
        eGDALUsage = GFU_Red;
    else if (keausage == "Green")
        eGDALUsage = GFU_Green;
    else if (keausage == "Blue")
        eGDALUsage = GFU_Blue;
    else if (keausage == "Alpha")
        eGDALUsage = GFU_Alpha;
    else
    {
        // don't recognise any other special names - generic column
        eGDALUsage = GFU_Generic;
    }

    return eGDALUsage;
}

GDALRATFieldType KEARasterAttributeTable::GetTypeOfCol(int nCol) const
{
    if ((nCol < 0) || (nCol >= (int)m_aoFields.size()))
        return GFT_Integer;

    GDALRATFieldType eGDALType;
    switch (m_aoFields[nCol].dataType)
    {
        case kealib::kea_att_bool:
        case kealib::kea_att_int:
            eGDALType = GFT_Integer;
            break;
        case kealib::kea_att_float:
            eGDALType = GFT_Real;
            break;
        case kealib::kea_att_string:
            eGDALType = GFT_String;
            break;
        default:
            eGDALType = GFT_Integer;
            break;
    }
    return eGDALType;
}

int KEARasterAttributeTable::GetColOfUsage(GDALRATFieldUsage eUsage) const
{
    unsigned int i;

    std::string keausage;
    switch (eUsage)
    {
        case GFU_PixelCount:
            keausage = "PixelCount";
            break;
        case GFU_Name:
            keausage = "Name";
            break;
        case GFU_Red:
            keausage = "Red";
            break;
        case GFU_Green:
            keausage = "Green";
            break;
        case GFU_Blue:
            keausage = "Blue";
            break;
        case GFU_Alpha:
            keausage = "Alpha";
            break;
        default:
            keausage = "Generic";
            break;
    }

    for (i = 0; i < m_aoFields.size(); i++)
    {
        if (m_aoFields[i].usage == keausage)
            return i;
    }
    return -1;
}

int KEARasterAttributeTable::GetRowCount() const
{
    return (int)m_poKEATable->getSize();
}

const char *KEARasterAttributeTable::GetValueAsString(int iRow,
                                                      int iField) const
{
    // Get ValuesIO do do the work
    char *apszStrList[1];
    if ((const_cast<KEARasterAttributeTable *>(this))
            ->ValuesIO(GF_Read, iField, iRow, 1, apszStrList) != CE_None)
    {
        return "";
    }

    const_cast<KEARasterAttributeTable *>(this)->osWorkingResult =
        apszStrList[0];
    CPLFree(apszStrList[0]);

    return osWorkingResult;
}

int KEARasterAttributeTable::GetValueAsInt(int iRow, int iField) const
{
    // Get ValuesIO do do the work
    int nValue = 0;
    if ((const_cast<KEARasterAttributeTable *>(this))
            ->ValuesIO(GF_Read, iField, iRow, 1, &nValue) != CE_None)
    {
        return 0;
    }

    return nValue;
}

double KEARasterAttributeTable::GetValueAsDouble(int iRow, int iField) const
{
    // Get ValuesIO do do the work
    double dfValue = 0.0;
    if ((const_cast<KEARasterAttributeTable *>(this))
            ->ValuesIO(GF_Read, iField, iRow, 1, &dfValue) != CE_None)
    {
        return 0;
    }

    return dfValue;
}

void KEARasterAttributeTable::SetValue(int iRow, int iField,
                                       const char *pszValue)
{
    // Get ValuesIO do do the work
    ValuesIO(GF_Write, iField, iRow, 1, const_cast<char **>(&pszValue));
}

void KEARasterAttributeTable::SetValue(int iRow, int iField, double dfValue)
{
    // Get ValuesIO do do the work
    ValuesIO(GF_Write, iField, iRow, 1, &dfValue);
}

void KEARasterAttributeTable::SetValue(int iRow, int iField, int nValue)
{
    // Get ValuesIO do do the work
    ValuesIO(GF_Write, iField, iRow, 1, &nValue);
}

CPLErr KEARasterAttributeTable::ValuesIO(GDALRWFlag eRWFlag, int iField,
                                         int iStartRow, int iLength,
                                         double *pdfData)
{
    /*if( ( eRWFlag == GF_Write ) && ( this->eAccess == GA_ReadOnly ) )
    {
        CPLError( CE_Failure, CPLE_NoWriteAccess,
            "Dataset not open in update mode");
        return CE_Failure;
    }*/
    CPLMutexHolderD(&m_hMutex);

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

        return CE_Failure;
    }

    if (iStartRow < 0 || (iStartRow + iLength) > (int)m_poKEATable->getSize())
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "iStartRow (%d) + iLength(%d) out of range.", iStartRow,
                 iLength);

        return CE_Failure;
    }

    switch (m_aoFields[iField].dataType)
    {
        case kealib::kea_att_bool:
        case kealib::kea_att_int:
        {
            // allocate space for ints
            int *panColData = (int *)VSI_MALLOC2_VERBOSE(iLength, sizeof(int));
            if (panColData == nullptr)
            {
                return CE_Failure;
            }

            if (eRWFlag == GF_Write)
            {
                // copy the application supplied doubles to ints
                for (int i = 0; i < iLength; i++)
                    panColData[i] = static_cast<int>(pdfData[i]);
            }

            // do the ValuesIO as ints
            CPLErr eVal =
                ValuesIO(eRWFlag, iField, iStartRow, iLength, panColData);
            if (eVal != CE_None)
            {
                CPLFree(panColData);
                return eVal;
            }

            if (eRWFlag == GF_Read)
            {
                // copy them back to doubles
                for (int i = 0; i < iLength; i++)
                    pdfData[i] = panColData[i];
            }

            CPLFree(panColData);
        }
        break;
        case kealib::kea_att_float:
        {
            try
            {
                if (eRWFlag == GF_Read)
                    m_poKEATable->getFloatFields(
                        iStartRow, iLength, m_aoFields[iField].idx, pdfData);
                else
                    m_poKEATable->setFloatFields(
                        iStartRow, iLength, m_aoFields[iField].idx, pdfData);
            }
            catch (kealib::KEAException &e)
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Failed to read/write attribute table: %s", e.what());
                return CE_Failure;
            }
        }
        break;
        case kealib::kea_att_string:
        {
            // allocate space for string pointers
            char **papszColData =
                (char **)VSI_MALLOC2_VERBOSE(iLength, sizeof(char *));
            if (papszColData == nullptr)
            {
                return CE_Failure;
            }

            if (eRWFlag == GF_Write)
            {
                // copy the application supplied doubles to strings
                for (int i = 0; i < iLength; i++)
                {
                    osWorkingResult.Printf("%.16g", pdfData[i]);
                    papszColData[i] = CPLStrdup(osWorkingResult);
                }
            }

            // do the ValuesIO as strings
            CPLErr eVal =
                ValuesIO(eRWFlag, iField, iStartRow, iLength, papszColData);
            if (eVal != CE_None)
            {
                if (eRWFlag == GF_Write)
                {
                    for (int i = 0; i < iLength; i++)
                        CPLFree(papszColData[i]);
                }
                CPLFree(papszColData);
                return eVal;
            }

            if (eRWFlag == GF_Read)
            {
                // copy them back to doubles
                for (int i = 0; i < iLength; i++)
                    pdfData[i] = CPLAtof(papszColData[i]);
            }

            // either we allocated them for write, or they were allocated
            // by ValuesIO on read
            for (int i = 0; i < iLength; i++)
                CPLFree(papszColData[i]);

            CPLFree(papszColData);
        }
        break;
        default:
            break;
    }
    return CE_None;
}

CPLErr KEARasterAttributeTable::ValuesIO(GDALRWFlag eRWFlag, int iField,
                                         int iStartRow, int iLength,
                                         int *pnData)
{
    /*if( ( eRWFlag == GF_Write ) && ( this->eAccess == GA_ReadOnly ) )
    {
        CPLError( CE_Failure, CPLE_NoWriteAccess,
            "Dataset not open in update mode");
        return CE_Failure;
    }*/
    CPLMutexHolderD(&m_hMutex);

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

        return CE_Failure;
    }

    if (iStartRow < 0 || (iStartRow + iLength) > (int)m_poKEATable->getSize())
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "iStartRow (%d) + iLength(%d) out of range.", iStartRow,
                 iLength);

        return CE_Failure;
    }

    switch (m_aoFields[iField].dataType)
    {
        case kealib::kea_att_bool:
        {
            // need to convert to/from bools
            bool *panColData =
                (bool *)VSI_MALLOC2_VERBOSE(iLength, sizeof(bool));
            if (panColData == nullptr)
            {
                return CE_Failure;
            }

            if (eRWFlag == GF_Write)
            {
                // copy the application supplied ints to bools
                for (int i = 0; i < iLength; i++)
                {
                    panColData[i] = (pnData[i] != 0);
                }
            }

            try
            {
                if (eRWFlag == GF_Read)
                    m_poKEATable->getBoolFields(
                        iStartRow, iLength, m_aoFields[iField].idx, panColData);
                else
                    m_poKEATable->setBoolFields(
                        iStartRow, iLength, m_aoFields[iField].idx, panColData);
            }
            catch (kealib::KEAException &e)
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Failed to read/write attribute table: %s", e.what());
                return CE_Failure;
            }

            if (eRWFlag == GF_Read)
            {
                // copy them back to ints
                for (int i = 0; i < iLength; i++)
                    pnData[i] = panColData[i] ? 1 : 0;
            }
            CPLFree(panColData);
        }
        break;
        case kealib::kea_att_int:
        {
            // need to convert to/from int64_t
            int64_t *panColData =
                (int64_t *)VSI_MALLOC2_VERBOSE(iLength, sizeof(int64_t));
            if (panColData == nullptr)
            {
                return CE_Failure;
            }

            if (eRWFlag == GF_Write)
            {
                // copy the application supplied ints to int64t
                for (int i = 0; i < iLength; i++)
                    panColData[i] = pnData[i];
            }

            try
            {
                if (eRWFlag == GF_Read)
                    m_poKEATable->getIntFields(
                        iStartRow, iLength, m_aoFields[iField].idx, panColData);
                else
                    m_poKEATable->setIntFields(
                        iStartRow, iLength, m_aoFields[iField].idx, panColData);
            }
            catch (kealib::KEAException &e)
            {
                // fprintf(stderr,"Failed to read/write attribute table: %s %d
                // %d %ld\n", e.what(), iStartRow, iLength,
                // m_poKEATable->getSize() );
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Failed to read/write attribute table: %s", e.what());
                return CE_Failure;
            }

            if (eRWFlag == GF_Read)
            {
                // copy them back to ints
                for (int i = 0; i < iLength; i++)
                    pnData[i] = static_cast<int>(panColData[i]);
            }
            CPLFree(panColData);
        }
        break;
        case kealib::kea_att_float:
        {
            // allocate space for doubles
            double *padfColData =
                (double *)VSI_MALLOC2_VERBOSE(iLength, sizeof(double));
            if (padfColData == nullptr)
            {
                return CE_Failure;
            }

            if (eRWFlag == GF_Write)
            {
                // copy the application supplied ints to doubles
                for (int i = 0; i < iLength; i++)
                    padfColData[i] = pnData[i];
            }

            // do the ValuesIO as doubles
            CPLErr eVal =
                ValuesIO(eRWFlag, iField, iStartRow, iLength, padfColData);
            if (eVal != CE_None)
            {
                CPLFree(padfColData);
                return eVal;
            }

            if (eRWFlag == GF_Read)
            {
                // copy them back to ints
                for (int i = 0; i < iLength; i++)
                    pnData[i] = static_cast<int>(padfColData[i]);
            }

            CPLFree(padfColData);
        }
        break;
        case kealib::kea_att_string:
        {
            // allocate space for string pointers
            char **papszColData =
                (char **)VSI_MALLOC2_VERBOSE(iLength, sizeof(char *));
            if (papszColData == nullptr)
            {
                return CE_Failure;
            }

            if (eRWFlag == GF_Write)
            {
                // copy the application supplied ints to strings
                for (int i = 0; i < iLength; i++)
                {
                    osWorkingResult.Printf("%d", pnData[i]);
                    papszColData[i] = CPLStrdup(osWorkingResult);
                }
            }

            // do the ValuesIO as strings
            CPLErr eVal =
                ValuesIO(eRWFlag, iField, iStartRow, iLength, papszColData);
            if (eVal != CE_None)
            {
                if (eRWFlag == GF_Write)
                {
                    for (int i = 0; i < iLength; i++)
                        CPLFree(papszColData[i]);
                }
                CPLFree(papszColData);
                return eVal;
            }

            if (eRWFlag == GF_Read)
            {
                // copy them back to ints
                for (int i = 0; i < iLength; i++)
                    pnData[i] = atoi(papszColData[i]);
            }

            // either we allocated them for write, or they were allocated
            // by ValuesIO on read
            for (int i = 0; i < iLength; i++)
                CPLFree(papszColData[i]);

            CPLFree(papszColData);
        }
        break;
        default:
            break;
    }
    return CE_None;
}

CPLErr KEARasterAttributeTable::ValuesIO(GDALRWFlag eRWFlag, int iField,
                                         int iStartRow, int iLength,
                                         char **papszStrList)
{
    /*if( ( eRWFlag == GF_Write ) && ( this->eAccess == GA_ReadOnly ) )
    {
        CPLError( CE_Failure, CPLE_NoWriteAccess,
            "Dataset not open in update mode");
        return CE_Failure;
    }*/
    CPLMutexHolderD(&m_hMutex);

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

        return CE_Failure;
    }

    if (iStartRow < 0 || (iStartRow + iLength) > (int)m_poKEATable->getSize())
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "iStartRow (%d) + iLength(%d) out of range.", iStartRow,
                 iLength);

        return CE_Failure;
    }

    switch (m_aoFields[iField].dataType)
    {
        case kealib::kea_att_bool:
        case kealib::kea_att_int:
        {
            // allocate space for ints
            int *panColData = (int *)VSI_MALLOC2_VERBOSE(iLength, sizeof(int));
            if (panColData == nullptr)
            {
                return CE_Failure;
            }
            if (eRWFlag == GF_Write)
            {
                // convert user supplied strings to ints
                for (int i = 0; i < iLength; i++)
                    panColData[i] = atoi(papszStrList[i]);
            }

            // call values IO to read/write ints
            CPLErr eVal =
                ValuesIO(eRWFlag, iField, iStartRow, iLength, panColData);
            if (eVal != CE_None)
            {
                CPLFree(panColData);
                return eVal;
            }

            if (eRWFlag == GF_Read)
            {
                // convert ints back to strings
                for (int i = 0; i < iLength; i++)
                {
                    osWorkingResult.Printf("%d", panColData[i]);
                    papszStrList[i] = CPLStrdup(osWorkingResult);
                }
            }
            CPLFree(panColData);
        }
        break;
        case kealib::kea_att_float:
        {
            // allocate space for doubles
            double *padfColData =
                (double *)VSI_MALLOC2_VERBOSE(iLength, sizeof(double));
            if (padfColData == nullptr)
            {
                return CE_Failure;
            }

            if (eRWFlag == GF_Write)
            {
                // convert user supplied strings to doubles
                for (int i = 0; i < iLength; i++)
                    padfColData[i] = CPLAtof(papszStrList[i]);
            }

            // call value IO to read/write doubles
            CPLErr eVal =
                ValuesIO(eRWFlag, iField, iStartRow, iLength, padfColData);
            if (eVal != CE_None)
            {
                CPLFree(padfColData);
                return eVal;
            }

            if (eRWFlag == GF_Read)
            {
                // convert doubles back to strings
                for (int i = 0; i < iLength; i++)
                {
                    osWorkingResult.Printf("%.16g", padfColData[i]);
                    papszStrList[i] = CPLStrdup(osWorkingResult);
                }
            }
            CPLFree(padfColData);
        }
        break;
        case kealib::kea_att_string:
        {
            try
            {
                if (eRWFlag == GF_Read)
                {
                    std::vector<std::string> aStrings;
                    m_poKEATable->getStringFields(
                        iStartRow, iLength, m_aoFields[iField].idx, &aStrings);
                    for (std::vector<std::string>::size_type i = 0;
                         i < aStrings.size(); i++)
                    {
                        // Copy using CPLStrdup so user can call CPLFree
                        papszStrList[i] = CPLStrdup(aStrings[i].c_str());
                    }
                }
                else
                {
                    // need to convert to a vector first
                    std::vector<std::string> aStrings;
                    for (int i = 0; i < iLength; i++)
                    {
                        aStrings.push_back(papszStrList[i]);
                    }
                    m_poKEATable->setStringFields(
                        iStartRow, iLength, m_aoFields[iField].idx, &aStrings);
                }
            }
            catch (kealib::KEAException &e)
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Failed to read/write attribute table: %s", e.what());
                return CE_Failure;
            }
        }
        break;
        default:
            break;
    }
    return CE_None;
}

int KEARasterAttributeTable::ChangesAreWrittenToFile()
{
    return TRUE;
}

void KEARasterAttributeTable::SetRowCount(int iCount)
{
    /*if( this->eAccess == GA_ReadOnly )
    {
        CPLError( CE_Failure, CPLE_NoWriteAccess,
            "Dataset not open in update mode");
        return;
    }*/

    if (iCount > (int)m_poKEATable->getSize())
    {
        m_poKEATable->addRows(iCount - m_poKEATable->getSize());
    }
    // can't shrink
}

CPLErr KEARasterAttributeTable::CreateColumn(const char *pszFieldName,
                                             GDALRATFieldType eFieldType,
                                             GDALRATFieldUsage eFieldUsage)
{
    /*if( this->eAccess == GA_ReadOnly )
    {
        CPLError( CE_Failure, CPLE_NoWriteAccess,
            "Dataset not open in update mode");
        return CE_Failure;
    }*/
    CPLMutexHolderD(&m_hMutex);

    std::string strUsage = "Generic";
    switch (eFieldUsage)
    {
        case GFU_PixelCount:
            strUsage = "PixelCount";
            eFieldType = GFT_Real;
            break;
        case GFU_Name:
            strUsage = "Name";
            eFieldType = GFT_String;
            break;
        case GFU_Red:
            strUsage = "Red";
            eFieldType = GFT_Integer;
            break;
        case GFU_Green:
            strUsage = "Green";
            eFieldType = GFT_Integer;
            break;
        case GFU_Blue:
            strUsage = "Blue";
            eFieldType = GFT_Integer;
            break;
        case GFU_Alpha:
            strUsage = "Alpha";
            eFieldType = GFT_Integer;
            break;
        default:
            // leave as "Generic"
            break;
    }

    try
    {
        if (eFieldType == GFT_Integer)
        {
            m_poKEATable->addAttIntField(pszFieldName, 0, strUsage);
        }
        else if (eFieldType == GFT_Real)
        {
            m_poKEATable->addAttFloatField(pszFieldName, 0, strUsage);
        }
        else
        {
            m_poKEATable->addAttStringField(pszFieldName, "", strUsage);
        }

        // assume we can just grab this now
        kealib::KEAATTField sKEAField = m_poKEATable->getField(pszFieldName);
        m_aoFields.push_back(sKEAField);
    }
    catch (kealib::KEAException &e)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Failed to add column: %s",
                 e.what());
        return CE_Failure;
    }

    return CE_None;
}

CPLErr KEARasterAttributeTable::SetLinearBinning(double ldfRow0Min,
                                                 double ldfBinSize)
{
    size_t nRows = m_poKEATable->getSize();

    osWorkingResult.Printf("%.16g", ldfRow0Min);
    m_poBand->SetMetadataItem("STATISTICS_HISTOMIN", osWorkingResult);
    osWorkingResult.Printf("%.16g", (nRows - 1) * ldfBinSize + ldfRow0Min);
    m_poBand->SetMetadataItem("STATISTICS_HISTOMAX", osWorkingResult);

    // STATISTICS_HISTONUMBINS now returned by metadata

    return CE_None;
}

int KEARasterAttributeTable::GetLinearBinning(double *pdfRow0Min,
                                              double *pdfBinSize) const
{
    const char *pszMin = m_poBand->GetMetadataItem("STATISTICS_HISTOMIN");
    const char *pszMax = m_poBand->GetMetadataItem("STATISTICS_HISTOMAX");
    if ((pszMin == nullptr) || (pszMax == nullptr))
    {
        return FALSE;
    }
    *pdfRow0Min = atof(pszMin);
    *pdfBinSize = (atof(pszMax) - *pdfRow0Min) / (m_poKEATable->getSize() - 1);

    return TRUE;
}

CPLXMLNode *KEARasterAttributeTable::Serialize() const
{
    if ((GetRowCount() * GetColumnCount()) > RAT_MAX_ELEM_FOR_CLONE)
        return nullptr;

    return GDALRasterAttributeTable::Serialize();
}

GDALRATTableType KEARasterAttributeTable::GetTableType() const
{
    kealib::KEALayerType keaType = m_poBand->getLayerType();
    if (keaType == kealib::kea_continuous)
    {
        return GRTT_ATHEMATIC;
    }
    else
    {
        return GRTT_THEMATIC;
    }
}

CPLErr
KEARasterAttributeTable::SetTableType(const GDALRATTableType eInTableType)
{
    kealib::KEALayerType keaType = (eInTableType == GRTT_ATHEMATIC)
                                       ? kealib::kea_continuous
                                       : kealib::kea_thematic;
    try
    {
        m_poBand->setLayerType(keaType);
        return CE_None;
    }
    catch (const kealib::KEAIOException &)
    {
        return CE_Failure;
    }
}
