/*
 * File: abc_img.c
 * Part of the ABClock package
 * (c) Peter Kleiweg
 *
 * 2002/05/31: version 1.0b
 * 2000/08/15: version 1.0
 * 2000/08/06: version 0.9
 * 2000/08/04: version 0.1
 *
 * This 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,
 * or (at your option) any later version.
 *
 */

#define my_VERSION "1.0b"

#ifdef __WIN32__
#  define my_PATH_SEP '\\'
#else
#  define my_PATH_SEP '/'
#endif

#ifdef __MSDOS__
#  ifndef __COMPACT__
#    error Memory model COMPACT required
#  endif  /* __COMPACT__  */
#  include <dir.h>
#  include <fcntl.h>
#  include <io.h>
#endif
#ifdef __WIN32__
#   include <fcntl.h>
#endif
#include <errno.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "abclib.h"

#ifdef __WIN32__
unsigned int
    _CRT_fmode = _O_BINARY;
#endif

typedef enum { FALSE = 0, TRUE } BOOL;

typedef enum { XBM, XPM, PBM, PPM, BMP, BMPWIN } IMAGETYPE;

IMAGETYPE
#if defined(__MSDOS__) || defined(__WIN32__)
    imagetype = BMP;
#   define my_NL "\r\n"
#else
    imagetype = PPM;
#   define my_NL "\n"
#endif

unsigned char
    **map;

char
    *filename = NULL,
    **arg_v,
    *programname,
    *no_mem_buffer,
    out_of_memory [] = "Out of memory";

int
    color [5][3] = {
	{ 255, 255, 255 },    /* background       */
	{ 255, 255, 255 },    /* inside of square */
	{   0,   0,   0 },    /* square border    */
	{   0,   0,   0 },    /* hours            */
	{   0,   0,   0 }     /* minutes          */
    },
    xsize = 80,
    ysize = 80,
    xmapsize,
    border = -1,
    indexcolor,
    arg_c;

BOOL
    verbose = TRUE;

FILE
    *fp;

void
    setup_clock (void),
    write_xbm (void),
    write_xpm (void),
    write_pbm (void),
    write_ppm (void),
    write_bmp (BOOL win),
    *s_malloc (size_t size),
    get_programname (char const *argv0),
    process_args (void),
    testcolor (int i, char const *s),
    testsize (int size, char const *s),
    errit (char const *format, ...),
    syntax (void);
char
    *get_arg (void);

int main (int argc, char *argv [])
{
    time_t
	tp;
    struct tm
	*tm;

    no_mem_buffer = (char *) malloc (1024);

    get_programname (argv [0]);

    arg_c = argc;
    arg_v = argv;
    process_args ();

    setup_clock ();

    switch (arg_c) {
	case 1:
	    time (&tp);
	    tm = localtime (&tp);
	    ABC_Make (tm->tm_hour, tm->tm_min, tm->tm_sec, xsize, ysize, border);
	    break;
	case 4:
	    ABC_Make (
                ((unsigned) atoi (arg_v [1])) % 24,
		((unsigned) atoi (arg_v [2])) % 60,
		((unsigned) atoi (arg_v [3])) % 60,
		xsize, ysize, border
		);
	    break;
	default:
	    syntax ();
    }

    if (filename) {
#ifdef __MSDOS__
	fp = fopen (filename, "wb");
#else
	fp = fopen (filename, "w");
#endif
	if (! fp)
	    errit ("Creating file \"%s\": %s", filename, strerror (errno));
	if (verbose)
	    fprintf (stderr, "Writing to file \"%s\"" my_NL, filename);
    } else {
	fp = stdout;
#ifdef __MSDOS__
	setmode (fileno (stdout), O_BINARY);
#endif
    }

    switch (imagetype) {
	case XBM:
	    write_xbm ();
	    break;
	case XPM:
	    write_xpm ();
	    break;
	case PBM:
	    write_pbm ();
	    break;
	case PPM:
	    write_ppm ();
	    break;
	case BMP:
	    write_bmp (FALSE);
	    break;
	case BMPWIN:
	    write_bmp (TRUE);
	    break;
    }

    if (fp != stdout)
	fclose (fp);

    return 0;
}

