/* libpbm1.c - pbm utility library part 1
**
** Copyright (C) 1988 by Jef Poskanzer.
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted, provided
** that the above copyright notice appear in all copies and that both that
** copyright notice and this permission notice appear in supporting
** documentation.  This software is provided "as is" without express or
** implied warranty.
*/

#define _POSIX_SOURCE
    /* This makes sure fileno() is in stdio.h */

#include <stdio.h>
#include "pbm.h"
#include "version.h"
#include "../compile.h"
#include "libpbm.h"
#include "shhopt.h"

#if __STDC__
#include <stdarg.h>
#else /*__STDC__*/
#include <varargs.h>
#endif /*__STDC__*/
#include <string.h>
#include <errno.h>

/* Variable-sized arrays. */

char*
pm_allocrow( cols, size )
    int cols;
    int size;
    {
    register char* itrow;

    itrow = (char*) malloc( cols * size );
    if ( itrow == (char*) 0 )
        pm_error( "out of memory allocating a row" );
    return itrow;
    }

void
pm_freerow( itrow )
    char* itrow;
    {
    free( itrow );
    }


#ifndef A_FRAGARRAY
char**
pm_allocarray( cols, rows, size )
    int cols, rows;
    int size;
    {
    char** its;
    int i;

    its = (char**) malloc( rows * sizeof(char*) );
    if ( its == (char**) 0 )
        pm_error( "out of memory allocating an array" );
    its[0] = (char*) malloc( rows * cols * size );
    if ( its[0] == (char*) 0 )
        pm_error( "out of memory allocating an array" );
    for ( i = 1; i < rows; ++i )
        its[i] = &(its[0][i * cols * size]);
    return its;
    }

void
pm_freearray( its, rows )
    char** its;
    int rows;
    {
    free( its[0] );
    free( its );
    }
#else /* A_FRAGARRAY */
char**
pm_allocarray( cols, rows, size )
    int cols, rows;
    int size;
    {
    char** its;
    int i;
    its = (char**) malloc( (rows + 1) * sizeof(char*) );
    if ( its == (char**) 0 )
        pm_error( "out of memory allocating an array" );
    its[rows] = its[0] = (char*) malloc( rows * cols * size );
    if ( its[0] != (char*) 0 )
        for ( i = 1; i < rows; ++i )
            its[i] = &(its[0][i * cols * size]);
    else
        for( i = 0; i < rows; ++i )
            its[i] = pm_allocrow( cols, size );
    return its;
    }
void
pm_freearray( its, rows )
    char** its;
    int rows;
    {
    int i;
    if( its[rows] != (char*) 0 )
        free( its[rows] );
    else
        for( i = 0; i < rows; ++i )
            pm_freerow( its[i] );
    free( its );
    }
#endif /* A_FRAGARRAY */


/* Case-insensitive keyword matcher. */

int
pm_keymatch( str, keyword, minchars )
    char* str;
    char* keyword;
    int minchars;
    {
    register int len;

    len = strlen( str );
    if ( len < minchars )
        return 0;
    while ( --len >= 0 )
        {
        register char c1, c2;

        c1 = *str++;
        c2 = *keyword++;
        if ( c2 == '\0' )
            return 0;
        if ( isupper( c1 ) )
            c1 = tolower( c1 );
        if ( isupper( c2 ) )
            c2 = tolower( c2 );
        if ( c1 != c2 )
            return 0;
        }
    return 1;
    }


/* Wrapper for Shhopt, to get it into the shared library */

void 
pm_optParseOptions(int *argc, char *argv[],
                   optStruct opt[], int allowNegNum) {

    optParseOptions(argc, argv, opt, allowNegNum);

}



void 
pm_optParseOptions2(int * const argc_p, char *argv[],
                    const optStruct2 opt, const unsigned long flags) {

    optParseOptions2(argc_p, argv, opt, flags);

}


/* Log base two hacks. */

