#ifdef _WIN32
#define _USE_MATH_DEFINES
#include <windows.h>
#define GLUT_DISABLE_ATEXIT_HACK
#endif /* _WIN32 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <GL/glut.h>

/* 
 * Window properties 
 */
#define WINDOW_X        100
#define WINDOW_Y        100
#define WINDOW_TITLE    "RGB Dither"

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

#define MAGIC_OFFSET    .375

typedef struct {
    float red, green, blue;
} rgb_color;

typedef struct {
    int width, height;
    int num_colors;
    int chars_per_pixel;
    int first_line, last_line;
    rgb_color *colors;
    rgb_color **pixels;
} xpm_image;

/*
 * Function prototypes
 */
void usage(void);
void draw(void);
void init(int argc, char **argv, void (*draw)(void));

int xpm_c2num(char c);
int xpm_s2num(char *s, int digits);
int xpm_cpci(int i);
int xpm_colors(char **file, xpm_image *image);
int xpm_open(char **file, xpm_image *info);
int xpm_close(xpm_image *image);

/*
 * Global variables
 */
xpm_image image;

int main(int argc, char **argv) {
#include "quallen.xpm"
    if (!xpm_open(quallen, &image)) {
        return 1;
    }

    /*
     * Init OpenGL and enter the event loop
     */
    init(argc, argv, draw);

    return 0;
}

/*
 * Draws the scene
 */
void draw(void) {
    rgb_color color;
    int x, y;

    glClear(GL_COLOR_BUFFER_BIT);

    glBegin(GL_POINTS);

    for (x = 0; x < image.width; x++) {
        for (y = 0; y < image.height; y++) {
            color = image.pixels[x][y];
            glColor3f(color.red, color.green, color.blue);
            glVertex2f(x + MAGIC_OFFSET, y + MAGIC_OFFSET);
        }
    }

    glEnd();

    glFlush();
}

/* 
 * Output some usage information
 */
void usage(void) {
    fprintf(stderr, "usage: peano iterations window_height\n");
    exit(1);
}

/*
 * Initialize the OpenGL machine
 */
void init(int argc, char **argv, void (*func)(void)) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB);
    glutInitWindowSize(image.width, image.height);
    glutInitWindowPosition(WINDOW_X, WINDOW_Y);
    glutCreateWindow(WINDOW_TITLE);
    glClearColor(0.0, 0.0, 0.0, 0.0);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, image.width, 0, image.height, -1, 1);

    glutDisplayFunc(func);
    glutMainLoop();

    /* Not reached */
}


/*
 * Converts the given XPM character to number
 */
int xpm_c2num(char c) {
    if (c == '.') {
        return 0;
    } else if (c == '#') {
        return 1;
    } else if (c >= 'a' && c <= 'z') {
        return c - 'a' + xpm_c2num('#') + 1;
    } else if (c >= 'A' && c <= 'Z') {
        return c - 'A' + xpm_c2num('z') + 1;
    } else if (c >= '0' && c <= '9') {
        return c - '0' + xpm_c2num('Z') + 1;
    } else {
        fprintf(stderr, "xpm_c2num failed: %c\n", c);
        exit(1);
    }
}

/*
 * Converts the given XPM string to a number
 */
int xpm_s2num(char *s, int digits) {
    int i, num = 0;

    for (i = 0; i < digits; i++) {
        num = (num << 6) + xpm_c2num(s[i]); 
    }

    return num;
}

/*
 * Returns the number of chars per color index
 */
int xpm_cpci(int i) {
    int n = 0;

    while (i) {
        i >>= 6;
        n++;
    }

    return n;
}

/*
 * Construct a colortable for the given XPM image
 */
int xpm_colors(char **file, xpm_image *image) {
    int i, n, val, color_num;
    char *p;

    if (!(image->colors = malloc(image->num_colors * sizeof(rgb_color)))) {
        return 0;
    }

    n = xpm_cpci(image->num_colors);

    for (i = image->num_colors; i > 0; i--) {
        /* Only works for RGB XPM images */
        if ((p = strrchr(file[i], '#'))) {
            if (sscanf(p + 1, "%x", &val) != 1) {
                return 0;
            }
        } else {
            return 0;
        }
        color_num = xpm_s2num(file[i], n);
        image->colors[color_num].red = ((val & 0xff0000) >> 16)/256.0;
        image->colors[color_num].green = ((val & 0x00ff00) >> 8)/256.0;
        image->colors[color_num].blue = (val & 0x0000ff)/256.0;
    }

    return 1;
}

/*
 * Creates an XPM image
 */
int xpm_open(char **file, xpm_image *image) {
    int cpp, i, j, x, y;

    if (sscanf(file[0], "%i%i%i%i", &image->width, &image->height, 
        &image->num_colors, &image->chars_per_pixel) != 4) {
        return 0;
    }

    image->first_line = image->num_colors + 1;
    image->last_line = image->first_line + image->height - 1;

    if (!xpm_colors(file, image)) {
        return 0;
    }

    if (!(image->pixels = malloc(image->width * sizeof(rgb_color *)))) {
        return 0;
    }

    for (i = 0; i < image->width; i++) {
        if (!(image->pixels[i] = malloc(image->height * sizeof(rgb_color)))) {
            return 0;
        }
    }

    cpp = image->chars_per_pixel;

    y = image->height - 1;
    for (i = image->first_line; i <= image->last_line; i++) {
        x = image->width - 1;
        for (j = (image->width - 1) * cpp; j >= 0; j -= cpp) {
            image->pixels[x][y] = image->colors[xpm_s2num(&file[i][j], cpp)];
            x--;
        }
        y--;
    }

    return 1;
}

/*
 * Destroys an XPM image
 */
int xpm_close(xpm_image *image) {
    int i;

    for (i = 0; i < image->height; i++) {
        free(image->pixels[i]);
    }

    free(image->pixels);
    free(image->colors);

    return 1;
}