void write_xbm ()
{
    int
	byte,
	x,
	y,
	n;

    if (verbose)
	fprintf (stderr, "Writing XBM" my_NL);

    fprintf (
	fp,
        "#define ABClock_width %i\n"
        "#define ABClock_height %i\n"
        "static char ABClock_bits[] = {\n",
	xsize,
        ysize
    );

    n = 0;
    for (y = ysize - 1; y >= 0; y--)
        for (x = 0; x < xmapsize; x += 8) {
	    byte = (map [x    ][y] > 1 ?   1 : 0) +
		   (map [x + 1][y] > 1 ?   2 : 0) +
		   (map [x + 2][y] > 1 ?   4 : 0) +
		   (map [x + 3][y] > 1 ?   8 : 0) +
		   (map [x + 4][y] > 1 ?  16 : 0) +
		   (map [x + 5][y] > 1 ?  32 : 0) +
		   (map [x + 6][y] > 1 ?  64 : 0) +
		   (map [x + 7][y] > 1 ? 128 : 0);
	    fprintf (
		fp,
	        "0x%02x%s%s",
		byte,
		(y == 0 && x == xmapsize - 8) ? "};" : ",",
		(++n % 15) ? "" : "\n"
	    );
	}
    if (n % 15)
	fprintf (fp, "\n");
}

void write_xpm ()
{
    int
	x,
	y;
    char
        c [] = ".,#@%";

    if (verbose)
	fprintf (stderr, "Writing XPM" my_NL);

    fprintf (
	fp,
        "/* XPM */\n"
	"static char *ABClock[] = {\n"
	"/* width height num_colors chars_per_pixel */\n"
	"\"    %i    %i        5            1\",\n"
	"/* colors */\n"
	"\"%c c #%02x%02x%02x\",\n"
	"\"%c c #%02x%02x%02x\",\n"
	"\"%c c #%02x%02x%02x\",\n"
	"\"%c c #%02x%02x%02x\",\n"
	"\"%c c #%02x%02x%02x\",\n"
	"/* pixels */\n",
	xsize,
	ysize,
	c [0], color [0][0], color [0][1], color [0][2],
	c [1], color [1][0], color [1][1], color [1][2],
	c [2], color [2][0], color [2][1], color [2][2],
	c [3], color [3][0], color [3][1], color [3][2],
	c [4], color [4][0], color [4][1], color [4][2]
    );

    for (y = ysize - 1; y >= 0; y--) {
	fprintf (fp, "\"");
	for (x = 0; x < xsize; x++)
	    fprintf (fp, "%c", c [map [x][y]]);
        fprintf (fp, y ? "\",\n" : "\"\n");
    }

    fprintf (fp, "};\n");
}

void write_pbm ()
{
    int
	x,
	y;

    if (verbose)
	fprintf (stderr, "Writing PBM" my_NL);

    fprintf (
	fp,
        "P4\n"
        "%i %i\n",
	xsize,
        ysize
    );

    for (y = ysize - 1; y >= 0; y--)
        for (x = 0; x < xmapsize; x += 8)
	    fputc ((map [x + 7][y] > 1 ?   1 : 0) +
		   (map [x + 6][y] > 1 ?   2 : 0) +
		   (map [x + 5][y] > 1 ?   4 : 0) +
		   (map [x + 4][y] > 1 ?   8 : 0) +
		   (map [x + 3][y] > 1 ?  16 : 0) +
		   (map [x + 2][y] > 1 ?  32 : 0) +
		   (map [x + 1][y] > 1 ?  64 : 0) +
		   (map [x    ][y] > 1 ? 128 : 0),
		   fp);
}

void write_ppm ()
{
    int
	x,
	y;

    if (verbose)
	fprintf (stderr, "Writing PPM" my_NL);

    fprintf (
        fp,
	"P6\n"
	"%i %i\n"
	"255\n",
	xsize,
	ysize
    );

    for (y = ysize - 1; y >= 0; y--)
        for (x = 0; x < xsize; x++)
	    fprintf (
	        fp,
		"%c%c%c",
		(unsigned char) color [map [x][y]][0],
		(unsigned char) color [map [x][y]][1],
		(unsigned char) color [map [x][y]][2]
            );
}