int
pm_maxvaltobits( maxval )
    int maxval;
    {
    if ( maxval <= 1 )
        return 1;
    else if ( maxval <= 3 )
        return 2;
    else if ( maxval <= 7 )
        return 3;
    else if ( maxval <= 15 )
        return 4;
    else if ( maxval <= 31 )
        return 5;
    else if ( maxval <= 63 )
        return 6;
    else if ( maxval <= 127 )
        return 7;
    else if ( maxval <= 255 )
        return 8;
    else if ( maxval <= 511 )
        return 9;
    else if ( maxval <= 1023 )
        return 10;
    else if ( maxval <= 2047 )
        return 11;
    else if ( maxval <= 4095 )
        return 12;
    else if ( maxval <= 8191 )
        return 13;
    else if ( maxval <= 16383 )
        return 14;
    else if ( maxval <= 32767 )
        return 15;
    else if ( (long) maxval <= 65535L )
        return 16;
    else
        pm_error( "maxval of %d is too large!", maxval );
        return -1;  /* Should never come here */
}

int
pm_bitstomaxval( bits )
    int bits;
    {
    return ( 1 << bits ) - 1;
    }


unsigned int 
pm_lcm(const unsigned int x, 
       const unsigned int y, 
       const unsigned int z, 
       const unsigned int limit) {
/*----------------------------------------------------------------------------
  Compute the least common multiple of x, y, and z.  If it's bigger than
  'limit', though, just return 'limit'.
-----------------------------------------------------------------------------*/
    unsigned int biggest;
    unsigned int candidate;

    if (x == 0 || y == 0 || z == 0)
        pm_error("pm_lcm(): Least common multiple of zero taken.");

    biggest = max(x, max(y,z));

    candidate = biggest;
    while (((candidate % x) != 0 ||       /* not a multiple of x */
            (candidate % y) != 0 ||       /* not a multiple of y */
            (candidate % z) != 0 ) &&     /* not a multiple of z */
           candidate <= limit)
        candidate += biggest;

    if (candidate > limit) 
        candidate = limit;

    return candidate;
}


/* Initialization. */

static char* progname;
static int showmessages;

