/* WBMP
 * ----
 * WBMP Level 0: B/W, Uncompressed
 * This implements the WBMP format as specified in WAPSpec 1.1 and 1.2.
 * It does not support ExtHeaders as defined in the spec. The spec states
 * that a WAP client does not need to implement ExtHeaders.
 *
 * (c) 2000 Johan Van den Brande <johan@vandenbrande.com>
 */

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

#include "gd.h"
#include "gdhelpers.h"
#include "wbmp.h"

#ifdef NOTDEF
#define __TEST  /* Compile with main function */
#define __DEBUG /* Extra verbose when with __TEST */
#define __WRITE /* readwbmp and writewbmp(stdout) */
#define __VIEW  /* view the wbmp on stdout */
#endif

/* getmbi
 * ------
 * Get a multibyte integer from a generic getin function
 * 'getin' can be getc, with in = NULL
 * you can find getin as a function just above the main function
 * This way you gain a lot of flexibility about how this package
 * reads a wbmp file.
 */
int getmbi(int (*getin)(void *in), void *in)
{
    unsigned int mbi = 0;
    int i;

    do {
        i = getin(in);
        if (i < 0) {
            return (-1);
        }
        mbi = (mbi << 7) | (i & 0x7f);
    } while (i & 0x80);

    return mbi;
}

/* putmbi
 * ------
 * Put a multibyte intgerer in some kind of output stream
 * I work here with a function pointer, to make it as generic
 * as possible. Look at this function as an iterator on the
 * mbi integers it spits out.
 *
 */
void putmbi(int i, void (*putout)(int c, void *out), void *out)
{
    int cnt, l, accu;

    /* Get number of septets */
    accu = cnt = 0;
    while (accu != i) {
        accu += i & 0x7f << 7 * cnt++;
    }

    /* Produce the multibyte output */
    for (l = cnt - 1; l > 0; l--) {
        putout(0x80 | (i & 0x7f << 7 * l) >> 7 * l, out);
    }

    putout(i & 0x7f, out);
}

/* skipheader
 * ----------
 * Skips the ExtHeader. Not needed for the moment
 */
int skipheader(int (*getin)(void *in), void *in)
{
    int i;

    do {
        i = getin(in);
        if (i < 0) {
            return (-1);
        }
    } while (i & 0x80);

    return 0;
}

/* create wbmp
 * -----------
 * create an empty wbmp
 */
Wbmp *createwbmp(int width, int height, int color)
{
    int i;
    Wbmp *wbmp;

    if ((wbmp = (Wbmp *)gdMalloc(sizeof(Wbmp))) == NULL) {
        return (NULL);
    }

    if (overflow2(sizeof(int), width)) {
        gdFree(wbmp);
        return NULL;
    }

    if (overflow2(sizeof(int) * width, height)) {
        gdFree(wbmp);
        return NULL;
    }

    if ((wbmp->bitmap = (int *)gdMalloc(sizeof(int) * width * height)) == NULL) {
        gdFree(wbmp);
        return NULL;
    }

    wbmp->width = width;
    wbmp->height = height;

    for (i = 0; i < width * height; wbmp->bitmap[i++] = color)
        ;

    return wbmp;
}

/* readwbmp
 * -------
 * Actually reads the WBMP format from an open file descriptor
 * It goes along by returning a pointer to a WBMP struct.
 */
