/******************************************************************************
 * $Id: sgidataset.cpp 10646 2007-01-18 02:38:10Z warmerdam $
 *
 * Project:  SGI Image Driver
 * Purpose:  Implement SGI Image Support based on Paul Bourke's SGI Image code.
 *           http://astronomy.swin.edu.au/~pbourke/dataformats/sgirgb/
 *           ftp://ftp.sgi.com/graphics/SGIIMAGESPEC
 * Authors:  Mike Mazzella (GDAL driver)
 *           Paul Bourke (original SGI format code)
 *
 ******************************************************************************
 * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.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 "cpl_port.h"
#include "cpl_string.h"

CPL_CVSID("$Id: sgidataset.cpp 10646 2007-01-18 02:38:10Z warmerdam $");
CPL_C_START
void	GDALRegister_SGI(void);
CPL_C_END

struct ImageRec
{
    GUInt16 imagic;
    GByte type;
    GByte bpc;
    GUInt16 dim;
    GUInt16 xsize;
    GUInt16 ysize;
    GUInt16 zsize;
    GUInt32 min;
    GUInt32 max;
    char wasteBytes[4];
    char name[80];
    GUInt32 colorMap;

    FILE* file;
    std::string fileName;
    unsigned char* tmp;
    unsigned char* tmpR;
    unsigned char* tmpG;
    unsigned char* tmpB;
    GUInt32 rleEnd;
    GUInt32* rowStart;
    GInt32* rowSize;

    ImageRec()
            : imagic(0),
              type(0),
              bpc(1),
              dim(0),
              xsize(0),
              ysize(0),
              zsize(0),
              min(0),
              max(0),
              colorMap(0),
              file(NULL),
              fileName(""),
              tmp(NULL),
              tmpR(NULL),
              tmpG(NULL),
              tmpB(NULL),
              rleEnd(0),
              rowStart(NULL),
              rowSize(NULL)
        {
            memset(wasteBytes, 0, 4);
            memset(name, 0, 80);
        }

    void Swap()
        {
#ifdef CPL_LSB
            CPL_SWAP16PTR(&imagic);
            CPL_SWAP16PTR(&dim);
            CPL_SWAP16PTR(&xsize);
            CPL_SWAP16PTR(&ysize);
            CPL_SWAP16PTR(&zsize);
            CPL_SWAP32PTR(&min);
            CPL_SWAP32PTR(&max);
#endif
        }
};

/************************************************************************/
/*                            ConvertLong()                             */
/************************************************************************/
static void ConvertLong(GUInt32* array, GInt32 length) 
{
#ifdef CPL_LSB
   GUInt32* ptr;
   ptr = (GUInt32*)array;
   while(length--)
     CPL_SWAP32PTR(ptr++);
#endif
}

/************************************************************************/
/*                            ImageGetRow()                             */
/************************************************************************/
static void ImageGetRow(ImageRec* image, unsigned char* buf, int y, int z) 
{
    unsigned char *iPtr, *oPtr, pixel;
    int count;

    y = image->ysize - 1 - y;

    if(int(image->type) == 1)
    {
        // reads row
        VSIFSeekL(image->file, (long)image->rowStart[y+z*image->ysize], SEEK_SET);
        if(VSIFReadL(image->tmp, 1, (GUInt32)image->rowSize[y+z*image->ysize], image->file) != (GUInt32)image->rowSize[y+z*image->ysize])
        {
            CPLError(CE_Failure, CPLE_OpenFailed, "file read error: row (%d) of (%s)\n", y, image->fileName.empty() ? "none" : image->fileName.c_str());
            return;
        }

        // expands row
        iPtr = image->tmp;
        oPtr = buf;
        int xsizeCount = 0;
        for(;;)
        {
            pixel = *iPtr++;
            count = (int)(pixel & 0x7F);
            if(!count)
            {
                if(xsizeCount != image->xsize)
                    CPLError(CE_Failure, CPLE_OpenFailed, "file read error: row (%d) of (%s)\n", y, image->fileName.empty() ? "none" : image->fileName.c_str());
                return;
            }
            if(pixel & 0x80)
            {
	      memcpy(oPtr, iPtr, count);
	      iPtr += count;
            }
            else
            {
                pixel = *iPtr++;
		memset(oPtr, pixel, count);
            }
	    oPtr += count;
	    xsizeCount += count;
        }
    }
    else
    {
        VSIFSeekL(image->file, 512+(y*image->xsize)+(z*image->xsize*image->ysize), SEEK_SET);
        if(VSIFReadL(buf, 1, image->xsize, image->file) != image->xsize)
        {
            CPLError(CE_Failure, CPLE_OpenFailed, "file read error: row (%d) of (%s)\n", y, image->fileName.empty() ? "none" : image->fileName.c_str());
            return;
        }
    }
}

