/******************************************************************************
 *
 * Project:  Erdas Imagine (.img) Translator
 * Purpose:  Implementation of the HFAField class for managing information
 *           about one field in a HFA dictionary type.  Managed by HFAType.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 1999, Intergraph Corporation
 * Copyright (c) 2009-2011, 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 "hfa_p.h"

#include <cerrno>
#include <climits>
#include <cstddef>
#include <cstdio>
#include <cstring>
#if HAVE_FCNTL_H
#  include <fcntl.h>
#endif
#include <algorithm>
#include <limits>
#include <vector>

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

CPL_CVSID("$Id: hfafield.cpp 38009 2017-04-14 15:07:49Z rouault $");

static const int MAX_ENTRY_REPORT = 16;

/************************************************************************/
/* ==================================================================== */
/*                              HFAField                                */
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                              HFAField()                              */
/************************************************************************/

HFAField::HFAField() :
    nBytes(0),
    nItemCount(0),
    chPointer('\0'),
    chItemType('\0'),
    pszItemObjectType(NULL),
    poItemObjectType(NULL),
    papszEnumNames(NULL),
    pszFieldName(NULL)
{
    memset(szNumberString, 0, sizeof(szNumberString));
}

/************************************************************************/
/*                             ~HFAField()                              */
/************************************************************************/

HFAField::~HFAField()

{
    CPLFree(pszItemObjectType);
    CSLDestroy(papszEnumNames);
    CPLFree(pszFieldName);
}

/************************************************************************/
/*                             Initialize()                             */
/************************************************************************/

const char *HFAField::Initialize( const char *pszInput )

{
    // Read the number.
    nItemCount = atoi(pszInput);
    if( nItemCount < 0 )
        return NULL;

    while( *pszInput != '\0' && *pszInput != ':' )
        pszInput++;

    if( *pszInput == '\0' )
        return NULL;

    pszInput++;

    // Is this a pointer?
    if( *pszInput == 'p' || *pszInput == '*' )
        chPointer = *(pszInput++);

    // Get the general type.
    if( *pszInput == '\0' )
        return NULL;

    chItemType = *(pszInput++);

    if( strchr("124cCesStlLfdmMbox", chItemType) == NULL )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Unrecognized item type: %c", chItemType);
        return NULL;
    }

    // If this is an object, we extract the type of the object.
    int i = 0;  // TODO: Describe why i needs to span chItemType blocks.

    if( chItemType == 'o' )
    {
        for( i = 0; pszInput[i] != '\0' && pszInput[i] != ','; i++ ) {}
        if( pszInput[i] == '\0' )
            return NULL;

        pszItemObjectType = static_cast<char *>(CPLMalloc(i + 1));
        strncpy(pszItemObjectType, pszInput, i);
        pszItemObjectType[i] = '\0';

        pszInput += i + 1;
    }

    // If this is an inline object, we need to skip past the
    // definition, and then extract the object class name.
    //
    // We ignore the actual definition, so if the object type isn't
    // already defined, things will not work properly.  See the
    // file lceugr250_00_pct.aux for an example of inline defs.
    if( chItemType == 'x' && *pszInput == '{' )
    {
        int nBraceDepth = 1;
        pszInput++;

        // Skip past the definition.
        while( nBraceDepth > 0 && *pszInput != '\0' )
        {
            if( *pszInput == '{' )
                nBraceDepth++;
            else if( *pszInput == '}' )
                nBraceDepth--;

            pszInput++;
        }
        if( *pszInput == '\0' )
            return NULL;

        chItemType = 'o';

        // Find the comma terminating the type name.
        for( i = 0; pszInput[i] != '\0' && pszInput[i] != ','; i++ ) {}
        if( pszInput[i] == '\0' )
            return NULL;

        pszItemObjectType = static_cast<char *>(CPLMalloc(i + 1));
        strncpy(pszItemObjectType, pszInput, i);
        pszItemObjectType[i] = '\0';

        pszInput += i + 1;
    }

    // If this is an enumeration we have to extract all the
    // enumeration values.
    if( chItemType == 'e' )
    {
        const int nEnumCount = atoi(pszInput);

        if( nEnumCount < 0 || nEnumCount > 100000 )
            return NULL;

        pszInput = strchr(pszInput, ':');
        if( pszInput == NULL )
            return NULL;

        pszInput++;

        papszEnumNames =
            static_cast<char **>(VSICalloc(sizeof(char *), nEnumCount + 1));
        if( papszEnumNames == NULL )
            return NULL;

        for( int iEnum = 0; iEnum < nEnumCount; iEnum++ )
        {
            for( i = 0; pszInput[i] != '\0' && pszInput[i] != ','; i++ ) {}

            if( pszInput[i] != ',' )
                return NULL;

            char *pszToken = static_cast<char *>(CPLMalloc(i + 1));
            strncpy(pszToken, pszInput, i);
            pszToken[i] = '\0';

            papszEnumNames[iEnum] = pszToken;

            pszInput += i + 1;
        }
    }

    // Extract the field name.
    for( i = 0; pszInput[i] != '\0' && pszInput[i] != ','; i++ ) {}
    if( pszInput[i] == '\0' )
        return NULL;

    pszFieldName = static_cast<char *>(CPLMalloc(i + 1));
    strncpy(pszFieldName, pszInput, i);
    pszFieldName[i] = '\0';

    pszInput += i + 1;

    return pszInput;
}

/************************************************************************/
/*                            CompleteDefn()                            */
/*                                                                      */
/*      Establish size, and pointers to component types.                */
/************************************************************************/

void HFAField::CompleteDefn( HFADictionary *poDict )

