/**************************************************************************
 *{@C
 *      Copyright:      1988-2020 Paul Obermeier (obermeier@poSoft.de)
 *
 *      Module:         FileFormats
 *      Filename:       FF_ImageSgiLib.c
 *
 *      Author:         Paul Obermeier
 *
 *      Description:    The original SGI library for reading and writing their
 *                      native file format.
 *
 *      Additional documentation:
 *                      None.
 *
 *      Exported functions:
 *
 **************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "UT_Compat.h"
#include "UT_Error.h"
#include "UT_FileIO.h"
#include "UT_Portable.h"
#include "FF_ImagePrivate.h"
#include "FF_ImageSgiLib.h"

#if !defined (_IOWRT)
    #define _IOWRT  1
#endif
#if !defined (_IOREAD)
    #define _IOREAD 2
#endif
#if !defined (_IORW)
    #define _IORW   4
#endif
#if !defined (_IOERR)
    #define _IOERR  8
#endif
#if !defined (_IOEOF)
    #define _IOEOF 16
#endif

static void (*i_errfunc)();
static unsigned short *ibufalloc(IMAGE *image);
static unsigned int img_optseek(IMAGE *image, unsigned int offset);
static IMAGE *imgopen(int, const char *, const char *,unsigned int, unsigned int,
                unsigned int, unsigned int, unsigned int);
static int img_write(IMAGE *image, char *buffer,int count);
static int img_badrow(IMAGE *image, unsigned int y, unsigned int z);

/*      error handler for the image library.  If the iseterror() routine
        has been called, sprintf's the args into a string and calls the
        error function.  Otherwise calls fprintf with the args and then
        exit.  This allows 'old' programs to assume that no errors
        ever need be worried about, while programs that know how and
        want to can handle the errors themselves.  Olson, 11/88
*/
static void i_errhdlr(fmt) 
char *fmt;
{
        if(i_errfunc) {
                char ebuf[2048];        /* be generous; if an error includes a
                        pathname, the maxlen is 1024, so we shouldn't ever 
                        overflow this! */
                sprintf(ebuf, "%s", fmt);
                (*i_errfunc)(ebuf);
                return;
        }

        /*
        fprintf(stderr, fmt);
        */

        return;
}

/*
 *      isetname 
 *
 *                              Paul Haeberli - 1984
 *
 */

static void isetname(IMAGE *image, char *name)
{
    strncpy(image->name,name,80);
}

static void cvtshorts( buffer, n)
register unsigned short buffer[];
register int n;
{
    register short i;
    register int nshorts = n>>1;
    register unsigned short swrd;

    for(i=0; i<nshorts; i++) {
        swrd = *buffer;
        *buffer++ = (swrd>>8) | (swrd<<8);
    }
}

static void cvtlongs( buffer, n)
register int buffer[];
register int n;
{
    register short i;
    register int nlongs = n>>2;
    register unsigned int lwrd;

    for(i=0; i<nlongs; i++) {
        lwrd = buffer[i];
        buffer[i] =     ((lwrd>>24)             | 
                        ((lwrd>>8) & 0xff00)    |
                        ((lwrd<<8) & 0xff0000)  |
                        (lwrd<<24)              );
    }
}

static void cvtimage( buffer )
register int buffer[];
{
    cvtshorts((unsigned short *)buffer,12);
    cvtlongs(buffer+3,12);
}

/*
 *      iopen -
 *
 *                              Paul Haeberli - 1984
 *
 */

IMAGE *sgiOpen(const char *file, const char *mode, unsigned int type, unsigned int dim,
               unsigned int xsize, unsigned int ysize, unsigned int zsize)
{
    return(imgopen(0, file, mode, type, dim, xsize, ysize, zsize));
}