void write_bmp (BOOL win)
{
    unsigned long
	ul;
    unsigned short
	us;
    unsigned char
        uc;
    int
	bitsperline,
	i,
	x,
	y;

    if (verbose)
	fprintf (stderr, "Writing BMP%s" my_NL, win ? " Windows" : "");

    /* file header */

    bitsperline = xsize * 4;

    if ((bitsperline % 32) != 0)
	bitsperline += (32 - (bitsperline % 32));

    fwrite ("BM", 1, 2, fp);

    ul = 14 + (win ? 40 : 12) + 16 * (win ? 4 : 3) + ysize * (bitsperline >> 3);
    fwrite (&ul, 4, 1, fp);

    us = 0;
    fwrite (&us, 2, 1, fp);
    fwrite (&us, 2, 1, fp);

    ul = 14 + (win ? 40 : 12) + 16 * (win ? 4 : 3);
    fwrite (&ul, 4, 1, fp);

    /* info header */

    if (win) {
	ul = 40;
	fwrite (&ul, 4, 1, fp);
	ul = xsize;
	fwrite (&ul, 4, 1, fp);
	ul = ysize;
	fwrite (&ul, 4, 1, fp);
	us = 1;
	fwrite (&us, 2, 1, fp);
	us = 4;
	fwrite (&us, 2, 1, fp);
	ul = 0;
	fwrite (&ul, 4, 1, fp);    
	fwrite (&ul, 4, 1, fp);    
	fwrite (&ul, 4, 1, fp);    
	fwrite (&ul, 4, 1, fp);    
	fwrite (&ul, 4, 1, fp);    
	fwrite (&ul, 4, 1, fp);
    } else {
	ul = 12;
	fwrite (&ul, 4, 1, fp);
	us = xsize;
	fwrite (&us, 2, 1, fp);
	us = ysize;
	fwrite (&us, 2, 1, fp);
	us = 1;
	fwrite (&us, 2, 1, fp);
	us = 4;
	fwrite (&us, 2, 1, fp);
    }

    /* rgb table, reverse from other bitmaps colors */

    uc = 0;
    for (i = 0; i < 5; i++) {
        fwrite (&(color [i][2]), 1, 1, fp);
        fwrite (&(color [i][1]), 1, 1, fp);
        fwrite (&(color [i][0]), 1, 1, fp);
        if (win)
	    fwrite (&uc, 1, 1, fp);
    }
    for (; i < 16; i++) {
        fwrite (&uc, 1, 1, fp);
        fwrite (&uc, 1, 1, fp);
        fwrite (&uc, 1, 1, fp);
	if (win)
	    fwrite (&uc, 1, 1, fp);
    }

    /* image data */

    for (y = 0; y < ysize; y++)
	for (x = 0; x < xmapsize; x += 2) {
	    us = 16 * ((unsigned char) map [x][y]) + (unsigned char) map [x + 1][y];
	    fwrite (&us, 1, 1, fp);
	}

}

void setup_clock ()
{
    int
	x,
	y;

    xmapsize = (xsize % 8) ? (xsize - (xsize % 8) + 8) : xsize;

    map = (unsigned char **) s_malloc (xmapsize * sizeof (unsigned char *));
    for (x = 0; x < xmapsize; x++)
	map [x] = (unsigned char *) s_malloc (ysize * sizeof (unsigned char));
    for (x = xsize; x < xmapsize; x++)
	for (y = 0; y < ysize; y++)
	    map [x][y] = 0;
}

void ABC_SetColor (int color)
{
    indexcolor = color;
}

void ABC_Rect (int x1, int y1, int x2, int y2)
{
    int
        x,
	y;

    if (x1 > x2 || y1 > y2)
	return;

    for (x = x1; x <= x2; x++)
	for (y = y1; y <= y2; y++)
	    map [x][y] = indexcolor;
}

void  testcolor (int i, char const *s)
{
    if (color [i][0] < 0 || color [i][0] > 255 ||
        color [i][1] < 0 || color [i][1] > 255 ||
        color [i][2] < 0 || color [i][2] > 255)
            errit ("Invalid color for -%s", s);
}

void testsize (int size, char const *s)
{
    if (size < ABC_MINSIZE || size > 1000)
        errit ("%s out of range, must be in [%i, 600]", s, (int) ABC_MINSIZE);
}

