/******************************************************************************
 *
 * Project:  TGA read-only driver
 * Author:   Even Rouault <even.rouault at spatialys.com>
 *
 ******************************************************************************
 * Copyright (c) 2020, Even Rouault <even.rouault at spatialys.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "gdal_pam.h"

#include <algorithm>
#include <vector>

extern "C" void CPL_DLL GDALRegister_TGA();

enum ImageType
{
    UNCOMPRESSED_COLORMAP = 1,
    UNCOMPRESSED_TRUE_COLOR = 2,
    UNCOMPRESSED_GRAYSCALE = 3,
    RLE_COLORMAP = 9,
    RLE_TRUE_COLOR = 10,
    RLE_GRAYSCALE = 11,
};

struct ImageHeader
{
    GByte     nIDLength;
    bool      bHasColorMap;
    ImageType eImageType;
    GUInt16   nColorMapFirstIdx;
    GUInt16   nColorMapLength;
    GByte     nColorMapEntrySize;
    GUInt16   nXOrigin;
    GUInt16   nYOrigin;
    GByte     nPixelDepth;
    GByte     nImageDescriptor;
};

/************************************************************************/
/*                         GDALTGADataset                               */
/************************************************************************/

class GDALTGADataset final: public GDALPamDataset
{
        friend class GDALTGARasterBand;

        struct ScanlineState
        {
            // Offset in the file of the start of the scanline
            vsi_l_offset        nOffset = 0;
            bool                bRemainingPixelsAreRLERun = false;
            // Number of pixels remaining from a previous scanline.
            // See https://en.wikipedia.org/wiki/Truevision_TGA#Specification_discrepancies
            // TGA v2.0 specification states  "Run-length Packets should never encode pixels from more than one scan line."
            // but earlier specification said the contrary.
            int                 nRemainingPixelsPrevScanline = 0;
            // Value of pixels remaining from a previous RLE run
            std::vector<GByte>  abyDataPrevRLERun{};
        };

        ImageHeader m_sImageHeader;
        VSILFILE   *m_fpImage;
        unsigned    m_nImageDataOffset = 0;
        std::vector<ScanlineState> m_aoScanlineState{};
        int         m_nLastLineKnownOffset = 0;
        bool        m_bFourthChannelIsAlpha = false;

    public:
        GDALTGADataset(const ImageHeader& sHeader, VSILFILE* fpImage);
        ~GDALTGADataset() override;

        static int Identify(GDALOpenInfo* poOpenInfo);
        static GDALDataset* Open(GDALOpenInfo* poOpenInfo);
};

/************************************************************************/
/*                        GDALTGARasterBand                             */
/************************************************************************/

class GDALTGARasterBand final: public GDALPamRasterBand
{
        std::unique_ptr<GDALColorTable> m_poColorTable{};
        bool                            m_bHasNoDataValue = false;
        double                          m_dfNoDataValue = 0;

    public:
        GDALTGARasterBand(GDALTGADataset* poDSIn, int nBandIn,
                          GDALDataType eDataTypeIn);

        CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void* pImage) override;

        GDALColorTable* GetColorTable() override { return m_poColorTable.get(); }

        GDALColorInterp GetColorInterpretation() override
        {
            if( m_poColorTable )
                return GCI_PaletteIndex;
            GDALTGADataset* poGDS = reinterpret_cast<GDALTGADataset*>(poDS);
            if( poGDS->GetRasterCount() == 1 )
                return GCI_GrayIndex;
            if( nBand == 4 )
                return poGDS->m_bFourthChannelIsAlpha ? GCI_AlphaBand : GCI_Undefined;
            return static_cast<GDALColorInterp>(GCI_RedBand + nBand - 1);
        }

        double GetNoDataValue(int* pbHasNoData) override
        {
            if( pbHasNoData )
                *pbHasNoData = m_bHasNoDataValue;
            return m_dfNoDataValue;
        }
};

/************************************************************************/
/*                            Identify()                                */
/************************************************************************/