{
    // Get a reference to the type object if we have a type name
    // for this field (not a built in).
    if( pszItemObjectType != NULL )
        poItemObjectType = poDict->FindType(pszItemObjectType);

    // Figure out the size.
    if( chPointer == 'p' )
    {
        nBytes = -1;  // We can't know the instance size.
    }
    else if( poItemObjectType != NULL )
    {
        poItemObjectType->CompleteDefn(poDict);
        if( poItemObjectType->nBytes == -1 )
            nBytes = -1;
        else if( poItemObjectType->nBytes != 0 &&
                 nItemCount > INT_MAX / poItemObjectType->nBytes )
            nBytes = -1;
        else
            nBytes = poItemObjectType->nBytes * nItemCount;

        // TODO(schwehr): What does the 8 represent?
        if( chPointer == '*' && nBytes != -1 )
        {
            if( nBytes > INT_MAX - 8 )
                nBytes = -1;
            else
                nBytes += 8;  // Count, and offset.
        }
    }
    else
    {
        const int nItemSize = poDict->GetItemSize(chItemType);
        if( nItemSize != 0 && nItemCount > INT_MAX / nItemSize )
            nBytes = -1;
        else
            nBytes = nItemSize * nItemCount;
    }
}

/************************************************************************/
/*                                Dump()                                */
/************************************************************************/

void HFAField::Dump( FILE *fp )

{
    const char *pszTypeName;

    switch( chItemType )
    {
    case '1':
        pszTypeName = "U1";
        break;

    case '2':
        pszTypeName = "U2";
        break;

    case '4':
        pszTypeName = "U4";
        break;

    case 'c':
        pszTypeName = "UCHAR";
        break;

    case 'C':
        pszTypeName = "CHAR";
        break;

    case 'e':
        pszTypeName = "ENUM";
        break;

    case 's':
        pszTypeName = "USHORT";
        break;

    case 'S':
        pszTypeName = "SHORT";
        break;

    case 't':
        pszTypeName = "TIME";
        break;

    case 'l':
        pszTypeName = "ULONG";
        break;

    case 'L':
        pszTypeName = "LONG";
        break;

    case 'f':
        pszTypeName = "FLOAT";
        break;

    case 'd':
        pszTypeName = "DOUBLE";
        break;

    case 'm':
        pszTypeName = "COMPLEX";
        break;

    case 'M':
        pszTypeName = "DCOMPLEX";
        break;

    case 'b':
        pszTypeName = "BASEDATA";
        break;

    case 'o':
        pszTypeName = pszItemObjectType;
        break;

    case 'x':
        pszTypeName = "InlineType";
        break;

    default:
        CPLAssert(false);
        pszTypeName = "Unknown";
    }

    CPL_IGNORE_RET_VAL(
        VSIFPrintf(fp, "    %-19s %c %s[%d];\n",
                   pszTypeName,
                   chPointer ? chPointer : ' ',
                   pszFieldName, nItemCount));

    if( papszEnumNames != NULL )
    {
        for( int i = 0; papszEnumNames[i] != NULL; i++ )
        {
            CPL_IGNORE_RET_VAL(
                VSIFPrintf(fp, "        %s=%d\n", papszEnumNames[i], i));
        }
    }
}

/************************************************************************/
/*                            SetInstValue()                            */
/************************************************************************/

CPLErr
HFAField::SetInstValue( const char *pszField, int nIndexValue,
                        GByte *pabyData, GUInt32 nDataOffset, int nDataSize,
                        char chReqType, void *pValue )