void
pm_init( argcP, argv )
    int* argcP;
    char* argv[];
    {
    int argn, i;
    char *rgbdef;

#ifndef VMS
    /* Extract program name. */
    progname = strrchr( argv[0], '/');
#else
{   char **temp_argv = argv;
    int old_argc = *argcP;
    int i;
    getredirection( argcP, &temp_argv );
    if (*argcP > old_argc) {
        /* Number of command line arguments has increased */
        fprintf( stderr, "Sorry!! getredirection() for VMS has changed the argument list!!!\n");
        fprintf( stderr, "This is intolerable at the present time, so we must stop!!!\n");
        exit(1);
    }
    for (i=0; i<*argcP; i++)
        argv[i] = temp_argv[i];
    }
    if ( progname == NULL ) progname = rindex( argv[0], ']');
    if ( progname == NULL ) progname = rindex( argv[0], '>');
#endif
    if ( progname == NULL )
        progname = argv[0];
    else
        ++progname;

    /* Check for any global args. */
    showmessages = 1;
    for ( argn = 1; argn < *argcP; ++argn )
        {
        if ( pm_keymatch( argv[argn], "-quiet", 6 ) ||
             pm_keymatch( argv[argn], "--quiet", 7 ) )
            {
            showmessages = 0;
            }
        else if ( pm_keymatch( argv[argn], "-version", 8 ) ||
            pm_keymatch( argv[argn], "--version", 9 ) )
            {
            pm_message( "Version: %s", PBMPLUS_VERSION );
#if defined(COMPILE_TIME) && defined(COMPILED_BY)
	    pm_message( "Compiled %s by user \"%s\"",
		       COMPILE_TIME, COMPILED_BY );
#endif
#ifdef BSD
            pm_message( "BSD defined" );
#endif /*BSD*/
#ifdef SYSV
#ifdef VMS
	    pm_message( "VMS & SYSV defined" );
#else
  	    pm_message( "SYSV defined" );
#endif
#endif /*SYSV*/
#ifdef MSDOS
            pm_message( "MSDOS defined" );
#endif /*MSDOS*/
#ifdef AMIGA
            pm_message( "AMIGA defined" );
#endif /* AMIGA */
#ifdef PBMPLUS_BROKENPUTC1
            pm_message( "PBMPLUS_BROKENPUTC1 defined" );
#endif /*PBMPLUS_BROKENPUTC1*/
#ifdef PBMPLUS_BROKENPUTC2
            pm_message( "PBMPLUS_BROKENPUTC2 defined" );
#endif /*PBMPLUS_BROKENPUTC2*/
#ifdef PPM_PACKCOLORS
            pm_message( "PPM_PACKCOLORS defined" );
#endif /*PPM_PACKCOLORS*/
#ifdef DEBUG
            pm_message( "DEBUG defined" );
#endif /*DEBUG*/
#ifdef NEED_VFPRINTF1
            pm_message( "NEED_VFPRINTF1 defined" );
#endif /*NEED_VFPRINTF1*/
#ifdef NEED_VFPRINTF2
            pm_message( "NEED_VFPRINTF2 defined" );
#endif /*NEED_VFPRINTF2*/
            pm_message( "RGB_ENV='%s'", RGBENV );
            rgbdef = getenv(RGBENV);
            if( rgbdef )
                pm_message( "RGBENV= '%s' (env vbl set to '%s')", 
                            RGBENV, rgbdef );
            else
                pm_message( "RGBENV= '%s' (env vbl is unset)", RGBENV);
            exit( 0 );
            }
        else
            continue;
        for ( i = argn + 1; i <= *argcP; ++i )
            argv[i - 1] = argv[i];
        --(*argcP);
        }
/* Set the stdin and stdout mode to binary.  This means nothing on Unix,
   but matters on cygwin.

   Note that stdin and stdout aren't necessarily image files.  In
   particular, stdout is sometimes text for human consumption,
   typically printed on the terminal.  Binary mode isn't really
   appropriate for that case.  We do this setting here without
   any knowledge of how stdin and stdout are being used because it is
   easy.  But we do make an exception for the case that we know the
   file is a terminal, to get a little closer to doing the right
   thing.  
*/
#ifdef O_BINARY
#ifdef HAVE_SETMODE
    if (!isatty(0)) setmode(0,O_BINARY);  /* Standard Input */
    if (!isatty(1)) setmode(1,O_BINARY);  /* Standard Output */
#endif /* HAVE_SETMODE */
#endif /* O_BINARY */
    }

/* Error handling. */

void
pm_usage( const char usage[] ) {
    fprintf( stderr, "usage:  %s %s\n", progname, usage );
    exit( 1 );
    }

void
pm_perror(const char reason[] ) {
    const char* e;

#ifdef A_STRERROR
    e = strerror(errno);
#else /* A_STRERROR */
    e = sys_errlist[errno];
#endif /* A_STRERROR */

    if ( reason != 0 && reason[0] != '\0' )
        pm_error( "%s - %s", reason, e );
    else
        pm_error( "%s", e );
    }

void
pm_message(const char format[], ... ) {

    va_list args;

    va_start( args, format );

    if ( showmessages )
        {
        fprintf( stderr, "%s: ", progname );
        (void) vfprintf( stderr, format, args );
        fputc( '\n', stderr );
        }
    va_end( args );
    }

void
pm_error(const char format[], ... ) {
    va_list args;

    va_start( args, format );

    fprintf( stderr, "%s: ", progname );
    (void) vfprintf( stderr, format, args );
    fputc( '\n', stderr );
    va_end( args );
    exit( 1 );
    }

#ifdef NEED_VFPRINTF1

/* Micro-vfprintf, for systems that don't have vfprintf but do have _doprnt.
*/

int
vfprintf( stream, format, args )
    FILE* stream;
    char* format;
    va_list args;
    {
    return _doprnt( format, args, stream );
    }
