/******************************************************************************
 * $Id: calsdataset.cpp 33717 2016-03-14 06:29:14Z goatbar $
 *
 * Project:  CALS driver
 * Purpose:  CALS driver
 * Author:   Even Rouault, <even dot rouault at spatialys dot com>
 *
 ******************************************************************************
 * Copyright (c) 2015, Even Rouault <even dot rouault at spatialys dot 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_frmts.h"
#include "gdal_pam.h"
#include "gdal_priv.h"

#include "tiff.h"

CPL_CVSID("$Id: calsdataset.cpp 33717 2016-03-14 06:29:14Z goatbar $");

/************************************************************************/
/* ==================================================================== */
/*                            CALSDataset                               */
/* ==================================================================== */
/************************************************************************/

class CALSDataset : public GDALPamDataset
{
    friend class CALSRasterBand;

    CPLString    osTIFFHeaderFilename;
    CPLString    osSparseFilename;
    GDALDataset* poUnderlyingDS;

    static void WriteLEInt16(VSILFILE* fp, GInt16 nVal);
    static void WriteLEInt32(VSILFILE* fp, GInt32 nVal);
    static void WriteTIFFTAG(VSILFILE* fp, GInt16 nTagName, GInt16 nTagType,
                             GInt32 nTagValue);

  public:
                CALSDataset();
                ~CALSDataset();

    static int          Identify( GDALOpenInfo * poOpenInfo );
    static GDALDataset *Open( GDALOpenInfo * );
    static GDALDataset *CreateCopy( const char *pszFilename,
                                           GDALDataset *poSrcDS,
                                           int bStrict,
                                           char **papszOptions,
                                           GDALProgressFunc pfnProgress,
                                           void *pProgressData );
};

/************************************************************************/
/* ==================================================================== */
/*                          CALSRasterBand                              */
/* ==================================================================== */
/************************************************************************/

class CALSRasterBand: public GDALPamRasterBand
{
    GDALRasterBand* poUnderlyingBand;

  public:
    CALSRasterBand(CALSDataset* poDSIn)
    {
        this->poDS = poDSIn;
        poUnderlyingBand = poDSIn->poUnderlyingDS->GetRasterBand(1);
        poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
        nBand = 1;
        eDataType = GDT_Byte;
    }

    virtual CPLErr IReadBlock( int nBlockXOff, int nBlockYOff, void * pData )
    {
        return poUnderlyingBand->ReadBlock(nBlockXOff, nBlockYOff, pData);
    }

    virtual CPLErr IRasterIO( GDALRWFlag eRWFlag,
                                int nXOff, int nYOff, int nXSize, int nYSize,
                                void * pData, int nBufXSize, int nBufYSize,
                                GDALDataType eBufType,
                                GSpacing nPixelSpace,
                                GSpacing nLineSpace,
                                GDALRasterIOExtraArg* psExtraArg )
    {
        return poUnderlyingBand->RasterIO(
                    eRWFlag, nXOff, nYOff, nXSize, nYSize,
                    pData, nBufXSize, nBufYSize, eBufType,
                    nPixelSpace, nLineSpace, psExtraArg ) ;
    }

    virtual GDALColorTable* GetColorTable()
    {
        return poUnderlyingBand->GetColorTable();
    }

    virtual GDALColorInterp GetColorInterpretation()
    {
        return GCI_PaletteIndex;
    }

    virtual char** GetMetadata(const char* pszDomain)
    {
        return poUnderlyingBand->GetMetadata(pszDomain);
    }

    virtual const char* GetMetadataItem(const char* pszKey, const char* pszDomain)
    {
        return poUnderlyingBand->GetMetadataItem(pszKey, pszDomain);
    }
};

/************************************************************************/
/* ==================================================================== */
/*                          CALSWrapperSrcBand                          */
/* ==================================================================== */
/************************************************************************/

class CALSWrapperSrcBand: public GDALPamRasterBand
{
        GDALDataset* poSrcDS;
        int bInvertValues;