static IMAGE *imgopen(int f, const char *file, const char *mode,
                unsigned int type, unsigned int dim,
                unsigned int xsize, unsigned int ysize, unsigned int zsize)
{
    register IMAGE  *image;
    int tablesize;
    register int i, max;
    FILE *fp;

    image = (IMAGE*)calloc(1,sizeof(IMAGE));
    if(!image ) {
        UT_ErrSetNum (UT_ErrFromOs(), str_no_memory);
        return NULL;
    }

    if (mode[0] == 'w') {
        fp = UT_FileOpen (file, "wb");
        if (!fp) {
            free (image);
            return NULL;
        }
        image->imagic = IMAGIC;
        image->type = type;
        image->xsize = xsize;
        image->ysize = 1;
        image->zsize = 1;
        if (dim>1)
            image->ysize = ysize;
        if (dim>2)
            image->zsize = zsize;
        if(image->zsize == 1) {
            image->dim = 2;
            if(image->ysize == 1)
                image->dim = 1;
        } else {
            image->dim = 3;
        }
        image->min = 10000000;
        image->max = 0;
        isetname(image,"no name"); 
        image->wastebytes = 0;
        /* If we are on an Intel architecture, revert byte order.
         * Some image programs (like IrfanView) only accept Motorola byte order.
         */
        image->dorev = UT_PortIsIntel();
        if (fwrite(image, sizeof(IMAGE), 1, fp) != 1) {
            i_errhdlr("iopen: error on write of image header\n");
            UT_FileClose (fp);
            free (image);
            return NULL;
        }
    } else {
        fp = UT_FileOpen (file, "rb");
        if (!fp) {
            UT_ErrAppendStr (" - ", str_open_read, file);
            free (image);
            return NULL;
        }
        if (fread(image, sizeof(IMAGE), 1, fp) != 1) {
            UT_ErrSetNum (UT_ErrUnexpEof, "Read SGI header");
            UT_FileClose (fp);
            free (image);
            return NULL;
        }
        if( ((image->imagic>>8) | ((image->imagic&0xff)<<8)) == IMAGIC ) {
            image->dorev = 1;
            cvtimage((int *)image);
        } else {
            image->dorev = 0;
        }
        if (image->imagic != IMAGIC) {
            UT_ErrSetNum (FF_ImgErrFormat, str_magic_num, image->imagic, file);
            UT_FileClose (fp);
            free (image);
            return NULL;
        }
    }
    if (mode[0] == 'w') {
        image->flags = _IOWRT;
    } else {
        image->flags = _IOREAD;
    }
    if(ISRLE(image->type)) {
        tablesize = image->ysize*image->zsize*sizeof(int);
        image->rowstart = (unsigned int *)malloc(tablesize);
        image->rowsize = (int *)malloc(tablesize);
        if( image->rowstart == 0 || image->rowsize == 0 ) {
            i_errhdlr("iopen: error on table alloc\n");
            UT_FileClose (fp);
            return NULL;
        }
        image->rleend = 512L+2*tablesize;
        if (*mode=='w') {
            max = image->ysize*image->zsize;
            for(i=0; i<max; i++) {
                image->rowstart[i] = 0;
                image->rowsize[i] = -1;
            }
        } else {
            tablesize = image->ysize*image->zsize*sizeof(int);
            fseek(fp, 512L, 0);
            if (fread(image->rowstart, tablesize, 1, fp) != 1) {
                i_errhdlr("iopen: error on read of rowstart\n");
                UT_FileClose (fp);
                return NULL;
            }
            if(image->dorev)
                cvtlongs((int*)image->rowstart,tablesize);
            if (fread(image->rowsize, tablesize, 1, fp) != 1) {
                i_errhdlr("iopen: error on read of rowsize\n");
                UT_FileClose (fp);
                return NULL;
            }
            if(image->dorev)
                cvtlongs(image->rowsize,tablesize);
        }
    }
    image->cnt = 0;
    image->ptr = 0;
    image->base = 0;
    if( (image->tmpbuf = ibufalloc(image)) == 0 ) { 
        i_errhdlr("iopen: error on tmpbuf alloc\n");
        UT_FileClose (fp);
        return NULL;
    }
    image->x = image->y = image->z = 0;
    image->file = fp;
    image->offset = 512L;                   /* set up for img_optseek */
    fseek(image->file, 512L, 0);
    return(image);
}