int readwbmp(int (*getin)(void *in), void *in, Wbmp **return_wbmp)
{
    int row, col, byte, pel, pos;
    Wbmp *wbmp;

    if ((wbmp = (Wbmp *)gdMalloc(sizeof(Wbmp))) == NULL) {
        return -1;
    }

    wbmp->type = getin(in);
    if (wbmp->type != 0) {
        gdFree(wbmp);
        return -1;
    }

    if (skipheader(getin, in)) {
        gdFree(wbmp);
        return -1;
    }

    wbmp->width = getmbi(getin, in);
    if (wbmp->width == -1) {
        gdFree(wbmp);
        return -1;
    }

    wbmp->height = getmbi(getin, in);
    if (wbmp->height == -1) {
        gdFree(wbmp);
        return -1;
    }

#ifdef __DEBUG
    printf("W: %d, H: %d\n", wbmp->width, wbmp->height);
#endif

    if (overflow2(sizeof(int), wbmp->width) || overflow2(sizeof(int) * wbmp->width, wbmp->height)) {
        gdFree(wbmp);
        return -1;
    }

    if ((wbmp->bitmap = (int *)gdMalloc(sizeof(int) * wbmp->width * wbmp->height)) == NULL) {
        gdFree(wbmp);
        return -1;
    }

#ifdef __DEBUG
    printf("DATA CONSTRUCTED\n");
#endif

    pos = 0;
    for (row = 0; row < wbmp->height; row++) {
        for (col = 0; col < wbmp->width;) {
            byte = getin(in);

            for (pel = 7; pel >= 0; pel--) {
                if (col++ < wbmp->width) {
                    if (byte & 1 << pel) {
                        wbmp->bitmap[pos] = WBMP_WHITE;
                    } else {
                        wbmp->bitmap[pos] = WBMP_BLACK;
                    }
                    pos++;
                }
            }
        }
    }

    *return_wbmp = wbmp;

    return 0;
}

/* writewbmp
 * ---------
 * Write a wbmp to a file descriptor
 *
 * Why not just giving a filedescriptor to this function?
 * Well, the incentive to write this function was the complete
 * integration in gd library from www.libgd.org. They use
 * their own io functions, so the passing of a function seemed to be
 * a logic(?) decision ...
 */
int writewbmp(Wbmp *wbmp, void (*putout)(int c, void *out), void *out)
{
    int row, col;
    int bitpos, octet;

    /* Generate the header */
    putout(0, out); /* WBMP Type 0: B/W, Uncompressed bitmap */
    putout(0, out); /* FixHeaderField */

    /* Size of the image */
    putmbi(wbmp->width, putout, out);  /* width */
    putmbi(wbmp->height, putout, out); /* height */

    /* Image data */
    for (row = 0; row < wbmp->height; row++) {
        bitpos = 8;
        octet = 0;

        for (col = 0; col < wbmp->width; col++) {
            octet |= ((wbmp->bitmap[row * wbmp->width + col] == 1) ? WBMP_WHITE : WBMP_BLACK)
                     << --bitpos;
            if (bitpos == 0) {
                bitpos = 8;
                putout(octet, out);
                octet = 0;
            }
        }

        if (bitpos != 8) {
            putout(octet, out);
        }
    }

    return 0;
}

/* freewbmp
 * --------
 * gdFrees up memory occupied by a WBMP structure
 */
void freewbmp(Wbmp *wbmp)
{
    gdFree(wbmp->bitmap);
    gdFree(wbmp);
}

/* printwbmp
 * ---------
 * print a WBMP to stdout for visualisation
 */
void printwbmp(Wbmp *wbmp)
{
    int row, col;
    for (row = 0; row < wbmp->height; row++) {
        for (col = 0; col < wbmp->width; col++) {
            if (wbmp->bitmap[wbmp->width * row + col] == WBMP_BLACK) {
                putchar('#');
            } else {
                putchar(' ');
            }
        }
        putchar('\n');
    }
}

#ifdef __TEST

/* putout to file descriptor
 * -------------------------
 */
int putout(int c, void *out) { return (putc(c, (FILE *)out)); }

/* getin from file descriptor
 * --------------------------
 */
int getin(void *in) { return (getc((FILE *)in)); }

/* Main function
 * -------------
 */
int main(int argc, char *argv[])
{
    FILE *wbmp_file;
    Wbmp *wbmp;

    wbmp_file = fopen(argv[1], "rb");
    if (wbmp_file) {
        readwbmp(&getin, wbmp_file, &wbmp);
#ifdef __VIEW
#ifdef __DEBUG
        printf("\nVIEWING IMAGE\n");
#endif
        printwbmp(wbmp);
#endif
#ifdef __WRITE
#ifdef __DEBUG
        printf("\nDUMPING WBMP to STDOUT\n");
#endif
        writewbmp(wbmp, &putout, stdout);
#endif
        freewbmp(wbmp);
        fclose(wbmp_file);
    }
}
#endif