/************************************************************************/
/* ==================================================================== */
/*				SGIDataset				*/
/* ==================================================================== */
/************************************************************************/

class SGIRasterBand;

class SGIDataset : public GDALPamDataset
{
    friend class SGIRasterBand;

    FILE*  fpImage;

    int	   bGeoTransformValid;
    double adfGeoTransform[6];

    char** papszMetadata;
    char** papszSubDatasets;

    ImageRec image;

public:
    SGIDataset();
    ~SGIDataset();

    virtual CPLErr GetGeoTransform(double*);
    static GDALDataset* Open(GDALOpenInfo*);
};

/************************************************************************/
/* ==================================================================== */
/*                            SGIRasterBand                             */
/* ==================================================================== */
/************************************************************************/

class SGIRasterBand : public GDALPamRasterBand
{
    friend class SGIDataset;

public:
    SGIRasterBand(SGIDataset*, int);

    virtual CPLErr IReadBlock(int, int, void*);
    virtual GDALColorInterp GetColorInterpretation();
};


/************************************************************************/
/*                           SGIRasterBand()                            */
/************************************************************************/

SGIRasterBand::SGIRasterBand(SGIDataset* poDS, int nBand)

{
  this->poDS = poDS;
  this->nBand = nBand;
  if(poDS == NULL)
  {
    eDataType = GDT_Byte;
  }
  else
  {
    if(int(poDS->image.bpc) == 1)
      eDataType = GDT_Byte;
    else
      eDataType = GDT_Int16;
  }
  nBlockXSize = poDS->nRasterXSize;;
  nBlockYSize = 1;
}

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

CPLErr SGIRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
				 void*  pImage)

{
    SGIDataset* poGDS = (SGIDataset*) poDS;
    
    CPLAssert(nBlockXOff == 0);
    if(nBlockXOff != 0)
    {
        printf("ERROR:  unhandled block value\n");
        exit(0);
    }

/* -------------------------------------------------------------------- */
/*      Load the desired data into the working buffer.              */
/* -------------------------------------------------------------------- */
    ImageGetRow(&(poGDS->image), (unsigned char*)pImage, nBlockYOff, nBand-1);

    return CE_None;
}

/************************************************************************/
/*                       GetColorInterpretation()                       */
/************************************************************************/

GDALColorInterp SGIRasterBand::GetColorInterpretation()

{
    SGIDataset* poGDS = (SGIDataset*)poDS;

    if(poGDS->nBands == 1)
        return GCI_GrayIndex;
    else if(poGDS->nBands == 2)
    {
        if(nBand == 1)
            return GCI_GrayIndex;
        else
            return GCI_AlphaBand;
    }
    else if(poGDS->nBands == 3)
    {
        if(nBand == 1)
            return GCI_RedBand;
        else if(nBand == 2)
            return GCI_GrayIndex;
        else
            return GCI_BlueBand;
    }
    else if(poGDS->nBands == 4)
    {
        if(nBand == 1)
            return GCI_RedBand;
        else if(nBand == 2)
            return GCI_GrayIndex;
        else if(nBand == 3)
            return GCI_BlueBand;
        else
            return GCI_AlphaBand;
    }
    return GCI_Undefined;
}

/************************************************************************/
/* ==================================================================== */
/*                             SGIDataset                               */
/* ==================================================================== */
/************************************************************************/


/************************************************************************/
/*                            SGIDataset()                              */
/************************************************************************/

SGIDataset::SGIDataset()
  : fpImage(NULL),
    bGeoTransformValid(FALSE),
    papszMetadata(NULL),
    papszSubDatasets(NULL)
{
    adfGeoTransform[0] = 0.0;
    adfGeoTransform[1] = 1.0;
    adfGeoTransform[2] = 0.0;
    adfGeoTransform[3] = 0.0;
    adfGeoTransform[4] = 0.0;
    adfGeoTransform[5] = 1.0;
}

/************************************************************************/
/*                           ~SGIDataset()                            */
/************************************************************************/

SGIDataset::~SGIDataset()