void process_args ()
{
    if (arg_c < 2)
	syntax ();

    if (arg_v [1][0] == '-' && arg_v [1][1] != '\0')
	syntax ();

    if (strcmp (arg_v [1], "-"))
	filename = arg_v [1];

    arg_v++;
    arg_c--;

    while (arg_c > 1 && arg_v [1][0] == '-') {
	if (! strcmp (arg_v [1], "-border")) {
	    border = atoi (get_arg ());
	    if (border < 0 || border > 200)
		errit ("Value for -border out of range");
	} else if (! strcmp (arg_v [1], "-width")) {
	    xsize = atoi (get_arg ());
	    testsize (xsize, "width");
	} else if (! strcmp (arg_v [1], "-height")) {
	    ysize = atoi (get_arg ());
	    testsize (ysize, "height");
	} else if (! strcmp (arg_v [1], "-xbm"))
	    imagetype = XBM;
	else if (! strcmp (arg_v [1], "-xpm"))
	    imagetype = XPM;
	else if (! strcmp (arg_v [1], "-pbm"))
	    imagetype = PBM;
	else if (! strcmp (arg_v [1], "-ppm"))
	    imagetype = PPM;
	else if (! strcmp (arg_v [1], "-bmp"))
	    imagetype = BMP;
	else if (! strcmp (arg_v [1], "-bmpwin"))
	    imagetype = BMPWIN;
	else if (! strcmp (arg_v [1], "-bg")) {
	    color [0][0] = atoi (get_arg ());
	    color [0][1] = atoi (get_arg ());
	    color [0][2] = atoi (get_arg ());
	    testcolor (0, "bg");
	} else if (! strcmp (arg_v [1], "-ic")) {
	    color [1][0] = atoi (get_arg ());
	    color [1][1] = atoi (get_arg ());
	    color [1][2] = atoi (get_arg ());
	    testcolor (1, "ic");
	} else if (! strcmp (arg_v [1], "-sc")) {
	    color [2][0] = atoi (get_arg ());
	    color [2][1] = atoi (get_arg ());
	    color [2][2] = atoi (get_arg ());
	    testcolor (2, "sc");
	} else if (! strcmp (arg_v [1], "-hc")) {
	    color [3][0] = atoi (get_arg ());
	    color [3][1] = atoi (get_arg ());
	    color [3][2] = atoi (get_arg ());
	    testcolor (3, "hc");
	} else if (! strcmp (arg_v [1], "-mc")) {
	    color [4][0] = atoi (get_arg ());
	    color [4][1] = atoi (get_arg ());
	    color [4][2] = atoi (get_arg ());
	    testcolor (4, "mc");
	} else if (! strcmp (arg_v [1], "-q"))
	    verbose = FALSE;
        else
	    syntax ();
	arg_c--;
	arg_v++;
    }
}

char *get_arg ()
{
    if (arg_c == 2)
        errit ("Missing argument for '%s'", arg_v [1]);

    arg_v++;
    arg_c--;
    return arg_v [1];
}

void *s_malloc (size_t size)
{
    void
	*p;

    p = malloc (size);
    if (! p) {
        free (no_mem_buffer);
	errit (out_of_memory);
    }
    return p;
}

void errit (char const *format, ...)
{
    va_list
	list;

    fprintf (stderr, my_NL "Error %s: ", programname);

    va_start (list, format);
    vfprintf (stderr, format, list);

    fprintf (stderr, my_NL my_NL);

    exit (1);
}

void get_programname (char const *argv0)
{
#ifdef __MSDOS__
    char
        name [MAXFILE];
    fnsplit (argv0, NULL, NULL, name, NULL);
    programname = strdup (name);
#else
    char
        *p;
    p = strrchr (argv0, my_PATH_SEP);
    if (p)
        programname = strdup (p + 1);
    else
        programname = strdup (argv0);
#endif    
}

void syntax ()
{
    fprintf (
	stderr,
	my_NL
	"Analogue Bitmap Clock -- version " my_VERSION my_NL
	"create image file" my_NL
	"(c) Peter Kleiweg 2000, 2002" my_NL
        my_NL
        "Usage: %s <filename> [options] [<hour> <minute> <second>]" my_NL
        my_NL
	"<filename> : use - for stdout" my_NL
        my_NL
	"If no time is specified, the current time will be used" my_NL
	my_NL
        "options:" my_NL
	"-border <int> : border width" my_NL
	"-width <int> : width" my_NL
	"-height <int> : height" my_NL
        "-xbm : create XBM image file" my_NL
        "-xpm : create XPM image file" my_NL
        "-pbm : create PBM image file" my_NL
        "-ppm : create PPM image file" my_NL
        "-bmp : create BMP image file" my_NL
        "-bmpwin : create BMP Windows image file" my_NL
        "-bg <red> <green <blue> : set background colour" my_NL
        "-hc <red> <green <blue> : set colour for hours" my_NL
        "-mc <red> <green <blue> : set colour for minutes" my_NL
        "-sc <red> <green <blue> : set border colour for square" my_NL
        "-ic <red> <green <blue> : set inside colour for square" my_NL
	"-q : quiet" my_NL
        "<red>, <green>, <blue> must be 0 - 255" my_NL
        my_NL,
	programname
    );
    exit (1);
}