    public:
        CALSWrapperSrcBand(GDALDataset* poSrcDSIn)
        {
            this->poSrcDS = poSrcDSIn;
            SetMetadataItem("NBITS", "1", "IMAGE_STRUCTURE");
            poSrcDS->GetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
            eDataType = GDT_Byte;
            bInvertValues = TRUE;
            GDALColorTable* poCT = poSrcDS->GetRasterBand(1)->GetColorTable();
            if( poCT != NULL && poCT->GetColorEntryCount() >= 2 )
            {
                const GDALColorEntry* psEntry1 = poCT->GetColorEntry(0);
                const GDALColorEntry* psEntry2 = poCT->GetColorEntry(1);
                if( psEntry1->c1 == 255 && psEntry1->c2 == 255 && psEntry1->c3 == 255 &&
                    psEntry2->c1 == 0 && psEntry2->c2 == 0 && psEntry2->c3 == 0 )
                {
                    bInvertValues = FALSE;
                }
            }
        }

        virtual CPLErr IReadBlock( CPL_UNUSED int nBlockXOff,
                                   CPL_UNUSED int nBlockYOff,
                                   CPL_UNUSED void * pData )
        {
            // Should not be called
            return CE_Failure;
        }

        virtual CPLErr IRasterIO( GDALRWFlag eRWFlag,
                                    int nXOff, int nYOff, int nXSize, int nYSize,
                                    void * pData, int nBufXSize, int nBufYSize,
                                    GDALDataType eBufType,
                                    GSpacing nPixelSpace,
                                    GSpacing nLineSpace,
                                    GDALRasterIOExtraArg* psExtraArg )
        {
            CPLErr eErr = poSrcDS->GetRasterBand(1)->RasterIO(
                        eRWFlag, nXOff, nYOff, nXSize, nYSize,
                        pData, nBufXSize, nBufYSize, eBufType,
                        nPixelSpace, nLineSpace, psExtraArg ) ;
            if( bInvertValues )
            {
                for(int j=0;j<nBufYSize;j++)
                {
                    for(int i=0;i<nBufXSize;i++)
                        ((GByte*)pData)[j * nLineSpace + i * nPixelSpace] =
                            1 - ((GByte*)pData)[j * nLineSpace + i * nPixelSpace];
                }
            }
            return eErr;
        }

};

/************************************************************************/
/* ==================================================================== */
/*                          CALSWrapperSrcDataset                       */
/* ==================================================================== */
/************************************************************************/

class CALSWrapperSrcDataset: public GDALPamDataset
{
    public:
        CALSWrapperSrcDataset(GDALDataset* poSrcDS, const char* pszPadding)
        {
            nRasterXSize = poSrcDS->GetRasterXSize();
            nRasterYSize = poSrcDS->GetRasterYSize();
            SetBand(1, new CALSWrapperSrcBand(poSrcDS));
            SetMetadataItem("TIFFTAG_DOCUMENTNAME", pszPadding);
        }
};


/************************************************************************/
/* ==================================================================== */
/*                            CALSDataset                               */
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                           CALSDataset()                              */
/************************************************************************/

CALSDataset::CALSDataset()
{
    poUnderlyingDS = NULL;
}

/************************************************************************/
/*                            ~CALSDataset()                            */
/************************************************************************/

CALSDataset::~CALSDataset()

{
    delete poUnderlyingDS;
    if( osTIFFHeaderFilename.size() )
        VSIUnlink(osTIFFHeaderFilename);
    if( osSparseFilename.size() )
        VSIUnlink(osSparseFilename);
}

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

int CALSDataset::Identify( GDALOpenInfo * poOpenInfo )