int GDALTGADataset::Identify(GDALOpenInfo* poOpenInfo)
{
    if( poOpenInfo->fpL == nullptr || poOpenInfo->nHeaderBytes < 18 )
        return FALSE;
    const GByte nColorType = poOpenInfo->pabyHeader[1];
    if( nColorType > 1 )
        return FALSE;
    const GByte nImageType = poOpenInfo->pabyHeader[2];
    if( nImageType != UNCOMPRESSED_COLORMAP &&
        nImageType != UNCOMPRESSED_TRUE_COLOR &&
        nImageType != UNCOMPRESSED_GRAYSCALE &&
        nImageType != RLE_COLORMAP &&
        nImageType != RLE_TRUE_COLOR &&
        nImageType != RLE_GRAYSCALE )
        return FALSE;
    if( nImageType == UNCOMPRESSED_COLORMAP || nImageType == RLE_COLORMAP )
    {
        if( nColorType != 1 )
            return FALSE;
    }
    else
    {
        if( nColorType != 0 )
            return FALSE;
    }

    // Mostly useful for fuzzing purposes to be able to fuzz TGA on small files
    // without relying on the tga extension
    if( poOpenInfo->nHeaderBytes > 26 &&
        memcmp(poOpenInfo->pabyHeader + poOpenInfo->nHeaderBytes - 26,
               "TRUEVISION-XFILE.\x00", 18) == 0 )
    {
        return TRUE;
    }

    if( !EQUAL(CPLGetExtension(poOpenInfo->pszFilename), "tga") )
        return FALSE;
    return TRUE;
}

/************************************************************************/
/*                           GDALTGADataset()                           */
/************************************************************************/

GDALTGADataset::GDALTGADataset(const ImageHeader& sHeader, VSILFILE* fpImage):
            m_sImageHeader(sHeader), m_fpImage(fpImage)
{
    m_nImageDataOffset = 18 + m_sImageHeader.nIDLength;
    if( m_sImageHeader.bHasColorMap )
    {
        m_nImageDataOffset += m_sImageHeader.nColorMapLength *
            ((m_sImageHeader.nColorMapEntrySize + 7) / 8);
    }
}

/************************************************************************/
/*                          ~GDALTGADataset()                           */
/************************************************************************/

GDALTGADataset::~GDALTGADataset()
{
    if( m_fpImage )
        VSIFCloseL(m_fpImage);
}

/************************************************************************/
/*                         GDALTGARasterBand()                          */
/************************************************************************/