#endif /*NEED_VFPRINTF1*/

#ifdef NEED_VFPRINTF2

/* Portable mini-vfprintf, for systems that don't have either vfprintf or
** _doprnt.  This depends only on fprintf.  If you don't have fprintf,
** you might consider getting a new stdio library.
*/

int
vfprintf( stream, format, args )
    FILE* stream;
    char* format;
    va_list args;
    {
    int n;
    char* ep;
    char fchar;
    char tformat[512];
    int do_long;
    int i;
    long l;
    unsigned u;
    unsigned long ul;
    char* s;
    double d;

    n = 0;
    while ( *format != '\0' )
        {
        if ( *format != '%' )
            { /* Not special, just write out the char. */
            (void) putc( *format, stream );
            ++n;
            ++format;
            }
        else
            {
            do_long = 0;
            ep = format + 1;

            /* Skip over all the field width and precision junk. */
            if ( *ep == '-' )
                ++ep;
            if ( *ep == '0' )
                ++ep;
            while ( isdigit( *ep ) )
                ++ep;
            if ( *ep == '.' )
                {
                ++ep;
                while ( isdigit( *ep ) )
                    ++ep;
                }
            if ( *ep == '#' )
                ++ep;
            if ( *ep == 'l' )
                {
                do_long = 1;
                ++ep;
                }

            /* Here's the field type.  Extract it, and copy this format
            ** specifier to a temp string so we can add an end-of-string.
            */
            fchar = *ep;
            (void) strncpy( tformat, format, ep - format + 1 );
            tformat[ep - format + 1] = '\0';

            /* Now do a one-argument fprintf with the format string we have
            ** isolated.
            */
            switch ( fchar )
                {
                case 'd':
                if ( do_long )
                    {
                    l = va_arg( args, long );
                    n += fprintf( stream, tformat, l );
                    }
                else
                    {
                    i = va_arg( args, int );
                    n += fprintf( stream, tformat, i );
                    }
                break;

                case 'o':
                case 'x':
                case 'X':
                case 'u':
                if ( do_long )
                    {
                    ul = va_arg( args, unsigned long );
                    n += fprintf( stream, tformat, ul );
                    }
                else
                    {
                    u = va_arg( args, unsigned );
                    n += fprintf( stream, tformat, u );
                    }
                break;

                case 'c':
                i = (char) va_arg( args, int );
                n += fprintf( stream, tformat, i );
                break;

                case 's':
                s = va_arg( args, char* );
                n += fprintf( stream, tformat, s );
                break;

                case 'e':
                case 'E':
                case 'f':
                case 'g':
                case 'G':
                d = va_arg( args, double );
                n += fprintf( stream, tformat, d );
                break;

                case '%':
                (void) putc( '%', stream );
                ++n;
                break;

                default:
                return -1;
                }

            /* Resume formatting on the next character. */
            format = ep + 1;
            }
        }
    return nc;
    }
#endif /*NEED_VFPRINTF2*/

#ifdef NEED_STRSTR
/* for systems which do not provide strstr */
char*
strstr(s1, s2)
    char *s1, *s2;
{
    int ls2 = strlen(s2);

    if (ls2 == 0)
        return (s1);
    while (strlen(s1) >= ls2) {
        if (strncmp(s1, s2, ls2) == 0)
            return (s1);
        s1++;
    }
    return (0);
}

#endif /*NEED_STRSTR*/


/* File open/close that handles "-" as stdin and checks errors. */

FILE*
pm_openr( name )
    char* name;
    {
    FILE* f;

    if ( strcmp( name, "-" ) == 0 )
        f = stdin;
    else
        {
#ifndef VMS
  	f = fopen( name, "rb" );
#else
	f = fopen ( name, "r", "ctx=stm" );
#endif
        if ( f == NULL )
            {
            pm_perror( name );
            exit( 1 );
            }
        }
    return f;
    }