{
    // If this field contains a pointer, then we will adjust the
    // data offset relative to it.
    if( chPointer != '\0' )
    {
        GUInt32 nCount = 0;

        // The count returned for BASEDATA's are the contents,
        // but here we really want to mark it as one BASEDATA instance
        // (see #2144).
        if( chItemType == 'b' )
        {
            nCount = 1;
        }
        // Set the size from string length.
        else if( chReqType == 's' && (chItemType == 'c' || chItemType == 'C'))
        {
            if( pValue != NULL )
                nCount = static_cast<GUInt32>(strlen((char *)pValue) + 1);
        }
        // Set size based on index. Assumes in-order setting of array.
        else
        {
            nCount = nIndexValue + 1;
        }

        // TODO(schwehr): What does the 8 represent?
        if( static_cast<int>(nCount) + 8 > nDataSize )
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Attempt to extend field %s in node past end of data, "
                     "not currently supported.",
                     pszField);
            return CE_Failure;
        }

        // We will update the object count iff we are writing beyond the end.
        GUInt32 nOffset = 0;
        memcpy(&nOffset, pabyData, 4);
        HFAStandard(4, &nOffset);
        if( nOffset < nCount )
        {
            nOffset = nCount;
            HFAStandard(4, &nOffset);
            memcpy(pabyData, &nOffset, 4);
        }

        if( pValue == NULL )
            nOffset = 0;
        else
            nOffset = nDataOffset + 8;
        HFAStandard(4, &nOffset);
        memcpy(pabyData + 4, &nOffset, 4);

        pabyData += 8;

        nDataOffset += 8;
        nDataSize -= 8;
    }

    // Pointers to char or uchar arrays requested as strings are
    // handled as a special case.
    if( (chItemType == 'c' || chItemType == 'C') && chReqType == 's' )
    {
        int nBytesToCopy = 0;

        if( nBytes == -1 )
        {
            if( pValue != NULL )
                nBytesToCopy = static_cast<int>(strlen((char *)pValue) + 1);
        }
        else
        {
            nBytesToCopy = nBytes;
        }

        if( nBytesToCopy > nDataSize )
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Attempt to extend field %s in node past end of data "
                     "not currently supported.",
                     pszField);
            return CE_Failure;
        }

        memset(pabyData, 0, nBytesToCopy);

        if( pValue != NULL )
            strncpy((char *)pabyData, (char *)pValue, nBytesToCopy);

        return CE_None;
    }

    // Translate the passed type into different representations.
    int nIntValue = 0;
    double dfDoubleValue = 0.0;

    if( chReqType == 's' )
    {
        CPLAssert(pValue != NULL);
        nIntValue = atoi((char *)pValue);
        dfDoubleValue = CPLAtof((char *)pValue);
    }
    else if( chReqType == 'd' )
    {
        CPLAssert(pValue != NULL);
        dfDoubleValue = *((double *)pValue);
        if( dfDoubleValue > INT_MAX )
            nIntValue = INT_MAX;
        else if( dfDoubleValue < INT_MIN )
            nIntValue = INT_MIN;
        else
            nIntValue = static_cast<int>(dfDoubleValue);
    }
    else if( chReqType == 'i' )
    {
        CPLAssert(pValue != NULL);
        nIntValue = *((int *)pValue);
        dfDoubleValue = nIntValue;
    }
    else if( chReqType == 'p' )
    {
        CPLError(
            CE_Failure, CPLE_NotSupported,
            "HFAField::SetInstValue() not supported yet for pointer values.");

        return CE_Failure;
    }
    else
    {
        CPLAssert(false);
        return CE_Failure;
    }

    // Handle by type.
    switch( chItemType )
    {
      case 'c':
      case 'C':
        if( nIndexValue + 1 > nDataSize )
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Attempt to extend field %s in node past end of data, "
                     "not currently supported.",
                     pszField);
              return CE_Failure;
        }

        if( chReqType == 's' )
        {
            CPLAssert(pValue != NULL);
            pabyData[nIndexValue] = ((char *)pValue)[0];
        }
        else
        {
            pabyData[nIndexValue] = static_cast<char>(nIntValue);
        }
        break;

      case 'e':
      case 's':
      {
          if( chItemType == 'e' && chReqType == 's' )
          {
              CPLAssert(pValue != NULL);
              nIntValue = CSLFindString(papszEnumNames, (char *) pValue);
              if( nIntValue == -1 )
              {
                  CPLError(CE_Failure, CPLE_AppDefined,
                            "Attempt to set enumerated field with unknown"
                            " value `%s'.",
                            (char *)pValue);
                  return CE_Failure;
              }
          }

          if( nIndexValue*2 + 2 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined,
                       "Attempt to extend field %s in node past end of data, "
                       "not currently supported.",
                       pszField);
              return CE_Failure;
          }

          // TODO(schwehr): Warn on clamping.
          unsigned short nNumber = static_cast<unsigned short>(nIntValue);
          // TODO(schwehr): What is this 2?
          HFAStandard(2, &nNumber);
          memcpy(pabyData + nIndexValue * 2, &nNumber, 2);
      }
      break;

      case 'S':
      {
          if( nIndexValue * 2 + 2 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined,
                       "Attempt to extend field %s in node past end of data, "
                       "not currently supported.",
                       pszField);
              return CE_Failure;
          }

          // TODO(schwehr): Warn on clamping.
          short nNumber = static_cast<short>(nIntValue);
          // TODO(schwehr): What is this 2?
          HFAStandard(2, &nNumber);
          memcpy(pabyData + nIndexValue * 2, &nNumber, 2);
      }
      break;

      case 't':
      case 'l':
      {
          if( nIndexValue * 4 + 4 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined,
                       "Attempt to extend field %s in node past end of data, "
                       "not currently supported.",
                       pszField);
              return CE_Failure;
          }

          GUInt32 nNumber = nIntValue;
          // TODO(schwehr): What is this 4?
          HFAStandard(4, &nNumber);
          memcpy(pabyData + nIndexValue * 4, &nNumber, 4);
      }
      break;

      case 'L':
      {
          if( nIndexValue * 4 + 4 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined,
                       "Attempt to extend field %s in node past end of data, "
                       "not currently supported.",
                       pszField);
              return CE_Failure;
          }

          GInt32 nNumber = nIntValue;
          HFAStandard(4, &nNumber);
          memcpy(pabyData + nIndexValue * 4, &nNumber, 4);
      }
      break;

      case 'f':
      {
          if( nIndexValue * 4 + 4 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined,
                       "Attempt to extend field %s in node past end of data, "
                       "not currently supported.",
                       pszField);
              return CE_Failure;
          }

          // TODO(schwehr): Warn on clamping.
          float fNumber = static_cast<float>(dfDoubleValue);
          // TODO(schwehr): 4 == sizeof(float)?
          HFAStandard(4, &fNumber);
          memcpy(pabyData + nIndexValue * 4, &fNumber, 4);
      }
      break;

      case 'd':
      {
          if( nIndexValue * 8 + 8 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined,
                       "Attempt to extend field %s in node past end of data, "
                       "not currently supported.",
                       pszField);
              return CE_Failure;
          }

          double dfNumber = dfDoubleValue;
          HFAStandard(8, &dfNumber);
          memcpy(pabyData + nIndexValue * 8, &dfNumber, 8);
      }
      break;

    case 'b':
    {
        // Extract existing rows, columns, and datatype.
        GInt32 nRows = 1;  // TODO(schwehr): Why init to 1 instead of 0?
        memcpy(&nRows, pabyData, 4);
        HFAStandard(4, &nRows);

        GInt32 nColumns = 1;  // TODO(schwehr): Why init to 1 instead of 0?
        memcpy(&nColumns, pabyData + 4, 4);
        HFAStandard(4, &nColumns);

        GInt16 nBaseItemType = 0;
        memcpy(&nBaseItemType, pabyData + 8, 2);
        HFAStandard(2, &nBaseItemType);

        // Are we using special index values to update the rows, columns
        // or type?

        if( nIndexValue == -3 )
            nBaseItemType = static_cast<GInt16>(nIntValue);
        else if( nIndexValue == -2 )
            nColumns = nIntValue;
        else if( nIndexValue == -1 )
            nRows = nIntValue;

        if( nIndexValue < -3 || nIndexValue >= nRows * nColumns )
            return CE_Failure;

        // Write back the rows, columns and basedatatype.
        HFAStandard(4, &nRows);
        memcpy(pabyData, &nRows, 4);
        HFAStandard(4, &nColumns);
        memcpy(pabyData + 4, &nColumns, 4);
        HFAStandard(2, &nBaseItemType);
        memcpy(pabyData + 8, &nBaseItemType, 2);
        HFAStandard(2, &nBaseItemType);  // Swap back for our use.

        if( nBaseItemType < EPT_MIN || nBaseItemType > EPT_MAX )
            return CE_Failure;
        const EPTType eBaseItemType = static_cast<EPTType>(nBaseItemType);

        // We ignore the 2 byte objecttype value.

        nDataSize -= 12;

        if( nIndexValue >= 0 )
        {
            if( (nIndexValue + 1) * (HFAGetDataTypeBits(eBaseItemType) / 8)
                > nDataSize )
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Attempt to extend field %s in node past end of "
                         "data, not currently supported.",
                         pszField);
                return CE_Failure;
            }

            if( eBaseItemType == EPT_f64 )
            {
                double dfNumber = dfDoubleValue;

                HFAStandard(8, &dfNumber);
                memcpy(pabyData + 12 + nIndexValue * 8, &dfNumber, 8);
            }
            else if( eBaseItemType == EPT_u8 )
            {
                // TODO(schwehr): Warn on clamping.
                unsigned char nNumber =
                    static_cast<unsigned char>(dfDoubleValue);
                memcpy(pabyData + 12 + nIndexValue, &nNumber, 1);
            }
            else
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Setting basedata field %s with type %s "
                         "not currently supported.",
                         pszField, HFAGetDataTypeName(eBaseItemType));
                return CE_Failure;
            }
        }
    }
    break;

      case 'o':
        if( poItemObjectType != NULL )
        {
            int nExtraOffset = 0;

            if( poItemObjectType->nBytes > 0 )
            {
                if( nIndexValue != 0 &&
                    poItemObjectType->nBytes > INT_MAX / nIndexValue )
                {
                    return CE_Failure;
                }
                nExtraOffset = poItemObjectType->nBytes * nIndexValue;
            }
            else
            {
                for( int iIndexCounter = 0;
                     iIndexCounter < nIndexValue && nExtraOffset < nDataSize;
                     iIndexCounter++ )
                {
                    const int nInc =
                        poItemObjectType->
                            GetInstBytes(pabyData + nExtraOffset,
                                         nDataSize - nExtraOffset);
                    if( nInc < 0 || nExtraOffset > INT_MAX - nInc )
                    {
                        CPLError(CE_Failure, CPLE_AppDefined,
                                 "Invalid return value");
                        return CE_Failure;
                    }

                    nExtraOffset += nInc;
                }
            }

            if( nExtraOffset >= nDataSize )
                return CE_Failure;

            if( pszField != NULL && strlen(pszField) > 0 )
            {
                return poItemObjectType->
                    SetInstValue(pszField, pabyData + nExtraOffset,
                                 nDataOffset + nExtraOffset,
                                 nDataSize - nExtraOffset,
                                 chReqType, pValue);
            }
            else
            {
                CPLAssert(false);
                return CE_Failure;
            }
        }
        break;

      default:
        CPLAssert(false);
        return CE_Failure;
        break;
    }

    return CE_None;
}