{
    // If in the ingested bytes we found neither srcdocid: or rtype: 1, give up
    if( poOpenInfo->nHeaderBytes == 0 ||
        (strstr( (const char*) poOpenInfo->pabyHeader, "srcdocid:") == NULL &&
         strstr( (const char*) poOpenInfo->pabyHeader, "rtype: 1") == NULL) )
        return FALSE;

    // If we found srcdocid: try to ingest up to 2048 bytes
    if( strstr( (const char*) poOpenInfo->pabyHeader, "srcdocid:") &&
        !poOpenInfo->TryToIngest(2048) )
        return FALSE;

    return strstr((const char*) poOpenInfo->pabyHeader, "rtype: 1") != NULL &&
           strstr((const char*) poOpenInfo->pabyHeader, "rorient:") != NULL &&
           strstr((const char*) poOpenInfo->pabyHeader, "rpelcnt:") != NULL;
}

/************************************************************************/
/*                           WriteLEInt16()                             */
/************************************************************************/

void CALSDataset::WriteLEInt16(VSILFILE* fp, GInt16 nVal)
{
    nVal = CPL_LSBWORD16(nVal);
    VSIFWriteL(&nVal, 1, 2, fp);
}

/************************************************************************/
/*                            WriteLEInt32()                            */
/************************************************************************/

void CALSDataset::WriteLEInt32(VSILFILE* fp, GInt32 nVal)
{
    nVal = CPL_LSBWORD32(nVal);
    VSIFWriteL(&nVal, 1, 4, fp);
}

/************************************************************************/
/*                            WriteTIFFTAG()                            */
/************************************************************************/

void CALSDataset::WriteTIFFTAG(VSILFILE* fp, GInt16 nTagName, GInt16 nTagType,
                               GInt32 nTagValue)
{
    WriteLEInt16(fp, nTagName);
    WriteLEInt16(fp, nTagType);
    WriteLEInt32(fp, 1);
    WriteLEInt32(fp, nTagValue);
}

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

GDALDataset *CALSDataset::Open( GDALOpenInfo * poOpenInfo )