FILE*
pm_openw( name )
    char* name;
    {
    FILE* f;

#ifndef VMS
    f = fopen( name, "wb" );
#else
    f = fopen ( name, "w", "mbc=32", "mbf=2" );  /* set buffer factors */
#endif
    if ( f == NULL )
        {
        pm_perror( name );
        exit( 1 );
        }
    return f;
    }


void
pm_close( f )
    FILE* f;
    {
    fflush( f );
    if ( ferror( f ) )
        pm_message( "a file read or write error occurred at some point" );
    if ( f != stdin )
        if ( fclose( f ) != 0 )
            pm_perror( "fclose" );
    }



/* The pnmtopng package uses pm_closer() and pm_closew() instead of 
   pm_close(), apparently because the 1999 Pbmplus package has them.
   I don't know what the difference is supposed to be.
*/

void
pm_closer(FILE *f) {
    pm_close(f);
}



void
pm_closew(FILE *f) {
    pm_close(f);
}



/* Endian I/O.
*/

int
pm_readbigshort( in, sP )
    FILE* in;
    short* sP;
    {
    int c;

    if ( (c = getc( in )) == EOF )
        return -1;
    *sP = ( c & 0xff ) << 8;
    if ( (c = getc( in )) == EOF )
        return -1;
    *sP |= c & 0xff;
    return 0;
    }

#if __STDC__
int
pm_writebigshort( FILE* out, short s )
#else /*__STDC__*/
int
pm_writebigshort( out, s )
    FILE* out;
    short s;
#endif /*__STDC__*/
    {
    (void) putc( ( s >> 8 ) & 0xff, out );
    (void) putc( s & 0xff, out );
    return 0;
    }

int
pm_readbiglong( in, lP )
    FILE* in;
    long* lP;
    {
    int c;

    if ( (c = getc( in )) == EOF )
        return -1;
    *lP = ( c & 0xff ) << 24;
    if ( (c = getc( in )) == EOF )
        return -1;
    *lP |= ( c & 0xff ) << 16;
    if ( (c = getc( in )) == EOF )
        return -1;
    *lP |= ( c & 0xff ) << 8;
    if ( (c = getc( in )) == EOF )
        return -1;
    *lP |= c & 0xff;
    return 0;
    }

int
pm_writebiglong( out, l )
    FILE* out;
    long l;
    {
    (void) putc( ( l >> 24 ) & 0xff, out );
    (void) putc( ( l >> 16 ) & 0xff, out );
    (void) putc( ( l >> 8 ) & 0xff, out );
    (void) putc( l & 0xff, out );
    return 0;
    }

int
pm_readlittleshort( in, sP )
    FILE* in;
    short* sP;
    {
    int c;

    if ( (c = getc( in )) == EOF )
        return -1;
    *sP = c & 0xff;
    if ( (c = getc( in )) == EOF )
        return -1;
    *sP |= ( c & 0xff ) << 8;
    return 0;
    }

#if __STDC__
int
pm_writelittleshort( FILE* out, short s )
#else /*__STDC__*/
int
pm_writelittleshort( out, s )
    FILE* out;
    short s;
#endif /*__STDC__*/
    {
    (void) putc( s & 0xff, out );
    (void) putc( ( s >> 8 ) & 0xff, out );
    return 0;
    }

int
pm_readlittlelong( in, lP )
    FILE* in;
    long* lP;
    {
    int c;

    if ( (c = getc( in )) == EOF )
        return -1;
    *lP = c & 0xff;
    if ( (c = getc( in )) == EOF )
        return -1;
    *lP |= ( c & 0xff ) << 8;
    if ( (c = getc( in )) == EOF )
        return -1;
    *lP |= ( c & 0xff ) << 16;
    if ( (c = getc( in )) == EOF )
        return -1;
    *lP |= ( c & 0xff ) << 24;
    return 0;
    }

int
pm_writelittlelong( out, l )
    FILE* out;
    long l;
    {
    (void) putc( l & 0xff, out );
    (void) putc( ( l >> 8 ) & 0xff, out );
    (void) putc( ( l >> 16 ) & 0xff, out );
    (void) putc( ( l >> 24 ) & 0xff, out );
    return 0;
    }


