/******************************************************************************
 *
 * 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,
                              static_cast<size_t>(nPixelsToFill) *
                                  nBytesPerPixel,
                              SEEK_CUR);
                }
                else
                {
                    if (nBands == 1)
                    {
                        VSIFReadL(static_cast<GByte *>(pImage) + x * nDTSize, 1,
                                  static_cast<size_t>(nPixelsToFill) * nDTSize,
                                  poGDS->m_fpImage);
                    }
                    else
                    {
                        abyData.resize(static_cast<size_t>(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 =
                    std::move(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, static_cast<size_t>(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(static_cast<size_t>(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, abyData.size(), 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);
}