/************************************************************************/
/*                          ExtractInstValue()                          */
/*                                                                      */
/*      Extract the value of an instance of a field.                    */
/*                                                                      */
/*      pszField should be NULL if this field is not a                  */
/*      substructure.                                                   */
/************************************************************************/

bool
HFAField::ExtractInstValue( const char *pszField, int nIndexValue,
                            GByte *pabyData, GUInt32 nDataOffset, int nDataSize,
                            char chReqType, void *pReqReturn,
                            int *pnRemainingDataSize )

{
    const int nInstItemCount = GetInstCount(pabyData, nDataSize);

    if( pnRemainingDataSize )
        *pnRemainingDataSize = -1;

    // Check the index value is valid.
    // Eventually this will have to account for variable fields.
    if( nIndexValue < 0 || nIndexValue >= nInstItemCount )
    {
        if( chItemType == 'b' && nIndexValue >= -3 && nIndexValue < 0 )
            /* ok - special index values */;
        else
            return false;
    }

    // If this field contains a pointer, then we will adjust the
    // data offset relative to it.
    if( chPointer != '\0' )
    {
        if( nDataSize < 8 )
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
            return false;
        }

        GUInt32 nOffset = 0;
        memcpy(&nOffset, pabyData + 4, 4);
        HFAStandard(4, &nOffset);

#if DEBUG_VERBOSE
        if( nOffset != static_cast<GUInt32>(nDataOffset + 8) )
        {
            // TODO(schwehr): Debug why this is happening.
            CPLError(CE_Warning, CPLE_AppDefined,
                     "ExtractInstValue: "
                     "%s.%s points at %d, not %d as expected",
                     pszFieldName, pszField ? pszField : "",
                     nOffset, nDataOffset + 8);
        }
#endif

        pabyData += 8;
        nDataOffset += 8;
        nDataSize -= 8;
    }

    // Pointers to char or uchar arrays requested as strings are
    // handled as a special case.
    if( (chItemType == 'c' || chItemType == 'C') && chReqType == 's' )
    {
        *((GByte **)pReqReturn) = pabyData;
        if( pnRemainingDataSize )
            *pnRemainingDataSize = nDataSize;
        return pabyData != NULL;
    }

    // Handle by type.
    char *pszStringRet = NULL;
    int nIntRet = 0;
    double dfDoubleRet = 0.0;
    GByte *pabyRawData = NULL;

    switch( chItemType )
    {
      case 'c':
      case 'C':
        if( nIndexValue >= nDataSize )
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
            return false;
        }
        nIntRet = pabyData[nIndexValue];
        dfDoubleRet = nIntRet;
        break;

      case 'e':
      case 's':
      {
          if( nIndexValue * 2 + 2 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
              return false;
          }
          unsigned short nNumber = 0;
          memcpy(&nNumber, pabyData + nIndexValue * 2, 2);
          HFAStandard(2, &nNumber);
          nIntRet = nNumber;
          dfDoubleRet = nIntRet;

          if( chItemType == 'e'
              && nIntRet >= 0 && nIntRet < CSLCount(papszEnumNames) )
          {
              pszStringRet = papszEnumNames[nIntRet];
          }
      }
      break;

      case 'S':
      {
          if( nIndexValue * 2 + 2 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
              return false;
          }
          short nNumber = 0;
          memcpy(&nNumber, pabyData + nIndexValue * 2, 2);
          HFAStandard(2, &nNumber);
          nIntRet = nNumber;
          dfDoubleRet = nIntRet;
      }
      break;

      case 't':
      case 'l':
      {
          if( nIndexValue * 4 + 4 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
              return false;
          }
          GUInt32 nNumber = 0;
          memcpy(&nNumber, pabyData + nIndexValue * 4, 4);
          HFAStandard(4, &nNumber);
          nIntRet = nNumber;
          dfDoubleRet = nIntRet;
      }
      break;

      case 'L':
      {
          if( nIndexValue * 4 + 4 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
              return false;
          }
          GInt32 nNumber = 0;
          // TODO(schwehr): What is 4?
          memcpy(&nNumber, pabyData + nIndexValue * 4, 4);
          HFAStandard(4, &nNumber);
          nIntRet = nNumber;
          dfDoubleRet = nIntRet;
      }
      break;

      case 'f':
      {
          if( nIndexValue * 4 + 4 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
              return false;
          }
          float fNumber = 0.0f;
          // TODO(schwehr): What is 4?
          memcpy(&fNumber, pabyData + nIndexValue * 4, 4);
          HFAStandard(4, &fNumber);
          dfDoubleRet = fNumber;
          if( dfDoubleRet > static_cast<double>(
                                            std::numeric_limits<int>::max()) ||
              dfDoubleRet < static_cast<double>(
                                            std::numeric_limits<int>::min()) ||
              CPLIsNan(fNumber) )
          {
              CPLError(CE_Failure, CPLE_AppDefined,
                       "Too large for int: %f", fNumber);
              return false;
          }
          nIntRet = static_cast<int>(fNumber);
      }
      break;

      case 'd':
      {
          if( nIndexValue * 8 + 8 > nDataSize )
          {
              CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
              return false;
          }
          double dfNumber = 0;
          memcpy(&dfNumber, pabyData + nIndexValue * 8, 8);
          HFAStandard(8, &dfNumber);
          dfDoubleRet = dfNumber;
          if( dfNumber > std::numeric_limits<int>::max() ||
              dfNumber < std::numeric_limits<int>::min() ||
              CPLIsNan(dfNumber) )
          {
              CPLError(CE_Failure, CPLE_AppDefined,
                       "Too large for int: %f", dfNumber);
              return false;
          }
          nIntRet = static_cast<int>(dfNumber);
      }
      break;

      case 'b':
      {
          if( nDataSize < 12 )
              return false;

          GInt32 nRows = 0;
          memcpy(&nRows, pabyData, 4);
          HFAStandard(4, &nRows);

          GInt32 nColumns = 0;
          memcpy(&nColumns, pabyData + 4, 4);
          HFAStandard(4, &nColumns);

          GInt16 nBaseItemType = 0;
          memcpy(&nBaseItemType, pabyData + 8, 2);
          HFAStandard(2, &nBaseItemType);
          // We ignore the 2 byte objecttype value.

          if( nIndexValue < -3 ||
              nRows <= 0 ||
              nColumns <= 0 ||
              nRows > INT_MAX / nColumns ||
              nIndexValue >= nRows * nColumns )
              return false;

          pabyData += 12;
          nDataSize -= 12;

          if( nIndexValue == -3 )
          {
              dfDoubleRet = nBaseItemType;
              nIntRet = nBaseItemType;
          }
          else if( nIndexValue == -2 )
          {
              dfDoubleRet = nColumns;
              nIntRet = nColumns;
          }
          else if( nIndexValue == -1 )
          {
              dfDoubleRet = nRows;
              nIntRet = nRows;
          }
          else if( nBaseItemType == EPT_u1 )
          {
              // TODO(schwehr): What are these constants like 8 and 0x7?
              if( nIndexValue * 8 >= nDataSize )
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }

              if( pabyData[nIndexValue >> 3] & (1 << (nIndexValue & 0x7)) )
              {
                  dfDoubleRet = 1;
                  nIntRet = 1;
              }
              else
              {
                  dfDoubleRet = 0.0;
                  nIntRet = 0;
              }
          }
          else if( nBaseItemType == EPT_u2 )
          {
              const int nBitOffset = nIndexValue & 0x3;
              const int nByteOffset = nIndexValue >> 2;

              if( nByteOffset >= nDataSize )
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }

              const int nMask = 0x3;
              nIntRet = (pabyData[nByteOffset] >> nBitOffset) & nMask;
              dfDoubleRet = nIntRet;
          }
          else if( nBaseItemType == EPT_u4 )
          {
              const int nBitOffset = nIndexValue & 0x7;
              const int nByteOffset = nIndexValue >> 3;

              if( nByteOffset >= nDataSize )
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }

              const int nMask = 0x7;
              nIntRet = (pabyData[nByteOffset] >> nBitOffset) & nMask;
              dfDoubleRet = nIntRet;
          }
          else if( nBaseItemType == EPT_u8 )
          {
              if( nIndexValue >= nDataSize )
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }
              dfDoubleRet = pabyData[nIndexValue];
              nIntRet = pabyData[nIndexValue];
          }
          else if( nBaseItemType == EPT_s8 )
          {
              if( nIndexValue >= nDataSize )
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }
              dfDoubleRet = ((signed char *)pabyData)[nIndexValue];
              nIntRet = ((signed char *)pabyData)[nIndexValue];
          }
          else if( nBaseItemType == EPT_s16 )
          {
              if( nIndexValue * 2 + 2 > nDataSize )
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }
              GInt16 nValue = 0;
              memcpy(&nValue, pabyData + 2 * nIndexValue, 2);
              HFAStandard(2, &nValue);

              dfDoubleRet = nValue;
              nIntRet = nValue;
          }
          else if( nBaseItemType == EPT_u16 )
          {
              if( nIndexValue * 2 + 2 > nDataSize )
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }
              GUInt16 nValue = 0;
              memcpy(&nValue, pabyData + 2 * nIndexValue, 2);
              HFAStandard(2, &nValue);

              dfDoubleRet = nValue;
              nIntRet = nValue;
          }
          else if( nBaseItemType == EPT_s32 )
          {
              if( nIndexValue * 4 + 4 > nDataSize)
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }
              GInt32 nValue = 0;
              memcpy(&nValue, pabyData + 4 * nIndexValue, 4);
              HFAStandard(4, &nValue);

              dfDoubleRet = nValue;
              nIntRet = nValue;
          }
          else if( nBaseItemType == EPT_u32 )
          {
              if( nIndexValue * 4 + 4 > nDataSize )
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }
              GUInt32 nValue = 0;
              memcpy(&nValue, pabyData + 4 * nIndexValue, 4);
              HFAStandard(4, &nValue);

              dfDoubleRet = nValue;
              nIntRet = nValue;
          }
          else if( nBaseItemType == EPT_f32 )
          {
              if( nIndexValue * 4 + 4 > nDataSize )
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }
              float fValue = 0.0f;
              memcpy(&fValue, pabyData + 4 * nIndexValue, 4);
              HFAStandard(4, &fValue);

              dfDoubleRet = fValue;
              nIntRet = static_cast<int>(fValue);
          }
          else if( nBaseItemType == EPT_f64 )
          {
              if( nIndexValue * 8 + 8 > nDataSize )
              {
                  CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
                  return false;
              }
              double dfValue = 0.0;
              memcpy(&dfValue, pabyData + 8 * nIndexValue, 8);
              HFAStandard(8, &dfValue);

              dfDoubleRet = dfValue;
              const int nMax = std::numeric_limits<int>::max();
              const int nMin = std::numeric_limits<int>::min();
              if( dfDoubleRet >= nMax )
              {
                  nIntRet = nMax;
              }
              else if( dfDoubleRet <= nMin )
              {
                  nIntRet = nMin;
              }
              else if( CPLIsNan(dfDoubleRet) )
              {
                  CPLError(CE_Warning, CPLE_AppDefined,
                           "NaN converted to INT_MAX.");
                  nIntRet = nMax;
              }
              else
              {
                  nIntRet = static_cast<int>(dfDoubleRet);
              }
          }
          else
          {
              CPLError(CE_Failure, CPLE_AppDefined,
                       "Unknown base item type: %d", nBaseItemType);
              return false;
          }
      }
      break;

      case 'o':
        if( poItemObjectType != NULL )
        {
            int nExtraOffset = 0;

            if( poItemObjectType->nBytes > 0 )
            {
                if( nIndexValue != 0 &&
                    poItemObjectType->nBytes > INT_MAX / nIndexValue )
                    // TODO(schwehr): Why was this CE_Failure when the others
                    // are false?
                    return false;
                nExtraOffset = poItemObjectType->nBytes * nIndexValue;
            }
            else
            {
                for( int iIndexCounter = 0;
                     iIndexCounter < nIndexValue && nExtraOffset < nDataSize;
                     iIndexCounter++ )
                {
                    const int nInc =
                        poItemObjectType->GetInstBytes(
                            pabyData + nExtraOffset,
                            nDataSize - nExtraOffset);
                    if( nInc < 0 || nExtraOffset > INT_MAX - nInc )
                    {
                        CPLError(CE_Failure, CPLE_AppDefined,
                                 "Invalid return value");
                        // TODO(schwehr): Verify this false is okay.
                        return false;
                    }

                    nExtraOffset += nInc;
                }
            }

            if( nExtraOffset >= nDataSize )
                return false;

            pabyRawData = pabyData + nExtraOffset;

            if( pszField != NULL && strlen(pszField) > 0 )
            {
                return
                    poItemObjectType->
                        ExtractInstValue(pszField, pabyRawData,
                                          nDataOffset + nExtraOffset,
                                          nDataSize - nExtraOffset,
                                          chReqType, pReqReturn,
                                          pnRemainingDataSize);
            }
        }
        break;

      default:
        return false;
        break;
    }

    // Return the appropriate representation.
    if( chReqType == 's' )
    {
        if( pszStringRet == NULL )
        {
            // HFAEntry:: BuildEntryFromMIFObject() expects to have always 8
            // bytes before the data. In normal situations, it should not go
            // here, but that can happen if the file is corrupted so reserve the
            // first 8 bytes before the string to contain null bytes.
            memset(szNumberString, 0, 8);
            CPLsnprintf(szNumberString + 8, sizeof(szNumberString) - 8,
                        "%.14g", dfDoubleRet);
            pszStringRet = szNumberString + 8;
        }

        *((char **)pReqReturn) = pszStringRet;
        return true;
    }
    else if( chReqType == 'd' )
    {
        *((double *)pReqReturn) = dfDoubleRet;
        return true;
    }
    else if( chReqType == 'i' )
    {
        *((int *)pReqReturn) = nIntRet;
        return true;
    }
    else if( chReqType == 'p' )
    {
        *((GByte **)pReqReturn) = pabyRawData;
        return true;
    }
    else
    {
        CPLAssert(false);
        return false;
    }
}