/* Read a file of unknown size to a buffer. Return the number of bytes
   read. Allocate more memory as we need it. The calling routine has
   to free() the buffer.

   Oliver Trepte, oliver@fysik4.kth.se, 930613 */

#define PM_BUF_SIZE 16384      /* First try this size of the buffer, then
                                   double this until we reach PM_MAX_BUF_INC */
#define PM_MAX_BUF_INC 65536   /* Don't allocate more memory in larger blocks
                                   than this. */

char *pm_read_unknown_size( file, nread )
    FILE* file;
    long* nread;
{
    long nalloc;
    register int val;
    char* buf;

    *nread = 0;
    if ((buf=malloc(PM_BUF_SIZE)) == NULL)
        pm_error("Cannot allocate memory");
    nalloc = PM_BUF_SIZE;

    while(1) {
        if (*nread >= nalloc) { /* We need a larger buffer */
            if (nalloc > PM_MAX_BUF_INC)
                nalloc += PM_MAX_BUF_INC;
            else
                nalloc += nalloc;
            if ((buf=realloc(buf, nalloc)) == NULL)
                pm_error("Cannot allocate %d bytes of memory", nalloc);
        }

        if ((val = getc(file)) == EOF)
            return (buf);

        buf[(*nread)++] = val;
    }
}



void
pm_nextimage(FILE * file, int * const eofP) {

    int c;

    c = getc(file);
    if (c == EOF) {
        if (feof(file))
            *eofP = TRUE;
        else
            pm_error("File error on getc() to position to image");
    } else {
        int rc;
        *eofP = FALSE;
        rc = ungetc(c, file);
        if (rc == EOF) 
            pm_error("File error doing ungetc() to position to image.");
    }
}



void
pbm_init(int *argcP, char *argv[]) {
    pm_init(argcP, argv);
}



void
pbm_nextimage(FILE *file, int * const eofP) {
    pm_nextimage(file, eofP);
}



void
pm_check(FILE * file, const enum pm_check_type check_type, 
         const unsigned int need_raster_size,
         enum pm_check_code * const retval_p) {

    struct stat statbuf;
    int curpos;  /* Current position of file; -1 if none */
    int rc;

    curpos = ftell(file);
    if (curpos >= 0) {
        /* This type of file has a current position */
            
        rc = fstat(fileno(file), &statbuf);
        if (rc != 0) 
            pm_error("fstat() failed to get size of file, though ftell() "
                     "successfully identified\n"
                     "the current position.  Errno=%s (%d)",
                     strerror(errno), errno);
        else if (!S_ISREG(statbuf.st_mode)) {
            /* Not a regular file; we can't know its size */
            if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE;
        } else {
            const unsigned int have_raster_size = statbuf.st_size - curpos;
                
            if (have_raster_size < need_raster_size)
                pm_error("File has invalid format.  The raster should "
                         "contain %d bytes, but\n"
                         "the file ends after only %d bytes.",
                         need_raster_size, have_raster_size);
            else if (have_raster_size > need_raster_size) {
                if (retval_p) *retval_p = PM_CHECK_TOO_LONG;
            } else {
                if (retval_p) *retval_p = PM_CHECK_OK;
            }
        }
    } else
        if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE;
}



void
pbm_check(FILE * file, const enum pm_check_type check_type, 
          const int format, const int cols, const int rows,
          enum pm_check_code * const retval_p) {

    if (check_type != PM_CHECK_BASIC) {
        if (retval_p) *retval_p = PM_CHECK_UNKNOWN_TYPE;
    } else if (format != RPBM_FORMAT) {
        if (retval_p) *retval_p = PM_CHECK_UNCHECKABLE;
    } else {        
        const unsigned int bytes_per_row = (cols+7)/8;
        const unsigned int need_raster_size = rows * bytes_per_row;
        
        pm_check(file, check_type, need_raster_size, retval_p);
    }
}
