/*
 * Copyright (c) 1990-1997 Sam Leffler
 * Copyright (c) 1991-1997 Silicon Graphics, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Sam Leffler and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Sam Leffler and Silicon Graphics.
 *
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
 *
 * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THIS SOFTWARE.
 */

/*
 * Convert a CCITT Group 3 or 4 FAX file to TIFF Group 3 or 4 format.
 */
#include "libport.h"
#include "tif_config.h"

#include <stdio.h>
#include <stdlib.h> /* should have atof & getopt */

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif

#ifdef HAVE_IO_H
#include <io.h>
#endif

#include "tiffiop.h"

#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif
#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1
#endif

#define TIFFhowmany8(x)                                                        \
    (((x)&0x07) ? ((uint32_t)(x) >> 3) + 1 : (uint32_t)(x) >> 3)

TIFF *faxTIFF;
char *rowbuf;
char *refbuf;

uint32_t xsize = 1728;
int verbose;
int stretch;
uint16_t badfaxrun;
uint32_t badfaxlines;

int copyFaxFile(TIFF *tifin, TIFF *tifout);
static void usage(int code);

/*
  Struct to carry client data.  Note that it does not appear that the client
  data is actually used in this program.
*/
typedef union _FAX_Client_Data
{
    thandle_t fh; /* Operating system file handle */
    int fd;       /* Integer file descriptor */
} FAX_Client_Data;