/************************************************************************/
/*                            GetInstBytes()                            */
/*                                                                      */
/*      Get the number of bytes in a particular instance of a           */
/*      field.  This will normally be the fixed internal nBytes         */
/*      value, but for pointer objects will include the variable        */
/*      portion.                                                        */
/************************************************************************/

int HFAField::GetInstBytes( GByte *pabyData, int nDataSize )

{
    if( nBytes > -1 )
        return nBytes;

    int nCount = 1;
    int nInstBytes = 0;

    if( chPointer != '\0' )
    {
        if( nDataSize < 4 )
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
            return -1;
        }

        memcpy(&nCount, pabyData, 4);
        HFAStandard(4, &nCount);

        pabyData += 8;
        nInstBytes += 8;
    }

    if( chItemType == 'b' && nCount != 0 )  // BASEDATA
    {
        if( nDataSize - nInstBytes < 4 + 4 + 2 )
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Buffer too small");
            return -1;
        }

        GInt32 nRows = 0;
        memcpy(&nRows, pabyData, 4);
        HFAStandard(4, &nRows);
        GInt32 nColumns = 0;
        memcpy(&nColumns, pabyData + 4, 4);
        HFAStandard(4, &nColumns);
        GInt16 nBaseItemType = 0;
        memcpy(&nBaseItemType, pabyData + 8, 2);
        HFAStandard(2, &nBaseItemType);
        if( nBaseItemType < EPT_MIN || nBaseItemType > EPT_MAX )
            return -1;

        EPTType eBaseItemType = static_cast<EPTType>(nBaseItemType);

        nInstBytes += 12;

        if( nRows < 0 || nColumns < 0 )
            return -1;
        if( nColumns != 0 && nRows > INT_MAX / nColumns )
            return -1;
        if( nRows != 0 &&
            ((HFAGetDataTypeBits(eBaseItemType) + 7) / 8) > INT_MAX / nRows )
            return -1;
        if( nColumns != 0 &&
            ((HFAGetDataTypeBits(eBaseItemType) + 7) / 8) * nRows >
            INT_MAX / nColumns )
            return -1;
        if( ((HFAGetDataTypeBits(eBaseItemType) + 7) / 8) * nRows * nColumns >
            INT_MAX - nInstBytes )
            return -1;

        nInstBytes +=
            ((HFAGetDataTypeBits(eBaseItemType) + 7) / 8) * nRows * nColumns;
    }
    else if( poItemObjectType == NULL )
    {
        if( nCount != 0 &&
            HFADictionary::GetItemSize(chItemType) > INT_MAX / nCount )
            return -1;
        if( nCount * HFADictionary::GetItemSize(chItemType) >
            INT_MAX - nInstBytes )
            return -1;
        nInstBytes += nCount * HFADictionary::GetItemSize(chItemType);
    }
    else
    {
        for( int i = 0;
             i < nCount && nInstBytes < nDataSize && nInstBytes >= 0;
             i++ )
        {
            const int nThisBytes =
                poItemObjectType->GetInstBytes(pabyData,
                                                nDataSize - nInstBytes);
            if( nThisBytes < 0 || nInstBytes > INT_MAX - nThisBytes )
            {
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid return value");
                return -1;
            }

            nInstBytes += nThisBytes;
            pabyData += nThisBytes;
        }
    }

    return nInstBytes;
}