static unsigned short *ibufalloc(IMAGE *image)
{
    return (unsigned short *)malloc(IBUFSIZE(image->xsize));
}

/*
 *      iclose and iflush -
 *
 *                              Paul Haeberli - 1984
 *
 */

static int iflush(IMAGE *image)
{
    unsigned short *base;

    if ( (image->flags&_IOWRT)
     && (base=image->base)!=NULL && (image->ptr-base)>0) {
            if (sgiPutrow(image, base, image->y,image->z)!=image->xsize) {
                    image->flags |= _IOERR;
                    return(EOF);
            }
    }
    return(0);
}

int sgiClose(IMAGE *image)
{
    int tablesize;

    iflush(image);
    img_optseek(image, 0);
    if (image->flags&_IOWRT) {
        if(image->dorev)
            cvtimage((int *)image);
        if (img_write(image,(char *)image,sizeof(IMAGE)) != 1) {
            i_errhdlr("iclose: error on write of image header\n");
            return EOF;
        }
        if(image->dorev)
            cvtimage((int *)image);
        if(ISRLE(image->type)) {
            img_optseek(image, 512L);
            tablesize = image->ysize*image->zsize*sizeof(int);
            if(image->dorev)
                cvtlongs((int *)image->rowstart,tablesize);
            if (img_write(image,(char *)(image->rowstart),tablesize) != 1) {
                i_errhdlr("iclose: error on write of rowstart\n");
                return EOF;
            }
            if(image->dorev)
                cvtlongs(image->rowsize,tablesize);
            if (img_write(image,(char *)(image->rowsize),tablesize) != 1) {
                i_errhdlr("iclose: error on write of rowsize\n");
                return EOF;
            }
        }
    }
    if(image->base) {
        free(image->base);
        image->base = 0;
    }
    if(image->tmpbuf) {
        free(image->tmpbuf);
        image->tmpbuf = 0;
    }
    if(ISRLE(image->type)) {
        if (image->rowstart) {
            free(image->rowstart);
            image->rowstart = 0;
        }
        if (image->rowsize) {
            free(image->rowsize);
            image->rowsize = 0;
        }
    }
    UT_FileClose(image->file);
    free (image);
    return 0;
}

/*
 *      img_seek, img_write, img_read, img_optseek -
 *
 *                              Paul Haeberli - 1984
 *
 */

static unsigned int img_seek(IMAGE *image, unsigned int y, unsigned int z)
{
    if(img_badrow(image,y,z)) {
        i_errhdlr("img_seek: row number out of range\n");
        return EOF;
    }
    image->x = 0;
    image->y = y;
    image->z = z;
    if(ISVERBATIM(image->type)) {
        switch(image->dim) {
            case 1:
                return img_optseek(image, 512L);
            case 2: 
                return img_optseek(image,512L+(y*image->xsize)*BPP(image->type));
            case 3: 
                return img_optseek(image,
                    512L+(y*image->xsize+z*image->xsize*image->ysize)*
                                                        BPP(image->type));
            default:
                i_errhdlr("img_seek: weird dim\n");
                break;
        }
    } else if(ISRLE(image->type)) {
        switch(image->dim) {
            case 1:
                return img_optseek(image, image->rowstart[0]);
            case 2: 
                return img_optseek(image, image->rowstart[y]);
            case 3: 
                return img_optseek(image, image->rowstart[y+z*image->ysize]);
            default:
                i_errhdlr("img_seek: weird dim\n");
                break;
        }
    } else 
        i_errhdlr("img_seek: weird image type\n");
    return((unsigned int)-1);
}

static int img_badrow(IMAGE *image, unsigned int y, unsigned int z)
{
    if(y>=image->ysize || z>=image->zsize)
        return 1;
    else
        return 0;
}

static int img_write(IMAGE *image, char *buffer,int count)
{
    int retval;

    retval =  fwrite(buffer, count, 1, image->file);
    if(retval == 1)
        image->offset += count;
    else
        image->offset = -1;
    return retval;
}