GDALTGARasterBand::GDALTGARasterBand(GDALTGADataset* poDSIn, int nBandIn,
                                     GDALDataType eDataTypeIn)
{
    poDS = poDSIn;
    nBand = nBandIn;
    eDataType = eDataTypeIn;
    nBlockXSize = poDSIn->GetRasterXSize();
    nBlockYSize = 1;
    if( poDSIn->m_sImageHeader.bHasColorMap )
    {
        VSIFSeekL(poDSIn->m_fpImage, 18 + poDSIn->m_sImageHeader.nIDLength, SEEK_SET);
        m_poColorTable.reset(new GDALColorTable());
        const int nColorTableByteCount = poDSIn->m_sImageHeader.nColorMapLength *
            ((poDSIn->m_sImageHeader.nColorMapEntrySize + 7) / 8);
        std::vector<GByte> abyData(nColorTableByteCount);
        VSIFReadL(&abyData[0], 1, abyData.size(), poDSIn->m_fpImage);
        if( poDSIn->m_sImageHeader.nColorMapEntrySize == 24 )
        {
            for( unsigned i = 0; i < poDSIn->m_sImageHeader.nColorMapLength; ++i )
            {
                GDALColorEntry sEntry;
                sEntry.c1 = abyData[3 * i + 2];
                sEntry.c2 = abyData[3 * i + 1];
                sEntry.c3 = abyData[3 * i + 0];
                sEntry.c4 = 255;
                m_poColorTable->SetColorEntry(
                    poDSIn->m_sImageHeader.nColorMapFirstIdx + i, &sEntry);
            }
        }
        else if( poDSIn->m_sImageHeader.nColorMapEntrySize == 32 )
        {
            unsigned nCountAlpha0 = 0;
            unsigned nAlphaIdx = 0;
            for( unsigned i = 0; i < poDSIn->m_sImageHeader.nColorMapLength; ++i )
            {
                GDALColorEntry sEntry;
                sEntry.c1 = abyData[4 * i + 2];
                sEntry.c2 = abyData[4 * i + 1];
                sEntry.c3 = abyData[4 * i + 0];
                sEntry.c4 = abyData[4 * i + 3];
                m_poColorTable->SetColorEntry(
                    poDSIn->m_sImageHeader.nColorMapFirstIdx + i, &sEntry);
                if( sEntry.c4 == 0 )
                {
                    nCountAlpha0 ++;
                    nAlphaIdx = poDSIn->m_sImageHeader.nColorMapFirstIdx + i;
                }
            }
            if( nCountAlpha0 == 1 )
            {
                m_dfNoDataValue = nAlphaIdx;
                m_bHasNoDataValue = true;
            }
        }
        else if( poDSIn->m_sImageHeader.nColorMapEntrySize == 15 ||
                 poDSIn->m_sImageHeader.nColorMapEntrySize == 16 )
        {
            for( unsigned i = 0; i < poDSIn->m_sImageHeader.nColorMapLength; ++i )
            {
                GUInt16 nVal = (abyData[2 * i + 1] << 8) | abyData[2 * i];
                GDALColorEntry sEntry;
                sEntry.c1 = ((nVal >> 10) & 31) << 3;
                sEntry.c2 = ((nVal >> 5) & 31) << 3;
                sEntry.c3 = ((nVal >> 0) & 31) << 3;
                sEntry.c4 = 255;
                m_poColorTable->SetColorEntry(
                    poDSIn->m_sImageHeader.nColorMapFirstIdx + i, &sEntry);
            }
        }
    }
}

/************************************************************************/
/*                            IReadBlock()                              */
/************************************************************************/

