/*
 * main.c - a commandline frontend for the barcode library
 *
 * Copyright (c) 1999 Michele Comitini (mcm@glisco.it)
 * Copyright (c) 1999 Alessandro Rubini (rubini@gnu.org)
 * Copyright (c) 1999 Prosa Srl. (prosa@prosa.it)
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */
	
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "cmdline.h"
#include "barcode.h"

#ifndef NO_LIBPAPER
#include <paper.h>
#endif

/*
 * Most of this file deals with command line options, by exploiting
 * the cmdline.[ch] engine to offer defaults via environment variables
 * and handling functions for complex options.
 *
 * In order to offer a friendly interface (for those who feel the
 * cmdline *is* friendly, like me), we have to convert names to enums...
 */

struct {
    char *name;
    int type;
} encode_tab[] = {
    {"ean",      BARCODE_EAN},
    {"ean13",    BARCODE_EAN},
    {"ean-13",   BARCODE_EAN},
    {"ean8",     BARCODE_EAN},
    {"ean-8",    BARCODE_EAN},
    {"upc",      BARCODE_UPC},
    {"upc-a",    BARCODE_UPC},
    {"upc-e",    BARCODE_UPC},
    {"isbn",     BARCODE_ISBN},
    {"39",       BARCODE_39},
    {"code39",   BARCODE_39},
    {"128c",     BARCODE_128C},
    {"code128c", BARCODE_128C},
    {"128b",     BARCODE_128B},
    {"code128b", BARCODE_128B},
    {"128",      BARCODE_128},
    {"code128",  BARCODE_128},
    {"i25",      BARCODE_I25},
    {"interleaved 2 of 5", BARCODE_I25},
    {NULL, 0}
};

/*
 * Get encoding type from string rapresentation.
 * Returns -1 on error.
 */
int encode_id(char *encode_name)
{
    int i;
    for (i = 0;  encode_tab[i].name; i++)
	if (!strcasecmp(encode_tab[i].name, encode_name))
	    return encode_tab[i].type;
    return -1;
}

int list_encodes(FILE *f) /* used in the help message */
{
    int prev = -1;
    int i;

    fprintf(f, "Known encodings are (synonyms appear on the same line):");
    for (i = 0;  encode_tab[i].name; i++) {
	if (encode_tab[i].type != prev)
	    fprintf(f, "\n\t");
	else
	    fprintf(f, ", ");
	fprintf(f, "\"%s\"", encode_tab[i].name);
	prev = encode_tab[i].type;
    }
    fprintf(f, "\n");
    return 0;
}


/*
 * Variables to hold cmdline arguments (or defaults)
 */

char *ifilename, *ofilename;
int encoding_type;                    /* filled by get_encoding() */
int code_width, code_height;          /* "-g" for standalone codes */
int lines, columns;                   /* "-t" for tables */
int xmargin0, ymargin0;               /* both for "-g" and "-t" */
int xmargin1, ymargin1;               /* same, but right and top */
int ximargin, yimargin;               /* "-m": internal margins */
int eps, ps, noascii, nochecksum;     /* boolean flags */
int page_wid, page_hei;               /* page size in points */
char *page_name;                      /* name of the media */
double unit = 1.0;                    /* unit specification */

char *prgname;  /* used to print error msgs, initialized to argv[0] by main */

/*
 * Functions to handle command line arguments
 */

struct encode_item {
    char *string;
    struct encode_item *next;
} *list_head, *list_tail;

/* each "-b" option adds a string to the input pool allocating its space */
int get_input_string(void *arg)
{
    struct encode_item *item = malloc(sizeof(*item));
    if (!item) {
	fprintf(stderr, "%s: malloc: %s\n", prgname, strerror(errno));
	return -2;
    }
    item->string = strdup(arg);
    if (!list_head) {
	list_head = list_tail = item;
    } else {
	list_tail->next = item;
	list_tail = item;
    }
    item->next = NULL;
    return 0;
}

/* and this function extracts strings from the pool */
unsigned char *retrieve_input_string(FILE *ifile)
{
    char *string;
    static char fileline[128];

    struct encode_item *item = list_head;
    if (list_tail) { /* this means at least one "-b" was specified */
	if (!item)
	    return NULL; /* the list is empty */
	string = item->string;
	list_head = item->next;
	free(item);
	return string;
    }

    /* else,  read from the file */
    if (!fgets(fileline, 128, ifile))
	return NULL;
    if (fileline[strlen(fileline)-1]=='\n')
	fileline[strlen(fileline)-1]= '\0';
    return strdup(fileline);
}