static int img_read(IMAGE *image, char *buffer, int count)
{
    int retval;

    retval =  fread(buffer, count, 1, image->file);
    if(retval == 1)
        image->offset += count;
    else
        image->offset = -1;
    return retval;
}

static unsigned int img_optseek(IMAGE *image, unsigned int offset)
{
    if(image->offset != offset) {
       image->offset = offset;
       return ((unsigned int) fseek(image->file,offset,0));
   }
   return offset;
}

/*
 *      img_getrowsize, img_setrowsize, img_rle_compact, img_rle_expand -
 *
 *                              Paul Haeberli - 1984
 *
 */

static int img_getrowsize(IMAGE *image)
{
    switch(image->dim) {
        case 1:
            return image->rowsize[0];
        case 2:
            return image->rowsize[image->y];
        case 3:
            return image->rowsize[image->y+image->z*image->ysize];
        default:
            i_errhdlr ("img_getrowsize: Unknown image dimension\n");
            return -1;
    }
}

static void img_setrowsize(IMAGE *image, int cnt, int y, int z)
{
    int *sizeptr = NULL;

    if(img_badrow(image,y,z)) 
        return;
    switch(image->dim) {
        case 1:
            sizeptr = &image->rowsize[0];
            image->rowstart[0] = image->rleend;
            break;
        case 2:
            sizeptr = &image->rowsize[y];
            image->rowstart[y] = image->rleend;
            break;
        case 3:
            sizeptr = &image->rowsize[y+z*image->ysize];
            image->rowstart[y+z*image->ysize] = image->rleend;
    }   
    if(*sizeptr != -1) 
        image->wastebytes += *sizeptr;
    *sizeptr = cnt;
    image->rleend += cnt;
}

#define docompact                                                       \
        while(iptr<ibufend) {                                           \
            sptr = iptr;                                                \
            iptr += 2;                                                  \
            while((iptr<ibufend)&&((iptr[-2]!=iptr[-1])||(iptr[-1]!=iptr[0])))\
                iptr++;                                                 \
            iptr -= 2;                                                  \
            count = iptr-sptr;                                          \
            while(count) {                                              \
                todo = count>126 ? 126:count;                           \
                count -= todo;                                          \
                *optr++ = 0x80|todo;                                    \
                while(todo--)                                           \
                    *optr++ = *sptr++;                                  \
            }                                                           \
            sptr = iptr;                                                \
            cc = *iptr++;                                               \
            while( (iptr<ibufend) && (*iptr == cc) )                    \
                iptr++;                                                 \
            count = iptr-sptr;                                          \
            while(count) {                                              \
                todo = count>126 ? 126:count;                           \
                count -= todo;                                          \
                *optr++ = todo;                                         \
                *optr++ = cc;                                           \
            }                                                           \
        }                                                               \
        *optr++ = 0;

static int img_rle_compact(unsigned short *expbuf, int ibpp,
                        unsigned short *rlebuf, int obpp, int cnt)
{
    if(ibpp == 1 && obpp == 1) {
        register unsigned char *iptr = (unsigned char *)expbuf;
        register unsigned char *ibufend = iptr+cnt;
        register unsigned char *sptr;
        register unsigned char *optr = (unsigned char *)rlebuf;
        register short todo, cc;
        register int count;

        docompact;
        return optr - (unsigned char *)rlebuf;
    } else if(ibpp == 1 && obpp == 2) {
        register unsigned char *iptr = (unsigned char *)expbuf;
        register unsigned char *ibufend = iptr+cnt;
        register unsigned char *sptr;
        register unsigned short *optr = rlebuf;
        register short todo, cc;
        register int count;

        docompact;
        return optr - rlebuf;
    } else if(ibpp == 2 && obpp == 1) {
        register unsigned short *iptr = expbuf;
        register unsigned short *ibufend = iptr+cnt;
        register unsigned short *sptr;
        register unsigned char *optr = (unsigned char *)rlebuf;
        register short todo, cc;
        register int count;

        docompact;
        return optr - (unsigned char *)rlebuf;
    } else if(ibpp == 2 && obpp == 2) {
        register unsigned short *iptr = expbuf;
        register unsigned short *ibufend = iptr+cnt;
        register unsigned short *sptr;
        register unsigned short *optr = rlebuf;
        register short todo, cc;
        register int count;

        docompact;
        return optr - rlebuf;
    } else  {
        i_errhdlr("rle_compact: bad bpp\n");
        return 0;
    }
}