CPLErr GDALTGARasterBand::IReadBlock(int /* nBlockXOff */, int nBlockYOff, void* pImage)
{
    GDALTGADataset* poGDS = reinterpret_cast<GDALTGADataset*>(poDS);

    const int nBands = poGDS->GetRasterCount();
    const int nLine = (poGDS->m_sImageHeader.nImageDescriptor & (1 << 5)) ?
                            nBlockYOff : nRasterYSize - 1 - nBlockYOff;
    const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
    if( !poGDS->m_aoScanlineState.empty() ) // RLE
    {
        if( poGDS->m_aoScanlineState[nLine].nOffset == 0 )
        {
            for( int i = poGDS->m_nLastLineKnownOffset; i < nLine; i++ )
            {
                if( IReadBlock(0,
                           (poGDS->m_sImageHeader.nImageDescriptor & (1 << 5)) ?
                                i : nRasterYSize - 1 - i,
                           nullptr) != CE_None )
                {
                    return CE_Failure;
                }
            }
        }
        VSIFSeekL(poGDS->m_fpImage, poGDS->m_aoScanlineState[nLine].nOffset, SEEK_SET);
        int x = 0;
        std::vector<GByte> abyData;
        const int nBytesPerPixel =
            (nBands == 1) ? nDTSize : (nBands == 4) ? 4 : poGDS->m_sImageHeader.nPixelDepth / 8;

        // Deal with a run from a previous scanline that continues on next
        // one(s)
        bool bRLERun = false;
        int nRemainingPixelsPrevScanline =
            poGDS->m_aoScanlineState[nLine].nRemainingPixelsPrevScanline;
        while( x < nRasterXSize )
        {
            int nPixelsToFillUnclamped;
            if( nRemainingPixelsPrevScanline != 0 )
            {
                abyData = poGDS->m_aoScanlineState[nLine].abyDataPrevRLERun;
                bRLERun = poGDS->m_aoScanlineState[nLine].bRemainingPixelsAreRLERun;
                nPixelsToFillUnclamped = nRemainingPixelsPrevScanline;
            }
            else
            {
                GByte nRepeatCount = 0;
                VSIFReadL(&nRepeatCount, 1, 1, poGDS->m_fpImage);
                bRLERun = ( nRepeatCount & 0x80 ) != 0;
                nPixelsToFillUnclamped = (nRepeatCount & 0x7f) + 1;
            }
            const int nPixelsToFill = std::min(nRasterXSize - x,
                                               nPixelsToFillUnclamped);
            if( bRLERun )
            {
                if( nBands == 1 )
                {
                    if( nRemainingPixelsPrevScanline == 0 )
                    {
                        abyData.resize(nDTSize);
                        VSIFReadL(&abyData[0],
                                  1,
                                  nDTSize,
                                  poGDS->m_fpImage);
                    }
                    if( pImage != nullptr )
                    {
                        GDALCopyWords(&abyData[0],
                                    eDataType,
                                    0,
                                    static_cast<GByte*>(pImage) + x * nDTSize,
                                    eDataType,
                                    nDTSize,
                                    nPixelsToFill);
                    }
                }
                else
                {
                    if( nRemainingPixelsPrevScanline == 0 )
                    {
                        abyData.resize(4);
                        VSIFReadL(&abyData[0], 1, nBytesPerPixel, poGDS->m_fpImage);
                    }
                    if( pImage != nullptr )
                    {
                        if( poGDS->m_sImageHeader.nPixelDepth == 16 )
                        {
                            const GUInt16 nValue = abyData[0] | (abyData[1] << 8);
                            const GByte nByteVal = ((nValue >> (5 * (3 - nBand))) & 31) << 3;
                            memset(static_cast<GByte*>(pImage) + x, nByteVal, nPixelsToFill);
                        }
                        else
                        {
                            memset(static_cast<GByte*>(pImage) + x,
                                   abyData[nBand <= 3 ? 3 - nBand: 3],
                                   nPixelsToFill);
                        }
                    }
                }
            }
            else
            {
                if( pImage == nullptr )
                {
                    VSIFSeekL(poGDS->m_fpImage, nPixelsToFill * nBytesPerPixel, SEEK_CUR);
                }
                else
                {
                    if( nBands == 1 )
                    {
                        VSIFReadL(static_cast<GByte*>(pImage) + x * nDTSize,
                                  1,
                                  nPixelsToFill * nDTSize,
                                  poGDS->m_fpImage);
                    }
                    else
                    {
                        abyData.resize( nBytesPerPixel * nPixelsToFill );
                        VSIFReadL(&abyData[0], 1, abyData.size(), poGDS->m_fpImage);
                        if( poGDS->m_sImageHeader.nPixelDepth == 16 )
                        {
                            for(int i = 0; i < nPixelsToFill; i++ )
                            {
                                const GUInt16 nValue = abyData[2 * i] | (abyData[2 * i + 1] << 8);
                                static_cast<GByte*>(pImage)[x + i] =
                                    ((nValue >> (5 * (3 - nBand))) & 31) << 3;
                            }
                        }
                        else
                        {
                            if( nBand <= 3 )
                            {
                                for(int i = 0; i < nPixelsToFill; i++ )
                                {
                                    static_cast<GByte*>(pImage)[x + i] =
                                        abyData[3 - nBand + nBytesPerPixel * i];
                                }
                            }
                            else
                            {
                                for(int i = 0; i < nPixelsToFill; i++ )
                                {
                                    static_cast<GByte*>(pImage)[x + i] =
                                        abyData[3 + nBytesPerPixel * i];
                                }
                            }
                        }
                    }
                }
            }
            x += nPixelsToFill;
            nRemainingPixelsPrevScanline = nPixelsToFillUnclamped - nPixelsToFill;
        }

        if( nLine + 1 < nRasterYSize )
        {
            poGDS->m_aoScanlineState[nLine + 1].nOffset = VSIFTellL(poGDS->m_fpImage);
            poGDS->m_aoScanlineState[nLine + 1].bRemainingPixelsAreRLERun = bRLERun;
            poGDS->m_aoScanlineState[nLine + 1].nRemainingPixelsPrevScanline = nRemainingPixelsPrevScanline;
            if( nRemainingPixelsPrevScanline )
                poGDS->m_aoScanlineState[nLine + 1].abyDataPrevRLERun = abyData;
        }
        if( pImage && nBands == 1 )
        {
#ifdef CPL_MSB
            if( nDTSize > 1 )
            {
                GDALSwapWords(pImage, nDTSize, nRasterXSize, nDTSize);
            }
#endif
        }
        return CE_None;
    }

    if( pImage == nullptr )
        return CE_Failure;

    if( nBands == 1 )
    {
        vsi_l_offset nOffset = poGDS->m_nImageDataOffset +
            static_cast<vsi_l_offset>(nLine) * nRasterXSize * nDTSize;
        VSIFSeekL(poGDS->m_fpImage, nOffset, SEEK_SET);
        VSIFReadL(pImage, 1, nRasterXSize * nDTSize, poGDS->m_fpImage);
#ifdef CPL_MSB
        if( nDTSize > 1 )
        {
            GDALSwapWords(pImage, nDTSize, nRasterXSize, nDTSize);
        }
#endif
    }
    else
    {
        const int nBytesPerPixel = (nBands == 4) ? 4 : poGDS->m_sImageHeader.nPixelDepth / 8;
        std::vector<GByte> abyData;
        abyData.resize( nBytesPerPixel * nRasterXSize );
        vsi_l_offset nOffset = poGDS->m_nImageDataOffset +
            static_cast<vsi_l_offset>(nLine) * nRasterXSize * nBytesPerPixel;
        VSIFSeekL(poGDS->m_fpImage, nOffset, SEEK_SET);
        VSIFReadL(&abyData[0], 1, nRasterXSize * nBytesPerPixel, poGDS->m_fpImage);
        if( poGDS->m_sImageHeader.nPixelDepth == 16 )
        {
            for(int i = 0; i < nRasterXSize; i++ )
            {
                const GUInt16 nValue = abyData[2 * i] | (abyData[2 * i + 1] << 8);
                static_cast<GByte*>(pImage)[i] = ((nValue >> (5 * (3 - nBand))) & 31) << 3;
            }
        }
        else
        {
            if( nBand <= 3 )
            {
                for(int i = 0; i < nRasterXSize; i++ )
                {
                    static_cast<GByte*>(pImage)[i] = abyData[3 - nBand + nBytesPerPixel * i];
                }
            }
            else
            {
                for(int i = 0; i < nRasterXSize; i++ )
                {
                    static_cast<GByte*>(pImage)[i] = abyData[3 + nBytesPerPixel * i];
                }
            }
        }
    }

    return CE_None;
}

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