/* accept a unit specification */
int get_unit(void *arg)
{
    static struct {
	char *str;
	double unit;
    } *ptr, unittab[] = {
	{"pt",  1.0},
	{"in",  72.0},
	{"cm",  72.0/2.54},
	{"mm",  72.0/25.4},
	{NULL, 0.0}
    };

    for (ptr = unittab; ptr->str && strcmp((char *)arg, ptr->str); ptr++)
	;
    unit = ptr->unit;
    if (ptr->str) return 0;

    fprintf(stderr, "%s: incorrect unit \"%s\" (use one of",
	    prgname, (char *)arg);
    for (ptr = unittab; ptr->str; ptr++)
	fprintf(stderr, " \"%s\"", ptr->str);
    fprintf(stderr, ")\n");
    return -2;
}

/* convert an encoding name to an encoding integer code */
int get_encoding(void *arg)
{
    encoding_type = encode_id((char *)arg);
    if (encoding_type >=0) return 0;
    fprintf(stderr, "%s: wrong encoding \"%s\"\n", prgname,
	    (char *)arg);
    return -2; /* error, no help */
}

/* convert a geometry specification */
int get_geometry(void *arg)
{
    double w, h, x = 0.0, y = 0.0;
    int n;

    n = sscanf((char *)arg, "%lfx%lf+%lf+%lf", &w, &h, &x, &y);
    if (n!=4 && n!=2) {
	fprintf(stderr, "%s: wrong geometry \"%s\"\n", prgname, (char *)arg);
	return -2;
    }
    /* convert to points */
    code_width  = w * unit;
    code_height = h * unit;
    xmargin0 = x * unit;
    ymargin0 = y * unit;
    return 0;
}

/* convert a geometry specification */
int get_table(void *arg)
{
    double x0 = 0.0, y0 = 0.0, x1 = 0.0, y1 = 0.0;
    int n;

    n = sscanf((char *)arg, "%dx%d+%lf+%lf-%lf-%lf",
	       &columns, &lines, &x0, &y0, &x1, &y1);

    if (n==1 || n==3) { /* error: 2, 4, 5, 6 are fine */
	fprintf(stderr, "%s: wrong table specification \"%s\"\n", prgname,
		(char *)arg);
	return -2;
    }
    if (n < 6) y1 = y0; /* symmetric by default */
    if (n < 5) x1 = x0;

    /* convert and return */
    xmargin0 = x0 * unit;
    ymargin0 = y0 * unit;
    xmargin1 = x1 * unit;
    ymargin1 = y1 * unit;
    return 0;
}

/* convert an internal margin specification */
int get_margin(void *arg)
{
    char separator;
    double x,y;
    int n;

    /* accept one number or two, separated by any char */
    n = sscanf((char *)arg, "%lf%c%lf", &x, &separator, &y);

    if (n==1) {
	n=3; y = x;
    }
    if (n==3) {
	ximargin = x * unit;
	yimargin = y * unit;
	return 0;
    }
    fprintf(stderr, "%s: wrong margin specification \"%s\"\n", prgname,
	    (char *)arg);
	return -2;
    return 0;
}

/* convert a page geometry specification */
int get_page_geometry(void *arg)
{
    int n;
    double dpw, dph; /* page width, height in mm or inches */
    static char tmpstr[20];
    page_name = arg; /* if undecipherable, we won't run the program :) */
    /*
     * try to decode a "mm" string (eg. "210mmx297mm" or "210x297mm")
     */
    n = sscanf((char *)arg, "%lfmmx%lf", &dpw, &dph);
    if (n != 2 && strlen(arg)<20) {
	n =  sscanf((char *)arg, "%lfx%lf%s", &dpw, &dph, tmpstr);
	if (n == 3 && !strcmp(tmpstr, "mm")) {
	    /* Ok, convert to points: 1in is 25.4mm, 1in is also 72p */
	    page_wid = (int)(dpw / 25.4 * 72.0 + 0.5);
	    page_hei = (int)(dph / 25.4 * 72.0 + 0.5);
	    return 0;
	}
    }

    /*
     * try to decode an "in" string (eg. "8.5inx11in" or "8.5x11in")
     */
    n = sscanf((char *)arg, "%lfinx%lf", &dpw, &dph);
    if (n != 2 && strlen(arg)<20) {
	n =  sscanf((char *)arg, "%lfx%lf%s", &dpw, &dph, tmpstr);
	if (n == 3 && !strcmp(tmpstr, "in")) {
	    page_wid = (int)(dpw * 72.0 + 0.5); /* round to points */
	    page_hei = (int)(dph * 72.0 + 0.5);
	    return 0;
	}
    }

    /*
     * try to decode a numeric specification
     */
    n = sscanf((char *)arg, "%lfx%lf", &dpw, &dph);
    if (n == 2) {
	page_wid = dpw * unit;
	page_hei = dph * unit;
	if (unit != 1.0) { /* rebuild the page name */
	    page_name = malloc(32); /* big, to avoid snprintf, missing on HP */
	    if (page_name)
		sprintf(page_name, "%dx%d\n", page_wid, page_hei);
	}
	return 0;
    }

#ifndef NO_LIBPAPER
    /*
     * try to use libpaper, since it is available
     */
    {
    const struct paper* paptr;

    paperinit();
    paptr = paperinfo(arg);
    if (!paptr) { /* unknown name */
	paperdone();
	return -1;
    }
    page_wid = (int)(paperpswidth(paptr) + 0.5);
    page_hei = (int)(paperpsheight(paptr) + 0.5);
    paperdone();
    return 0;
    }
#endif
    /* If we got here, the argument is undecipherable: fail */
    fprintf(stderr, "%s: wrong page size specification \"%s\"\n", prgname,
	    (char *)arg);
    return -2;
}