#define doexpand                                \
        while(1) {                              \
            pixel = *iptr++;                    \
            if ( !(count = (pixel & 0x7f)) )    \
                return;                         \
            if(pixel & 0x80) {                  \
               while(count--)                   \
                    *optr++ = *iptr++;          \
            } else {                            \
               pixel = *iptr++;                 \
               while(count--)                   \
                    *optr++ = pixel;            \
            }                                   \
        }

static void img_rle_expand(unsigned short *rlebuf, int ibpp,
                        unsigned short *expbuf, int obpp)
{
    if(ibpp == 1 && obpp == 1) {
        register unsigned char *iptr = (unsigned char *)rlebuf;
        register unsigned char *optr = (unsigned char *)expbuf;
        register unsigned short pixel,count;

        doexpand;
    } else if(ibpp == 1 && obpp == 2) {
        register unsigned char *iptr = (unsigned char *)rlebuf;
        register unsigned short *optr = expbuf;
        register unsigned short pixel,count;

        doexpand;
    } else if(ibpp == 2 && obpp == 1) {
        register unsigned short *iptr = rlebuf;
        register unsigned char  *optr = (unsigned char *)expbuf;
        register unsigned short pixel,count;

        doexpand;
    } else if(ibpp == 2 && obpp == 2) {
        register unsigned short *iptr = rlebuf;
        register unsigned short *optr = expbuf;
        register unsigned short pixel,count;

        doexpand;
    } else 
        i_errhdlr("rle_expand: bad bpp\n");
}

/*
 *      sgiPutrow, sgiGetrow -
 *
 *                              Paul Haeberli - 1984
 *
 */

int sgiPutrow(IMAGE *image, unsigned short *buffer,
                unsigned int y, unsigned int z) 
{
    register unsigned short     *sptr;
    register unsigned char      *cptr;
    register unsigned int x;
    register unsigned int min, max;
    register int cnt;

    if( !(image->flags & (_IORW|_IOWRT)) )
        return -1;
    if(image->dim<3)
        z = 0;
    if(image->dim<2)
        y = 0;
    if(ISVERBATIM(image->type)) {
        switch(BPP(image->type)) {
            case 1: 
                min = image->min;
                max = image->max;
                cptr = (unsigned char *)image->tmpbuf;
                sptr = buffer;
                for(x=image->xsize; x--;) { 
                    *cptr = *sptr++;
                    if (*cptr > max) max = *cptr;
                    if (*cptr < min) min = *cptr;
                    cptr++;
                }
                image->min = min;
                image->max = max;
                img_seek(image,y,z);
                cnt = image->xsize;
                if (img_write(image,(char *)(image->tmpbuf),cnt) != 1)
                    return -1;
                else
                    return cnt;
                /* NOTREACHED */

            case 2: 
                min = image->min;
                max = image->max;
                sptr = buffer;
                for(x=image->xsize; x--;) { 
                    if (*sptr > max) max = *sptr;
                    if (*sptr < min) min = *sptr;
                    sptr++;
                }
                image->min = min;
                image->max = max;
                img_seek(image,y,z);
                cnt = image->xsize<<1;
                if(image->dorev)        
                    cvtshorts(buffer,cnt);
                if (img_write(image,(char *)(buffer),cnt) != 1) {
                    if(image->dorev)    
                        cvtshorts(buffer,cnt);
                    return -1;
                } else {
                    if(image->dorev)    
                        cvtshorts(buffer,cnt);
                    return image->xsize;
                }
                /* NOTREACHED */

            default:
                i_errhdlr("sgiPutrow: weird bpp\n");
        }
    } else if(ISRLE(image->type)) {
        switch(BPP(image->type)) {
            case 1: 
                min = image->min;
                max = image->max;
                sptr = buffer;
                for(x=image->xsize; x--;) { 
                    if (*sptr > max) max = *sptr;
                    if (*sptr < min) min = *sptr;
                    sptr++;
                }
                image->min = min;
                image->max = max;
                cnt = img_rle_compact(buffer,2,image->tmpbuf,1,image->xsize);
                img_setrowsize(image,cnt,y,z);
                img_seek(image,y,z);
                if (img_write(image,(char *)(image->tmpbuf),cnt) != 1)
                    return -1;
                else
                    return image->xsize;
                /* NOTREACHED */

            case 2: 
                min = image->min;
                max = image->max;
                sptr = buffer;
                for(x=image->xsize; x--;) { 
                    if (*sptr > max) max = *sptr;
                    if (*sptr < min) min = *sptr;
                    sptr++;
                }
                image->min = min;
                image->max = max;
                cnt = img_rle_compact(buffer,2,image->tmpbuf,2,image->xsize);
                cnt <<= 1;
                img_setrowsize(image,cnt,y,z);
                img_seek(image,y,z);
                if(image->dorev)
                    cvtshorts(image->tmpbuf,cnt);
                if (img_write(image,(char *)(image->tmpbuf),cnt) != 1) {
                    if(image->dorev)
                        cvtshorts(image->tmpbuf,cnt);
                    return -1;
                } else {
                    if(image->dorev)
                        cvtshorts(image->tmpbuf,cnt);
                    return image->xsize;
                }
                /* NOTREACHED */

            default:
                i_errhdlr("sgiPutrow: weird bpp\n");
        }
    } else 
        i_errhdlr("sgiPutrow: weird image type\n");
    return(-1);
}