/************************************************************************/
/*                            GetInstCount()                            */
/*                                                                      */
/*      Get the count for a particular instance of a field.  This       */
/*      will normally be the built in value, but for variable fields    */
/*      this is extracted from the data itself.                         */
/************************************************************************/

int HFAField::GetInstCount( GByte * pabyData, int nDataSize )

{
    if( chPointer == '\0' )
        return nItemCount;

    if( chItemType == 'b' )
    {
        if( nDataSize < 20 )
            return 0;

        GInt32 nRows = 0;
        memcpy(&nRows, pabyData + 8, 4);
        HFAStandard(4, &nRows);
        GInt32 nColumns = 0;
        memcpy(&nColumns, pabyData + 12, 4);
        HFAStandard(4, &nColumns);

        if( nRows < 0 || nColumns < 0 )
            return 0;
        if( nColumns != 0 && nRows > INT_MAX / nColumns )
            return 0;

        return nRows * nColumns;
    }

    if( nDataSize < 4 )
        return 0;

    GInt32 nCount = 0;
    memcpy(&nCount, pabyData, 4);
    HFAStandard(4, &nCount);
    return nCount;
}

/************************************************************************/
/*                           DumpInstValue()                            */
/************************************************************************/

void HFAField::DumpInstValue( FILE *fpOut,
                              GByte *pabyData,
                              GUInt32 nDataOffset, int nDataSize,
                              const char *pszPrefix )