/*
 * The table of possible arguments
 */
struct commandline option_table[] = {
    {'i', CMDLINE_S, &ifilename, NULL, NULL, NULL,
                   "input file (strings to encode), default is stdin"},
    {'o', CMDLINE_S, &ofilename, NULL, NULL, NULL,
                    "output file, default is stdout"},
    {'b', CMDLINE_S, NULL, get_input_string, NULL, NULL,
                   "string to encode (use input file if missing)"},
    {'e', CMDLINE_S, NULL, get_encoding, "BARCODE_ENCODING", NULL,
                   "encoding type (default is best fit for first string)"},
    {'u', CMDLINE_S, NULL, get_unit, "BARCODE_UNIT", NULL,
                    "unit (\"mm\", \"in\", ...) used to decode -g, -t, -p"},
    {'g', CMDLINE_S, NULL, get_geometry, "BARCODE_GEOMETRY", NULL,
                    "geometry on the page: <wid>x<hei>[+<margin>+<margin>]"},
    {'t', CMDLINE_S, NULL, get_table, "BARCODE_TABLE", NULL,
                    "table geometry: <cols>x<lines>[+<margin>+<margin>]"},
    {'m', CMDLINE_S, NULL, get_margin, "BARCODE_MARGIN", "10",
                    "internal margin for each item in a table: <xm>[,<ym>]"},
    {'n', CMDLINE_NONE, &noascii, NULL, NULL, NULL,
                    "\"numeric\": avoid printing text along with the bars"},
    {'c', CMDLINE_NONE, &nochecksum, NULL, NULL, NULL,
                    "no Checksum character, if the chosen encoding allows it"},
    {'E', CMDLINE_NONE, &eps, NULL, NULL, NULL,
                    "print one code as eps file (default: multi-page ps)"},
    {'p', CMDLINE_S, NULL, get_page_geometry, NULL, NULL,
                    "page size (refer to the man page)"},
    {0,}
};

#ifdef NO_STRERROR
/*
 * A strerror replacement (thanks to Thad Floryan <thad@thadlabs.com>)
 */
char *strerror(int error)
{
    static char msg[16];
    if (error >= 0 && error < sys_nerr)
	return sys_errlist[error];
    sprintf(msg, "Error %d", error);
    return msg;
}
#endif
	 
/*
 * The main function
 */