GDALDataset* GDALTGADataset::Open(GDALOpenInfo* poOpenInfo)
{
    if( !Identify(poOpenInfo) )
        return nullptr;
    if( poOpenInfo->eAccess == GA_Update )
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Update of existing TGA file not supported");
        return nullptr;
    }

    ImageHeader sHeader;
    sHeader.nIDLength = poOpenInfo->pabyHeader[0];
    sHeader.bHasColorMap = poOpenInfo->pabyHeader[1] == 1;
    sHeader.eImageType = static_cast<ImageType>(poOpenInfo->pabyHeader[2]);
    sHeader.nColorMapFirstIdx = CPL_LSBUINT16PTR(poOpenInfo->pabyHeader + 3);
    sHeader.nColorMapLength = CPL_LSBUINT16PTR(poOpenInfo->pabyHeader + 5);
    sHeader.nColorMapEntrySize = poOpenInfo->pabyHeader[7];
    sHeader.nXOrigin = CPL_LSBUINT16PTR(poOpenInfo->pabyHeader + 8);
    sHeader.nYOrigin = CPL_LSBUINT16PTR(poOpenInfo->pabyHeader + 10);
    const GUInt16 nWidth = CPL_LSBUINT16PTR(poOpenInfo->pabyHeader + 12);
    const GUInt16 nHeight = CPL_LSBUINT16PTR(poOpenInfo->pabyHeader + 14);
    if( nWidth == 0 || nHeight == 0 )
        return nullptr;
    sHeader.nPixelDepth = poOpenInfo->pabyHeader[16];
    sHeader.nImageDescriptor = poOpenInfo->pabyHeader[17];

    if( sHeader.bHasColorMap )
    {
        if( sHeader.nColorMapEntrySize != 15 &&
            sHeader.nColorMapEntrySize != 16 &&
            sHeader.nColorMapEntrySize != 24 &&
            sHeader.nColorMapEntrySize != 32 )
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Color map entry size %d not supported",
                     sHeader.nColorMapEntrySize);
            return nullptr;
        }
    }

    GDALTGADataset* poDS = new GDALTGADataset(sHeader, poOpenInfo->fpL);

    VSIFSeekL(poOpenInfo->fpL, 0, SEEK_END);
    const auto nSize = VSIFTellL(poOpenInfo->fpL);

    bool hasFourthChannel = (sHeader.nImageDescriptor & 15) == 8;
    bool fourthChannelIsAlpha = hasFourthChannel;

    // Detect presence of optional TGA file footer.
    if( nSize >= 26 )
    {
        VSIFSeekL(poOpenInfo->fpL, nSize - 26, SEEK_SET);
        GByte abyTail[26];
        VSIFReadL(abyTail, 1, 26, poOpenInfo->fpL);
        if( memcmp(abyTail + 8, "TRUEVISION-XFILE.\x00", 18) == 0 )
        {
            const unsigned nExtensionAreaOffset = CPL_LSBUINT32PTR(abyTail);
            if( nExtensionAreaOffset > 0 )
            {
                VSIFSeekL(poOpenInfo->fpL, nExtensionAreaOffset, SEEK_SET);
                std::vector<GByte> abyExtendedData(495);
                VSIFReadL(&abyExtendedData[0], 1, abyExtendedData.size(),
                          poOpenInfo->fpL);
                const GUInt16 nExtSize = CPL_LSBUINT16PTR(&abyExtendedData[0]);
                if( nExtSize >= 495 )
                {
                    if( abyExtendedData[2] != ' ' && abyExtendedData[2] != '\0' )
                    {
                        std::string osAuthorName;
                        osAuthorName.assign(
                            reinterpret_cast<const char*>(&abyExtendedData[2]), 40);
                        osAuthorName.resize(strlen(osAuthorName.c_str()));
                        while( !osAuthorName.empty() && osAuthorName.back() == ' ' )
                        {
                            osAuthorName.resize(osAuthorName.size()-1);
                        }
                        poDS->GDALDataset::SetMetadataItem("AUTHOR_NAME", osAuthorName.c_str());
                    }

                    if( abyExtendedData[43] != ' ' && abyExtendedData[43] != '\0' )
                    {
                        std::string osComments;
                        for( int i = 0; i < 4; i++ )
                        {
                            if( abyExtendedData[43 + 81 * i] == '\0' )
                            {
                                break;
                            }
                            std::string osLine;
                            osLine.assign(reinterpret_cast<const char*>(
                                &abyExtendedData[43 + 81 * i]), 80);
                            osLine.resize(strlen(osLine.c_str()));
                            while( !osLine.empty() && osLine.back() == ' ' )
                            {
                                osLine.resize(osLine.size()-1);
                            }
                            if( i > 0 )
                                osComments += '\n';
                            osComments += osLine;
                        }
                        poDS->GDALDataset::SetMetadataItem("COMMENTS", osComments.c_str());
                    }

                    // const GUInt32 nOffsetToScanlineTable = CPL_LSBUINT32PTR(&abyExtendedData[490]);
                    // Did not find yet an image using a scanline table

                    const GByte nAttributeType = abyExtendedData[494];
                    if( nAttributeType == 1 )
                    {
                        // undefined data in the Alpha field, can be ignored
                        hasFourthChannel = false;
                    }
                    else if( nAttributeType == 2 )
                    {
                        // undefined data in the Alpha field, but should be retained
                        fourthChannelIsAlpha = false;
                    }
                }
            }
        }
    }

    if( sHeader.nIDLength > 0 && 18 + sHeader.nIDLength <= poOpenInfo->nHeaderBytes )
    {
        std::string osID;
        osID.assign( reinterpret_cast<const char*>(poOpenInfo->pabyHeader + 18),
                     sHeader.nIDLength );
        poDS->GDALDataset::SetMetadataItem("IMAGE_ID", osID.c_str());
    }

    poOpenInfo->fpL = nullptr;
    poDS->nRasterXSize = nWidth;
    poDS->nRasterYSize = nHeight;
    poDS->m_bFourthChannelIsAlpha = fourthChannelIsAlpha;
    if( sHeader.eImageType == RLE_COLORMAP ||
        sHeader.eImageType == RLE_GRAYSCALE ||
        sHeader.eImageType == RLE_TRUE_COLOR )
    {
        // nHeight is a GUInt16, so well bounded...
        // coverity[tainted_data]
        poDS->m_aoScanlineState.resize(nHeight);
        poDS->m_aoScanlineState[0].nOffset = poDS->m_nImageDataOffset;
    }
    if( sHeader.eImageType == UNCOMPRESSED_COLORMAP ||
        sHeader.eImageType == RLE_COLORMAP ||
        sHeader.eImageType == UNCOMPRESSED_GRAYSCALE ||
        sHeader.eImageType == RLE_GRAYSCALE )
    {
        if( sHeader.nPixelDepth != 8 &&
            sHeader.nPixelDepth != 16 )
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Pixel depth %d not supported", sHeader.nPixelDepth);
            delete poDS;
            return nullptr;
        }
        poDS->SetBand(1, new GDALTGARasterBand(poDS, 1,
            sHeader.nPixelDepth == 16 ? GDT_UInt16 : GDT_Byte));
    }
    else
    {
        if( sHeader.nPixelDepth != 16 &&
            sHeader.nPixelDepth != 24 &&
            sHeader.nPixelDepth != 32 )
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Pixel depth %d not supported", sHeader.nPixelDepth);
            delete poDS;
            return nullptr;
        }
        int l_nBands = sHeader.nPixelDepth == 16 ? 3 : (3 + (hasFourthChannel ? 1 : 0));
        for( int iBand = 1; iBand <= l_nBands; iBand++ )
        {
            poDS->SetBand(iBand, new GDALTGARasterBand(poDS, iBand, GDT_Byte));
        }
    }

/* -------------------------------------------------------------------- */
/*      Initialize any PAM information.                                 */
/* -------------------------------------------------------------------- */

    poDS->SetDescription( poOpenInfo->pszFilename );
    poDS->TryLoadXML();

/* -------------------------------------------------------------------- */
/*      Check for overviews.                                            */
/* -------------------------------------------------------------------- */

    poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename );

    return poDS;
}
/************************************************************************/
/*                       GDALRegister_TGA()                             */
/************************************************************************/

void GDALRegister_TGA()

{
    if( GDALGetDriverByName("TGA") != nullptr )
        return;

    GDALDriver *poDriver = new GDALDriver();

    poDriver->SetDescription("TGA");
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
                              "TGA/TARGA Image File Format");
    poDriver->SetMetadataItem( GDAL_DMD_MIMETYPE, "image/x-tga" );
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/tga.html");
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "tga");
    poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );

    poDriver->pfnOpen = GDALTGADataset::Open;
    poDriver->pfnIdentify = GDALTGADataset::Identify;

    GetGDALDriverManager()->RegisterDriver(poDriver);
}