{
    if (!Identify(poOpenInfo) || poOpenInfo->fpL == NULL )
        return NULL;

    const char* pszRPelCnt = strstr((const char*) poOpenInfo->pabyHeader, "rpelcnt:");
    int nXSize, nYSize;
    if( sscanf(pszRPelCnt+strlen("rpelcnt:"),"%d,%d",&nXSize,&nYSize) != 2 ||
        nXSize <= 0 || nYSize <= 0 )
        return NULL;

    const char* pszOrient = strstr((const char*) poOpenInfo->pabyHeader, "rorient:");
    int nAngle1, nAngle2;
    if( sscanf(pszOrient+strlen("rorient:"),"%d,%d",&nAngle1,&nAngle2) != 2 )
        return NULL;

    const char* pszDensity = strstr((const char*) poOpenInfo->pabyHeader, "rdensty:");
    int nDensity = 0;
    if( pszDensity )
        sscanf(pszDensity+strlen("rdensty:"), "%d", &nDensity);

    VSIFSeekL(poOpenInfo->fpL, 0, SEEK_END);
    int nFAX4BlobSize = (int)VSIFTellL(poOpenInfo->fpL) - 2048;
    if( nFAX4BlobSize < 0 )
        return NULL;

    CALSDataset* poDS = new CALSDataset();
    poDS->nRasterXSize = nXSize;
    poDS->nRasterYSize = nYSize;

    // Create a TIFF header for a single-strip CCITTFAX4 file
    poDS->osTIFFHeaderFilename = CPLSPrintf("/vsimem/cals/header_%p.tiff", poDS);
    VSILFILE* fp = VSIFOpenL(poDS->osTIFFHeaderFilename, "wb");
    const int nTagCount = 10;
    const int nHeaderSize = 4 + 4 + 2 + nTagCount * 12 + 4;
    WriteLEInt16(fp, TIFF_LITTLEENDIAN); /* TIFF little-endian signature */
    WriteLEInt16(fp, 42); // TIFF classic

    WriteLEInt32(fp, 8);  /* Offset of IFD0 */

    WriteLEInt16(fp, nTagCount); /* Number of entries */

    WriteTIFFTAG(fp, TIFFTAG_IMAGEWIDTH, TIFF_LONG, nXSize);
    WriteTIFFTAG(fp, TIFFTAG_IMAGELENGTH, TIFF_LONG, nYSize);
    WriteTIFFTAG(fp, TIFFTAG_BITSPERSAMPLE, TIFF_SHORT, 1);
    WriteTIFFTAG(fp, TIFFTAG_COMPRESSION, TIFF_SHORT, COMPRESSION_CCITTFAX4);
    WriteTIFFTAG(fp, TIFFTAG_PHOTOMETRIC, TIFF_SHORT, PHOTOMETRIC_MINISWHITE);
    WriteTIFFTAG(fp, TIFFTAG_STRIPOFFSETS, TIFF_LONG, nHeaderSize);
    WriteTIFFTAG(fp, TIFFTAG_SAMPLESPERPIXEL, TIFF_SHORT, 1);
    WriteTIFFTAG(fp, TIFFTAG_ROWSPERSTRIP, TIFF_LONG, nYSize);
    WriteTIFFTAG(fp, TIFFTAG_STRIPBYTECOUNTS, TIFF_LONG, nFAX4BlobSize);
    WriteTIFFTAG(fp, TIFFTAG_PLANARCONFIG, TIFF_SHORT, PLANARCONFIG_CONTIG);

    WriteLEInt32(fp, 0);  /* Offset of next IFD */

    VSIFCloseL(fp);

    // Create a /vsisparse/ description file assembling the TIFF header
    // with the FAX4 codestream that starts at offset 2048 of the CALS file
    poDS->osSparseFilename = CPLSPrintf("/vsimem/cals/sparse_%p.xml", poDS);
    fp = VSIFOpenL(poDS->osSparseFilename, "wb");
    CPLAssert(fp);
    VSIFPrintfL(fp, "<VSISparseFile>"
                    "<Length>%d</Length>"
                    "<SubfileRegion>"
                      "<Filename relative='0'>%s</Filename>"
                      "<DestinationOffset>0</DestinationOffset>"
                      "<SourceOffset>0</SourceOffset>"
                      "<RegionLength>%d</RegionLength>"
                    "</SubfileRegion>"
                    "<SubfileRegion>"
                      "<Filename relative='0'>%s</Filename>"
                      "<DestinationOffset>%d</DestinationOffset>"
                      "<SourceOffset>%d</SourceOffset>"
                      "<RegionLength>%d</RegionLength>"
                    "</SubfileRegion>"
                    "</VSISparseFile>",
                nHeaderSize + nFAX4BlobSize,
                poDS->osTIFFHeaderFilename.c_str(),
                nHeaderSize,
                poOpenInfo->pszFilename,
                nHeaderSize,
                2048,
                nFAX4BlobSize);
    VSIFCloseL(fp);

    poDS->poUnderlyingDS = (GDALDataset*) GDALOpenEx(
        CPLSPrintf("/vsisparse/%s", poDS->osSparseFilename.c_str()),
        GDAL_OF_RASTER | GDAL_OF_INTERNAL, NULL, NULL, NULL);
    if( poDS->poUnderlyingDS == NULL )
    {
        delete poDS;
        return NULL;
    }

    if( nAngle1 != 0 || nAngle2 != 270 )
    {
        poDS->SetMetadataItem("PIXEL_PATH", CPLSPrintf("%d", nAngle1));
        poDS->SetMetadataItem("LINE_PROGRESSION", CPLSPrintf("%d", nAngle2));
    }

    if( nDensity != 0 )
    {
        poDS->SetMetadataItem("TIFFTAG_XRESOLUTION", CPLSPrintf("%d", nDensity));
        poDS->SetMetadataItem("TIFFTAG_YRESOLUTION", CPLSPrintf("%d", nDensity));
        poDS->SetMetadataItem("TIFFTAG_RESOLUTIONUNIT", "2 (pixels/inch)");
    }

    poDS->SetBand(1, new CALSRasterBand(poDS));

/* -------------------------------------------------------------------- */
/*      Initialize any PAM information.                                 */
/* -------------------------------------------------------------------- */
    poDS->SetDescription( poOpenInfo->pszFilename );
    poDS->TryLoadXML( poOpenInfo->GetSiblingFiles() );

/* -------------------------------------------------------------------- */
/*      Open overviews.                                                 */
/* -------------------------------------------------------------------- */
    poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename,
                                 poOpenInfo->GetSiblingFiles() );

    return( poDS );
}