{
    FlushCache();

    if(fpImage != NULL)
        VSIFCloseL(fpImage);

    if(papszMetadata != NULL) CSLDestroy(papszMetadata);

    if(image.tmp != NULL) CPLFree(image.tmp);
    if(image.tmpR != NULL) CPLFree(image.tmpR);
    if(image.tmpG != NULL) CPLFree(image.tmpG);
    if(image.tmpB != NULL) CPLFree(image.tmpB);
    if(image.rowSize != NULL) CPLFree(image.rowSize);
    if(image.rowStart != NULL) CPLFree(image.rowStart);
}

/************************************************************************/
/*                          GetGeoTransform()                           */
/************************************************************************/

CPLErr SGIDataset::GetGeoTransform(double * padfTransform)

{
    if(bGeoTransformValid)
    {
        memcpy(padfTransform, adfGeoTransform, sizeof(double)*6);
        
        return CE_None;
    }
    else 
        return GDALPamDataset::GetGeoTransform(padfTransform);
}

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

GDALDataset* SGIDataset::Open(GDALOpenInfo* poOpenInfo)

{
/* -------------------------------------------------------------------- */
/*	First we check to see if the file has the expected header	*/
/*	bytes.								*/    
/* -------------------------------------------------------------------- */
    if(poOpenInfo->nHeaderBytes < 12)
        return NULL;

    ImageRec tmpImage;
    memcpy(&tmpImage, poOpenInfo->pabyHeader, 12);
    tmpImage.Swap();

    if(tmpImage.imagic != 474)
        return NULL;

    if(tmpImage.bpc != 1)
    {
        CPLError(CE_Failure, CPLE_NotSupported, 
                 "The SGI driver only supports 1 byte channel values.\n");
        return NULL;
    }
  
    if(poOpenInfo->eAccess == GA_Update)
    {
        CPLError(CE_Failure, CPLE_NotSupported, 
                 "The SGI driver does not support update access to existing"
                 " datasets.\n");
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Create a corresponding GDALDataset.                             */
/* -------------------------------------------------------------------- */
    SGIDataset* poDS;

    poDS = new SGIDataset();

/* -------------------------------------------------------------------- */
/*      Open the file using the large file api.                         */
/* -------------------------------------------------------------------- */
    poDS->fpImage = VSIFOpenL(poOpenInfo->pszFilename, "rb");
    if(poDS->fpImage == NULL)
    {
        CPLError(CE_Failure, CPLE_OpenFailed, "VSIFOpenL(%s) failed unexpectedly in sgidataset.cpp", poOpenInfo->pszFilename);
        return NULL;
    }
    // printf("----------- %s\n", poOpenInfo->pszFilename);

    poDS->eAccess = GA_ReadOnly;

/* -------------------------------------------------------------------- */
/*	Read pre-image data after ensuring the file is rewound.         */
/* -------------------------------------------------------------------- */
    VSIFSeekL(poDS->fpImage, 0, SEEK_SET);
    if(VSIFReadL((void*)(&(poDS->image)), 1, 12, poDS->fpImage) != 12)
    {
        CPLError(CE_Failure, CPLE_OpenFailed, "file read error while reading header in sgidataset.cpp");
        return NULL;
    }
    poDS->image.Swap();
    poDS->image.file = poDS->fpImage;
    poDS->image.fileName = poOpenInfo->pszFilename;

/* -------------------------------------------------------------------- */
/*      Capture some information from the file that is of interest.     */
/* -------------------------------------------------------------------- */
    poDS->nRasterXSize = poDS->image.xsize;
    poDS->nRasterYSize = poDS->image.ysize;
    poDS->nBands = poDS->image.zsize;
    int numItems = (int(poDS->image.bpc) == 1) ? 256 : 65536;
    poDS->image.tmp = (unsigned char*)CPLMalloc(poDS->image.xsize*numItems);
    poDS->image.tmpR = (unsigned char*)CPLMalloc(poDS->image.xsize*numItems);
    poDS->image.tmpG = (unsigned char*)CPLMalloc(poDS->image.xsize*numItems);
    poDS->image.tmpB = (unsigned char*)CPLMalloc(poDS->image.xsize*numItems);
    if((poDS->image.tmp == NULL) || (poDS->image.tmpR == NULL) ||
       (poDS->image.tmpG == NULL) || (poDS->image.tmpB == NULL))
    {
      if(poDS->image.tmp != NULL) {CPLFree(poDS->image.tmp); poDS->image.tmp = NULL;}
      if(poDS->image.tmpR != NULL) {CPLFree(poDS->image.tmpR); poDS->image.tmpR = NULL;}
      if(poDS->image.tmpG != NULL) {CPLFree(poDS->image.tmpG); poDS->image.tmpG = NULL;}
      if(poDS->image.tmpB != NULL) {CPLFree(poDS->image.tmpB); poDS->image.tmpB = NULL;}
      CPLError(CE_Failure, CPLE_OpenFailed, "ran out of memory in sgidataset.cpp");
      return NULL;
    }
    memset(poDS->image.tmp, 0, poDS->image.xsize*numItems);
    memset(poDS->image.tmpR, 0, poDS->image.xsize*numItems);
    memset(poDS->image.tmpG, 0, poDS->image.xsize*numItems);
    memset(poDS->image.tmpB, 0, poDS->image.xsize*numItems);

    if(int(poDS->image.type) == 1)
    {
        int x = poDS->image.ysize * poDS->image.zsize * sizeof(GUInt32);
        poDS->image.rowStart = (GUInt32*)CPLMalloc(x);
        poDS->image.rowSize = (GInt32*)CPLMalloc(x);
        memset(poDS->image.rowStart, 0, x);
        memset(poDS->image.rowSize, 0, x);
        if(poDS->image.rowStart == NULL || poDS->image.rowSize == NULL)
        {
	  if(poDS->image.tmp != NULL) {CPLFree(poDS->image.tmp); poDS->image.tmp = NULL;}
	  if(poDS->image.tmpR != NULL) {CPLFree(poDS->image.tmpR); poDS->image.tmpR = NULL;}
	  if(poDS->image.tmpG != NULL) {CPLFree(poDS->image.tmpG); poDS->image.tmpG = NULL;}
	  if(poDS->image.tmpB != NULL) {CPLFree(poDS->image.tmpB); poDS->image.tmpB = NULL;}
	  CPLError(CE_Failure, CPLE_OpenFailed, "ran out of memory in sgidataset.cpp");
	  return NULL;
        }
        poDS->image.rleEnd = 512 + (2 * x);
        VSIFSeekL(poDS->fpImage, 512, SEEK_SET);
        if((int) VSIFReadL(poDS->image.rowStart, 1, x, poDS->image.file) != x)
        {
            CPLError(CE_Failure, CPLE_OpenFailed, "file read error while reading start positions in sgidataset.cpp");
            return NULL;
        }
        if((int) VSIFReadL(poDS->image.rowSize, 1, x, poDS->image.file) != x)
        {
            CPLError(CE_Failure, CPLE_OpenFailed, "file read error while reading row lengths in sgidataset.cpp");
            return NULL;
        }
        ConvertLong(poDS->image.rowStart, x/(int)sizeof(GUInt32));
        ConvertLong((GUInt32*)poDS->image.rowSize, x/(int)sizeof(GInt32));
    }
    else
    {
        poDS->image.rowStart = NULL;
        poDS->image.rowSize = NULL;
    }

/* -------------------------------------------------------------------- */
/*      Create band information objects.                                */
/* -------------------------------------------------------------------- */
    for(int iBand = 0; iBand < poDS->nBands; iBand++)
        poDS->SetBand(iBand+1, new SGIRasterBand(poDS, iBand+1));

/* -------------------------------------------------------------------- */
/*      Check for world file.                                           */
/* -------------------------------------------------------------------- */
    poDS->bGeoTransformValid = 
        GDALReadWorldFile(poOpenInfo->pszFilename, ".wld", 
                          poDS->adfGeoTransform);

/* -------------------------------------------------------------------- */
/*      Check for overviews.                                            */
/* -------------------------------------------------------------------- */
    poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename );

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

    return poDS;
}

/************************************************************************/
/*                         GDALRegister_SGI()                          */
/************************************************************************/

void GDALRegister_SGI()

{
    GDALDriver*  poDriver;

    if(GDALGetDriverByName("SGI") == NULL)
    {
        poDriver = new GDALDriver();
        
        poDriver->SetDescription("SGI");
        poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, 
                                  "SGI Image File Format 1.0");
        poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "rgb");
        poDriver->SetMetadataItem(GDAL_DMD_MIMETYPE, "image/rgb");
        poDriver->pfnOpen = SGIDataset::Open;
        GetGDALDriverManager()->RegisterDriver(poDriver);
    }
}