int sgiGetrow(IMAGE *image, unsigned short *buffer,
                unsigned int y, unsigned int z) 
{
    register short i;
    register unsigned char *cptr;
    register unsigned short *sptr;
    register short cnt; 

    if( !(image->flags & (_IORW|_IOREAD)) )
        return -1;
    if(image->dim<3)
        z = 0;
    if(image->dim<2)
        y = 0;
    img_seek(image, y, z);
    if(ISVERBATIM(image->type)) {
        switch(BPP(image->type)) {
            case 1: 
                if (img_read(image,(char *)image->tmpbuf,image->xsize) 
                                                            != 1)
                    return -1;
                else {
                    cptr = (unsigned char *)image->tmpbuf;
                    sptr = buffer;
                    for(i=image->xsize; i--;)
                        *sptr++ = *cptr++;
                }
                return image->xsize;
                /* NOTREACHED */

            case 2: 
                cnt = image->xsize<<1; 
                if (img_read(image,(char *)(buffer),cnt) != 1)
                    return -1;
                else {
                    if(image->dorev)
                        cvtshorts(buffer,cnt);
                    return image->xsize;
                }
                /* NOTREACHED */

            default:
                i_errhdlr("sgiGetrow: weird bpp\n");
                break;
        }
    } else if(ISRLE(image->type)) {
        switch(BPP(image->type)) {
            case 1: 
                if( (cnt = img_getrowsize(image)) == -1 )
                    return -1;
                if( img_read(image,(char *)(image->tmpbuf),cnt) != 1 )
                    return -1;
                else {
                    img_rle_expand(image->tmpbuf,1,buffer,2);
                    return image->xsize;
                }
                /* NOTREACHED */

            case 2: 
                if( (cnt = img_getrowsize(image)) == -1 )
                    return -1;
                if( 1 != img_read(image,(char *)(image->tmpbuf),cnt) )
                    return -1;
                else {
                    if(image->dorev)
                        cvtshorts(image->tmpbuf,cnt);
                    img_rle_expand(image->tmpbuf,2,buffer,2);
                    return image->xsize;
                }
                /* NOTREACHED */

            default:
                i_errhdlr("sgiGetrow: weird bpp\n");
                break;
        }
    } else 
        i_errhdlr("sgiGetrow: weird image type\n");
    return -1;
}