int main(int argc, char *argv[])
{
    FILE *in;
    TIFF *out = NULL;
    FAX_Client_Data client_data;
    TIFFErrorHandler whandler = NULL;
    int compression_in = COMPRESSION_CCITTFAX3;
    int compression_out = COMPRESSION_CCITTFAX3;
    int fillorder_in = FILLORDER_LSB2MSB;
    int fillorder_out = FILLORDER_LSB2MSB;
    uint32_t group3options_in = 0;  /* 1d-encoded */
    uint32_t group3options_out = 0; /* 1d-encoded */
    uint32_t group4options_in = 0;  /* compressed */
    uint32_t group4options_out = 0; /* compressed */
    uint32_t defrowsperstrip = (uint32_t)0;
    uint32_t rowsperstrip;
    int photometric_in = PHOTOMETRIC_MINISWHITE;
    int photometric_out = PHOTOMETRIC_MINISWHITE;
    int mode = FAXMODE_CLASSF;
    int rows;
    int c;
    int pn, npages;
    float resY = 196.0;

#if !HAVE_DECL_OPTARG
    extern int optind;
    extern char *optarg;
#endif

    while ((c = getopt(argc, argv, "R:X:o:r:1234ABLMPUW5678abcflmprsuvwzh")) !=
           -1)
        switch (c)
        {
                /* input-related options */
            case '3': /* input is g3-encoded */
                compression_in = COMPRESSION_CCITTFAX3;
                break;
            case '4': /* input is g4-encoded */
                compression_in = COMPRESSION_CCITTFAX4;
                break;
            case 'U': /* input is uncompressed (g3 and g4) */
                group3options_in |= GROUP3OPT_UNCOMPRESSED;
                group4options_in |= GROUP4OPT_UNCOMPRESSED;
                break;
            case '1': /* input is 1d-encoded (g3 only) */
                group3options_in &= ~GROUP3OPT_2DENCODING;
                break;
            case '2': /* input is 2d-encoded (g3 only) */
                group3options_in |= GROUP3OPT_2DENCODING;
                break;
            case 'P': /* input has not-aligned EOL (g3 only) */
                group3options_in &= ~GROUP3OPT_FILLBITS;
                break;
            case 'A': /* input has aligned EOL (g3 only) */
                group3options_in |= GROUP3OPT_FILLBITS;
                break;
            case 'W': /* input has 0 mean white */
                photometric_in = PHOTOMETRIC_MINISWHITE;
                break;
            case 'B': /* input has 0 mean black */
                photometric_in = PHOTOMETRIC_MINISBLACK;
                break;
            case 'L': /* input has lsb-to-msb fillorder */
                fillorder_in = FILLORDER_LSB2MSB;
                break;
            case 'M': /* input has msb-to-lsb fillorder */
                fillorder_in = FILLORDER_MSB2LSB;
                break;
            case 'R': /* input resolution */
                resY = (float)atof(optarg);
                break;
            case 'X': /* input width */
                xsize = (uint32_t)atoi(optarg);
                break;

                /* output-related options */
            case '7': /* generate g3-encoded output */
                compression_out = COMPRESSION_CCITTFAX3;
                break;
            case '8': /* generate g4-encoded output */
                compression_out = COMPRESSION_CCITTFAX4;
                break;
            case 'u': /* generate uncompressed output (g3 and g4) */
                group3options_out |= GROUP3OPT_UNCOMPRESSED;
                group4options_out |= GROUP4OPT_UNCOMPRESSED;
                break;
            case '5': /* generate 1d-encoded output (g3 only) */
                group3options_out &= ~GROUP3OPT_2DENCODING;
                break;
            case '6': /* generate 2d-encoded output (g3 only) */
                group3options_out |= GROUP3OPT_2DENCODING;
                break;
            case 'c': /* generate "classic" g3 format */
                mode = FAXMODE_CLASSIC;
                break;
            case 'f': /* generate Class F format */
                mode = FAXMODE_CLASSF;
                break;
            case 'm': /* output's fillorder is msb-to-lsb */
                fillorder_out = FILLORDER_MSB2LSB;
                break;
            case 'l': /* output's fillorder is lsb-to-msb */
                fillorder_out = FILLORDER_LSB2MSB;
                break;
            case 'o':
                out = TIFFOpen(optarg, "w");
                if (out == NULL)
                {
                    fprintf(stderr, "%s: Can not create or open %s\n", argv[0],
                            optarg);
                    return EXIT_FAILURE;
                }
                break;
            case 'a': /* generate EOL-aligned output (g3 only) */
                group3options_out |= GROUP3OPT_FILLBITS;
                break;
            case 'p': /* generate not EOL-aligned output (g3 only) */
                group3options_out &= ~GROUP3OPT_FILLBITS;
                break;
            case 'r': /* rows/strip */
                defrowsperstrip = atol(optarg);
                break;
            case 's': /* stretch image by dup'ng scanlines */
                stretch = 1;
                break;
            case 'w': /* undocumented -- for testing */
                photometric_out = PHOTOMETRIC_MINISWHITE;
                break;
            case 'b': /* undocumented -- for testing */
                photometric_out = PHOTOMETRIC_MINISBLACK;
                break;
            case 'z': /* undocumented -- for testing */
                compression_out = COMPRESSION_LZW;
                break;
            case 'v': /* -v for info */
                verbose++;
                break;
            case 'h':
                usage(EXIT_SUCCESS);
                break;
            case '?':
                usage(EXIT_FAILURE);
                /*NOTREACHED*/
        }
    npages = argc - optind;
    if (npages < 1)
        usage(EXIT_FAILURE);

    rowbuf = _TIFFmalloc(TIFFhowmany8(xsize));
    refbuf = _TIFFmalloc(TIFFhowmany8(xsize));
    if (rowbuf == NULL || refbuf == NULL)
    {
        fprintf(stderr, "%s: Not enough memory\n", argv[0]);
        return (EXIT_FAILURE);
    }

    if (out == NULL)
    {
        out = TIFFOpen("fax.tif", "w");
        if (out == NULL)
        {
            fprintf(stderr, "%s: Can not create fax.tif\n", argv[0]);
            return (EXIT_FAILURE);
        }
    }

    faxTIFF = TIFFClientOpen(
        "(FakeInput)", "w",
        /* TIFFClientOpen() fails if we don't set existing value here */
        TIFFClientdata(out), TIFFGetReadProc(out), TIFFGetWriteProc(out),
        TIFFGetSeekProc(out), TIFFGetCloseProc(out), TIFFGetSizeProc(out),
        TIFFGetMapFileProc(out), TIFFGetUnmapFileProc(out));
    if (faxTIFF == NULL)
    {
        fprintf(stderr, "%s: Can not create fake input file\n", argv[0]);
        return (EXIT_FAILURE);
    }
    TIFFSetMode(faxTIFF, O_RDONLY);
    TIFFSetField(faxTIFF, TIFFTAG_IMAGEWIDTH, xsize);
    TIFFSetField(faxTIFF, TIFFTAG_SAMPLESPERPIXEL, 1);
    TIFFSetField(faxTIFF, TIFFTAG_BITSPERSAMPLE, 1);
    TIFFSetField(faxTIFF, TIFFTAG_FILLORDER, fillorder_in);
    TIFFSetField(faxTIFF, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
    TIFFSetField(faxTIFF, TIFFTAG_PHOTOMETRIC, photometric_in);
    TIFFSetField(faxTIFF, TIFFTAG_YRESOLUTION, resY);
    TIFFSetField(faxTIFF, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH);

    /* NB: this must be done after directory info is setup */
    TIFFSetField(faxTIFF, TIFFTAG_COMPRESSION, compression_in);
    if (compression_in == COMPRESSION_CCITTFAX3)
        TIFFSetField(faxTIFF, TIFFTAG_GROUP3OPTIONS, group3options_in);
    else if (compression_in == COMPRESSION_CCITTFAX4)
        TIFFSetField(faxTIFF, TIFFTAG_GROUP4OPTIONS, group4options_in);
    for (pn = 0; optind < argc; pn++, optind++)
    {
        in = fopen(argv[optind], "rb");
        if (in == NULL)
        {
            fprintf(stderr, "%s: %s: Can not open\n", argv[0], argv[optind]);
            continue;
        }
#if defined(_WIN32) && defined(USE_WIN32_FILEIO)
        client_data.fh = (thandle_t)_get_osfhandle(fileno(in));
#else
        client_data.fd = fileno(in);
#endif
        TIFFSetClientdata(faxTIFF, client_data.fh);
        TIFFSetFileName(faxTIFF, (const char *)argv[optind]);
        TIFFSetField(out, TIFFTAG_IMAGEWIDTH, xsize);
        TIFFSetField(out, TIFFTAG_BITSPERSAMPLE, 1);
        TIFFSetField(out, TIFFTAG_COMPRESSION, compression_out);
        TIFFSetField(out, TIFFTAG_PHOTOMETRIC, photometric_out);
        TIFFSetField(out, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
        TIFFSetField(out, TIFFTAG_SAMPLESPERPIXEL, 1);
        switch (compression_out)
        {
            /* g3 */
            case COMPRESSION_CCITTFAX3:
                TIFFSetField(out, TIFFTAG_GROUP3OPTIONS, group3options_out);
                TIFFSetField(out, TIFFTAG_FAXMODE, mode);
                rowsperstrip =
                    (defrowsperstrip) ? defrowsperstrip : (uint32_t)-1L;
                break;

            /* g4 */
            case COMPRESSION_CCITTFAX4:
                TIFFSetField(out, TIFFTAG_GROUP4OPTIONS, group4options_out);
                TIFFSetField(out, TIFFTAG_FAXMODE, mode);
                rowsperstrip =
                    (defrowsperstrip) ? defrowsperstrip : (uint32_t)-1L;
                break;

            default:
                rowsperstrip = (defrowsperstrip) ? defrowsperstrip
                                                 : TIFFDefaultStripSize(out, 0);
        }
        TIFFSetField(out, TIFFTAG_ROWSPERSTRIP, rowsperstrip);
        TIFFSetField(out, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
        TIFFSetField(out, TIFFTAG_FILLORDER, fillorder_out);
        TIFFSetField(out, TIFFTAG_SOFTWARE, "fax2tiff");
        TIFFSetField(out, TIFFTAG_XRESOLUTION, 204.0);
        if (!stretch)
        {
            TIFFGetField(faxTIFF, TIFFTAG_YRESOLUTION, &resY);
            TIFFSetField(out, TIFFTAG_YRESOLUTION, resY);
        }
        else
            TIFFSetField(out, TIFFTAG_YRESOLUTION, 196.);
        TIFFSetField(out, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH);
        TIFFSetField(out, TIFFTAG_PAGENUMBER, pn, npages);

        if (!verbose)
            whandler = TIFFSetWarningHandler(NULL);
        rows = copyFaxFile(faxTIFF, out);
        fclose(in);
        if (!verbose)
            (void)TIFFSetWarningHandler(whandler);

        TIFFSetField(out, TIFFTAG_IMAGELENGTH, rows);

        if (verbose)
        {
            fprintf(stderr, "%s:\n", argv[optind]);
            fprintf(stderr, "%d rows in input\n", rows);
            fprintf(stderr, "%" PRIu32 " total bad rows\n", badfaxlines);
            fprintf(stderr, "%" PRIu16 " max consecutive bad rows\n",
                    badfaxrun);
        }
        if (compression_out == COMPRESSION_CCITTFAX3 && mode == FAXMODE_CLASSF)
        {
            TIFFSetField(out, TIFFTAG_BADFAXLINES, badfaxlines);
            TIFFSetField(out, TIFFTAG_CLEANFAXDATA,
                         badfaxlines ? CLEANFAXDATA_REGENERATED
                                     : CLEANFAXDATA_CLEAN);
            TIFFSetField(out, TIFFTAG_CONSECUTIVEBADFAXLINES, badfaxrun);
        }
        TIFFWriteDirectory(out);
    }
    TIFFClose(out);
    _TIFFfree(rowbuf);
    _TIFFfree(refbuf);
    return (EXIT_SUCCESS);
}

int copyFaxFile(TIFF *tifin, TIFF *tifout)
{
    uint32_t row;
    uint32_t linesize = TIFFhowmany8(xsize);
    uint16_t badrun;
    int ok;

    tifin->tif_rawdatasize = (tmsize_t)TIFFGetFileSize(tifin);
    if (tifin->tif_rawdatasize == 0)
    {
        TIFFError(tifin->tif_name, "Empty input file");
        return (0);
    }
    tifin->tif_rawdata = _TIFFmalloc(tifin->tif_rawdatasize);
    if (tifin->tif_rawdata == NULL)
    {
        TIFFError(tifin->tif_name, "Not enough memory");
        return (0);
    }
    if (!ReadOK(tifin, tifin->tif_rawdata, tifin->tif_rawdatasize))
    {
        TIFFError(tifin->tif_name, "Read error at scanline 0");
        return (0);
    }
    tifin->tif_rawcp = tifin->tif_rawdata;
    tifin->tif_rawcc = tifin->tif_rawdatasize;

    (*tifin->tif_setupdecode)(tifin);
    (*tifin->tif_predecode)(tifin, (tsample_t)0);
    tifin->tif_row = 0;
    badfaxlines = 0;
    badfaxrun = 0;

    _TIFFmemset(refbuf, 0, linesize);
    row = 0;
    badrun = 0; /* current run of bad lines */
    while (tifin->tif_rawcc > 0)
    {
        ok = (*tifin->tif_decoderow)(tifin, (tdata_t)rowbuf, linesize, 0);
        if (!ok)
        {
            badfaxlines++;
            badrun++;
            /* regenerate line from previous good line */
            _TIFFmemcpy(rowbuf, refbuf, linesize);
        }
        else
        {
            if (badrun > badfaxrun)
                badfaxrun = badrun;
            badrun = 0;
            _TIFFmemcpy(refbuf, rowbuf, linesize);
        }
        tifin->tif_row++;

        if (TIFFWriteScanline(tifout, rowbuf, row, 0) < 0)
        {
            fprintf(stderr, "%s: Write error at row %" PRIu32 ".\n",
                    tifout->tif_name, row);
            break;
        }
        row++;
        if (stretch)
        {
            if (TIFFWriteScanline(tifout, rowbuf, row, 0) < 0)
            {
                fprintf(stderr, "%s: Write error at row %" PRIu32 ".\n",
                        tifout->tif_name, row);
                break;
            }
            row++;
        }
    }
    if (badrun > badfaxrun)
        badfaxrun = badrun;
    _TIFFfree(tifin->tif_rawdata);
    return (row);
}

static const char usage_info[] =
    "Create a TIFF Class F fax file from raw fax data\n\n"
    "usage: fax2tiff [options] input.raw...\n"
    "where options are:\n"
    " -3		input data is G3-encoded		[default]\n"
    " -4		input data is G4-encoded\n"
    " -U		input data is uncompressed (G3 or G4)\n"
    " -1		input data is 1D-encoded (G3 only)	[default]\n"
    " -2		input data is 2D-encoded (G3 only)\n"
    " -P		input is not EOL-aligned (G3 only)	[default]\n"
    " -A		input is EOL-aligned (G3 only)\n"
    " -M		input data has MSB2LSB bit order\n"
    " -L		input data has LSB2MSB bit order	[default]\n"
    " -B		input data has min 0 means black\n"
    " -W		input data has min 0 means white	[default]\n"
    " -R #		input data has # resolution (lines/inch) [default is "
    "196]\n"
    " -X #		input data has # width			[default is "
    "1728]\n"
    "\n"
    " -o out.tif	write output to out.tif\n"
#ifdef CCITT_SUPPORT
    " -7		generate G3-encoded output		[default]\n"
    " -8		generate G4-encoded output\n"
    " -u		generate uncompressed output (G3 or G4)\n"
    " -5		generate 1D-encoded output (G3 only)\n"
    " -6		generate 2D-encoded output (G3 only)	[default]\n"
    " -p		generate not EOL-aligned output (G3 only)\n"
    " -a		generate EOL-aligned output (G3 only)	[default]\n"
#endif
    " -c		generate \"classic\" TIFF format\n"
    " -f		generate TIFF Class F (TIFF/F) format	[default]\n"
    " -m		output fill order is MSB2LSB\n"
    " -l		output fill order is LSB2MSB		[default]\n"
    " -r #		make each strip have no more than # rows\n"
    " -s		stretch image by duplicating scanlines\n"
    " -v		print information about conversion work\n"
#ifdef LZW_SUPPORT
    " -z		generate LZW compressed output\n"
#endif
    ;

static void usage(int code)
{
    FILE *out = (code == EXIT_SUCCESS) ? stdout : stderr;

    fprintf(out, "%s\n\n", TIFFGetVersion());
    fprintf(out, "%s", usage_info);
    exit(code);
}