int main(int argc, char **argv)
{
    FILE *ifile = stdin;
    FILE *ofile = stdout;
    char *line;
    int flags=0; /* for the library */
    int page, retval;

    prgname = argv[0];

    /* First of all, accept "--help" and "-h" as a special case */
    if (argc == 2 && (!strcmp(argv[1],"--help") || !strcmp(argv[1],"-h"))) {
	commandline_errormsg(stderr, option_table, argv[0], "Options:\n");
	fprintf(stderr,"\n");
	list_encodes(stderr);
	exit(1);
    }
    /* Also, accept "--version" as a special case */
    if (argc == 2 && (!strcmp(argv[1],"--version"))) {
	printf("barcode frontend (GNU barcode) " BARCODE_VERSION "\n");
	exit(0);
    }

    /* Otherwise, parse the commandline */
    retval = commandline(option_table, argc, argv, "Use: %s [options]\n");
    if (retval) {
	if (retval == -1) /* help printed, complete it */
	    list_encodes(stderr);
	else /* no help printed, suggest it */
	    fprintf(stderr, "%s: try \"%s --help\"\n", prgname, prgname);
	exit(1);
    }

    /* If no paper size has been specified, use the default, if any */
    if (!page_name) {
	page_wid = 595; page_hei = 842;
	page_name = "A4"; /* I live in Europe :) */
#ifndef NO_LIBPAPER
	get_page_geometry(systempapername()); /* or the system default */
#endif
    }

    /* FIXME: print warnings for incompatible options */

    /* open the input stream if specified */
    if (ifilename)
	ifile = fopen(ifilename,"r");
    if (!ifile) {
	fprintf(stderr, "%s: %s: %s\n", argv[0], ifilename,
		strerror(errno));
	exit(1);
    }

    /* open the output stream if specified */
    if (ofilename)
	ofile = fopen(ofilename,"w");
    if (!ofile) {
	fprintf(stderr, "%s: %s: %s\n", argv[0], ofilename,
		strerror(errno));
	exit(1);
    }

    if (encoding_type < 0) { /* unknown type specified */
	fprintf(stderr,"%s: Unknown endoding. Try \"%s --help\"\n",
 		argv[0], argv[0]);
	exit(1);
    }
    flags |= encoding_type;  
    ps = !eps; /* a shortcut */
    if (eps)
	flags |= BARCODE_OUT_EPS; /* print headers too */
    else
	flags |= BARCODE_OUT_PS | BARCODE_OUT_NOHEADERS;
    if (noascii)
	flags |= BARCODE_NO_ASCII;
    if (nochecksum)
	flags |= BARCODE_NO_CHECKSUM;

    /* the table is not available in eps mode */
    if (eps && (lines>1 || columns>1)) {
	fprintf(stderr, "%s: can't print tables in EPS format\n",argv[0]);
	exit(1);
    }

    if (ps) { /* The header is independent of single/table mode */
	/* Headers. Don't let the library do it, we may need multi-page */
	fprintf(ofile, "%%!PS-Adobe-2.0\n");
	/* It would be nice to know the bounding box. Leave it alone */
	fprintf(ofile, "%%%%Creator: \"barcode\", "
		"libbarcode sample frontend\n");
	if (page_name)
	    fprintf(ofile, "%%%%DocumentPaperSizes: %s\n", page_name);
	fprintf(ofile, "%%%%EndComments\n");
	fprintf(ofile, "%%%%EndProlog\n\n");
    }

    /*
     * Here we are, ready to work. Handle the one-per-page case first,
     * as it is shorter.
     */
    if (!lines && !columns) {
	page = 0;
	while ( (line = retrieve_input_string(ifile)) ) {
	    page++;
	    if (ps) {
		fprintf(ofile, "%%%%Page: %i %i\n\n",page,page);
	    }
	    if (Barcode_Encode_and_Print(line, ofile, code_width, code_height,
					 xmargin0, ymargin0, flags) < 0) {
		fprintf(stderr, "%s: can't encode \"%s\"\n", argv[0], line);
	    }
	    if (eps) break; /* if output is eps, do it once only */
	    fprintf(ofile, "showpage\n");
	}
	/* no more lines, print footers */
	if (ps) {
	    fprintf(ofile, "%%%%Trailer\n\n");
	}
    } else {

	/* table mode, the header has been already printed */
	
	int xstep = (page_wid - xmargin0 - xmargin1)/columns;
	int ystep = (page_hei - ymargin0 - ymargin1)/lines;
	int x = columns, y = -1; /* position in the table, start off-page */

	page=0;


	while ( (line = retrieve_input_string(ifile)) ) {
	    x++;  /* fit x and y */
	    if (x >= columns) {
		x=0; y--;
		if (y<0) {
		    y = lines-1; page++;
		    if (page>1) fprintf(ofile, "showpage\n");
		    fprintf(ofile, "%%%%Page: %i %i\n\n",page,page);
		}
	    }

	    /*
	     * Print this code, using the internal margins as spacing.
	     * In order to remove the extra (default) margin, subtract it
	     * in advance (dirty)
	     */
	    if (Barcode_Encode_and_Print(line, ofile,
		    xstep - 2*ximargin, ystep - 2*yimargin,
		    xmargin0 + ximargin + x * xstep - BARCODE_DEFAULT_MARGIN,
		    ymargin0 + yimargin + y * ystep - BARCODE_DEFAULT_MARGIN,
		    flags)<0) {
		fprintf(stderr, "%s: can't encode \"%s\"\n", argv[0], line);
	    }
	}
	fprintf(ofile, "showpage\n\n%%%%Trailer\n\n");
    }
    return 0;
}