{
    const int nEntries = GetInstCount(pabyData, nDataSize);

    // Special case for arrays of chars or uchars which are printed
    // as a string.
    if( (chItemType == 'c' || chItemType == 'C') && nEntries > 0 )
    {
        void *pReturn = NULL;
        if( ExtractInstValue(NULL, 0,
                             pabyData, nDataOffset, nDataSize,
                             's', &pReturn) )
            CPL_IGNORE_RET_VAL(
                VSIFPrintf(fpOut, "%s%s = `%s'\n",
                           pszPrefix, pszFieldName,
                           static_cast<char *>(pReturn)));
        else
            CPL_IGNORE_RET_VAL(
                VSIFPrintf(fpOut, "%s%s = (access failed)\n",
                           pszPrefix, pszFieldName));

        return;
    }

    // For BASEDATA objects, we want to first dump their dimension and type.
    if( chItemType == 'b' )
    {
        int nDataType = 0;
        const bool bSuccess =
            ExtractInstValue(NULL, -3, pabyData, nDataOffset,
                             nDataSize, 'i', &nDataType);
        if( bSuccess )
        {
            int nColumns = 0;
            ExtractInstValue(NULL, -2, pabyData, nDataOffset,
                             nDataSize, 'i', &nColumns);
            int nRows = 0;
            ExtractInstValue(NULL, -1, pabyData, nDataOffset,
                             nDataSize, 'i', &nRows);
            CPL_IGNORE_RET_VAL(VSIFPrintf(
                fpOut, "%sBASEDATA(%s): %dx%d of %s\n",
                pszPrefix, pszFieldName,
                nColumns, nRows,
                (nDataType >= EPT_MIN && nDataType <= EPT_MAX)
                ? HFAGetDataTypeName(static_cast<EPTType>(nDataType))
                : "invalid type"));
        }
        else
        {
            CPL_IGNORE_RET_VAL(VSIFPrintf(fpOut, "%sBASEDATA(%s): empty\n",
                                          pszPrefix, pszFieldName));
        }
    }

    // Dump each entry in the field array.
    void *pReturn = NULL;

    const int nMaxEntry = std::min(MAX_ENTRY_REPORT, nEntries);
    for( int iEntry = 0; iEntry < nMaxEntry; iEntry++ )
    {
        if( nEntries == 1 )
            CPL_IGNORE_RET_VAL(
                VSIFPrintf(fpOut, "%s%s = ", pszPrefix, pszFieldName));
        else
            CPL_IGNORE_RET_VAL(
                VSIFPrintf(fpOut, "%s%s[%d] = ",
                           pszPrefix, pszFieldName, iEntry));

        switch( chItemType )
        {
          case 'f':
          case 'd':
          {
              double dfValue = 0.0;
              if( ExtractInstValue(NULL, iEntry,
                                    pabyData, nDataOffset, nDataSize,
                                    'd', &dfValue) )
                  CPL_IGNORE_RET_VAL(VSIFPrintf(fpOut, "%f\n", dfValue));
              else
                  CPL_IGNORE_RET_VAL(VSIFPrintf(fpOut, "(access failed)\n"));
          }
          break;

          case 'b':
          {
              double dfValue = 0.0;

              if( ExtractInstValue(NULL, iEntry,
                                    pabyData, nDataOffset, nDataSize,
                                    'd', &dfValue) )
                  CPL_IGNORE_RET_VAL(
                      VSIFPrintf(fpOut, "%s%.15g\n", pszPrefix, dfValue));
              else
                  CPL_IGNORE_RET_VAL(
                      VSIFPrintf(fpOut, "%s(access failed)\n", pszPrefix));
          }
          break;

          case 'e':
            if( ExtractInstValue(NULL, iEntry,
                                  pabyData, nDataOffset, nDataSize,
                                  's', &pReturn) )
                CPL_IGNORE_RET_VAL(VSIFPrintf(fpOut, "%s\n",
                                               (char *) pReturn));
            else
                CPL_IGNORE_RET_VAL(VSIFPrintf(fpOut, "(access failed)\n"));
            break;

          case 'o':
            if( !ExtractInstValue(NULL, iEntry,
                                   pabyData, nDataOffset, nDataSize,
                                   'p', &pReturn) )
            {
                CPL_IGNORE_RET_VAL(VSIFPrintf(fpOut, "(access failed)\n"));
            }
            else
            {
                CPL_IGNORE_RET_VAL(VSIFPrintf(fpOut, "\n"));

                const int nByteOffset =
                    static_cast<int>(((GByte *) pReturn) - pabyData);

                char szLongFieldName[256] = {};
                snprintf(szLongFieldName, sizeof(szLongFieldName),
                          "%s    ", pszPrefix);

                if( poItemObjectType )
                    poItemObjectType->DumpInstValue(fpOut,
                                                    pabyData + nByteOffset,
                                                    nDataOffset + nByteOffset,
                                                    nDataSize - nByteOffset,
                                                    szLongFieldName);
            }
            break;

          default:
          {
              GInt32 nIntValue = 0;

              if( ExtractInstValue(NULL, iEntry,
                                   pabyData, nDataOffset, nDataSize,
                                   'i', &nIntValue) )
                  CPL_IGNORE_RET_VAL(VSIFPrintf(fpOut, "%d\n", nIntValue));
              else
                  CPL_IGNORE_RET_VAL(VSIFPrintf(fpOut, "(access failed)\n"));
          }
          break;
        }
    }

    if( nEntries > MAX_ENTRY_REPORT )
        CPL_IGNORE_RET_VAL(VSIFPrintf(
            fpOut, "%s ... remaining instances omitted ...\n", pszPrefix));

    if( nEntries == 0 )
        CPL_IGNORE_RET_VAL(
            VSIFPrintf(fpOut, "%s%s = (no values)\n", pszPrefix, pszFieldName));
}