/************************************************************************/
/*                             CreateCopy()                             */
/************************************************************************/

GDALDataset *CALSDataset::CreateCopy( const char *pszFilename,
                                      GDALDataset *poSrcDS,
                                      int bStrict,
                                      char ** /* papszOptionsUnused */,
                                      GDALProgressFunc pfnProgress,
                                      void *pProgressData )
{
    if( poSrcDS->GetRasterCount() == 0 ||
        (bStrict && poSrcDS->GetRasterCount() != 1) )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "CALS driver only supports single band raster.");
        return NULL;
    }
    if( poSrcDS->GetRasterBand(1)->GetMetadataItem("NBITS", "IMAGE_STRUCTURE") == NULL ||
        !EQUAL(poSrcDS->GetRasterBand(1)->GetMetadataItem("NBITS", "IMAGE_STRUCTURE"), "1") )
    {
        CPLError( (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
                  "CALS driver only supports 1-bit.");
        if( bStrict )
            return NULL;
    }

    if( poSrcDS->GetRasterXSize() > 999999 ||
        poSrcDS->GetRasterYSize() > 999999 )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "CALS driver only supports datasets with dimension <= 999999.");
        return NULL;
    }

    GDALDriver* poGTiffDrv = (GDALDriver*)GDALGetDriverByName("GTiff");
    if( poGTiffDrv == NULL )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "CALS driver needs GTiff driver.");
        return NULL;
    }

    /* Write a in-memory TIFF with just the TIFF header to figure out */
    /* how large it will be */
    CPLString osTmpFilename(CPLSPrintf("/vsimem/cals/tmp_%p", poSrcDS));
    char** papszOptions = NULL;
    papszOptions = CSLSetNameValue(papszOptions, "COMPRESS", "CCITTFAX4");
    papszOptions = CSLSetNameValue(papszOptions, "NBITS", "1");
    papszOptions = CSLSetNameValue(papszOptions, "BLOCKYSIZE",
                                   CPLSPrintf("%d", poSrcDS->GetRasterYSize()));
    papszOptions = CSLSetNameValue(papszOptions, "SPARSE_OK", "YES");
    GDALDataset* poDS = poGTiffDrv->Create(osTmpFilename,
                                           poSrcDS->GetRasterXSize(),
                                           poSrcDS->GetRasterYSize(),
                                           1, GDT_Byte,
                                           papszOptions);
    if( poDS == NULL )
    {
        // Should not happen normally (except if CCITTFAX4 not available)
        CSLDestroy(papszOptions);
        return NULL;
    }
    const char INITIAL_PADDING[] = "12345";
    poDS->SetMetadataItem("TIFFTAG_DOCUMENTNAME", INITIAL_PADDING); // to adjust padding
    GDALClose(poDS);
    VSIStatBufL sStat;
    if( VSIStatL(osTmpFilename, &sStat) != 0 )
    {
        // Shoudln't happen really... Just to make Coverity happy
        CSLDestroy(papszOptions);
        return NULL;
    }
    int nTIFFHeaderSize = static_cast<int>(sStat.st_size);
    VSIUnlink(osTmpFilename);


    /* Redo the same thing, but this time write it to the output file */
    /* and use a variable TIFF tag (TIFFTAG_DOCUMENTNAME) to enlarge the */
    /* header + the variable TIFF tag so that they are 2048 bytes large */
    char szBuffer[2048+1];
    szBuffer[2048] = 0;
    memset(szBuffer, 'X', 2048 - nTIFFHeaderSize + strlen(INITIAL_PADDING));
    szBuffer[2048 - nTIFFHeaderSize + strlen(INITIAL_PADDING)] = 0;
    GDALDataset* poTmpDS = new CALSWrapperSrcDataset(poSrcDS, szBuffer);
    poDS = poGTiffDrv->CreateCopy(pszFilename, poTmpDS, FALSE, papszOptions,
                                  pfnProgress, pProgressData );
    delete poTmpDS;
    CSLDestroy(papszOptions);
    if( poDS == NULL )
        return NULL;
    delete poDS;

    // Now replace the TIFF header by the CALS header !
    VSILFILE* fp = VSIFOpenL(pszFilename, "rb+");
    if( fp == NULL )
        return NULL; // Shoudln't happen normally
    memset(szBuffer, ' ', 2048);
    CPLString osField;
    osField = "srcdocid: NONE";
    memcpy(szBuffer, osField, osField.size());

    osField = "dstdocid: NONE";
    memcpy(szBuffer + 128, osField, osField.size());

    osField = "txtfilid: NONE";
    memcpy(szBuffer + 128*2, osField, osField.size());

    osField = "figid: NONE";
    memcpy(szBuffer + 128*3, osField, osField.size());

    osField = "srcgph: NONE";
    memcpy(szBuffer + 128*4, osField, osField.size());

    osField = "doccls: NONE";
    memcpy(szBuffer + 128*5, osField, osField.size());

    osField = "rtype: 1";
    memcpy(szBuffer + 128*6, osField, osField.size());

    int nAngle1 = 0, nAngle2 = 270;
    const char* pszPixelPath = poSrcDS->GetMetadataItem("PIXEL_PATH");
    const char* pszLineProgression = poSrcDS->GetMetadataItem("LINE_PROGRESSION");
    if( pszPixelPath && pszLineProgression )
    {
        nAngle1 = atoi(pszPixelPath);
        nAngle2 = atoi(pszLineProgression);
    }
    osField = CPLSPrintf("rorient: %03d,%03d", nAngle1, nAngle2);
    memcpy(szBuffer + 128*7, osField, osField.size());

    osField = CPLSPrintf("rpelcnt: %06d,%06d",
                         poSrcDS->GetRasterXSize(),
                         poSrcDS->GetRasterYSize());
    memcpy(szBuffer + 128*8, osField, osField.size());

    int nDensity = 200;
    const char* pszXRes = poSrcDS->GetMetadataItem("TIFFTAG_XRESOLUTION");
    const char* pszYRes = poSrcDS->GetMetadataItem("TIFFTAG_YRESOLUTION");
    const char* pszResUnit = poSrcDS->GetMetadataItem("TIFFTAG_RESOLUTIONUNIT");
    if( pszXRes && pszYRes && pszResUnit && EQUAL(pszXRes, pszYRes) &&
        atoi(pszResUnit) == 2 )
    {
        nDensity = atoi(pszXRes);
        if( nDensity < 1 || nDensity > 9999 )
            nDensity = 200;
    }
    osField = CPLSPrintf("rdensty: %04d", nDensity);
    memcpy(szBuffer + 128*9, osField, osField.size());

    osField = "notes: NONE";
    memcpy(szBuffer + 128*10, osField, osField.size());
    VSIFWriteL(szBuffer, 1, 2048, fp);
    VSIFCloseL(fp);

    GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly, NULL);
    return Open(&oOpenInfo);
}

/************************************************************************/
/*                        GDALRegister_CALS()                           */
/************************************************************************/

void GDALRegister_CALS()

{
    if( GDALGetDriverByName( "CALS" ) != NULL )
        return;

    GDALDriver *poDriver = new GDALDriver();

    poDriver->SetDescription( "CALS" );
    poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
    poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
                               "CALS (Type 1)" );
    poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
                               "frmt_cals.html" );

    poDriver->SetMetadataItem( GDAL_DMD_EXTENSIONS, ".cal .ct1");

    poDriver->pfnIdentify = CALSDataset::Identify;
    poDriver->pfnOpen = CALSDataset::Open;
    poDriver->pfnCreateCopy = CALSDataset::CreateCopy;

    GetGDALDriverManager()->RegisterDriver( poDriver );
}
