/* gfx-stuff ... Daniel Hiepler, 2007 */

#include <stdio.h>
#include <getopt.h>
#include <SDL/SDL.h>
#include "include/palette.h"
#include "include/brush.h"

#ifndef TRUE
#define TRUE 1
#endif

#ifndef FALSE
#define FALSE 0
#endif

struct graphics
{
    int width, height, depth;
    int old_x, old_y;
    int mouse_x, mouse_y, mouse_button;
    int sdl_flags;
    SDL_Surface *display;
    Uint8 *pixelbuf;
};

/* ************************************************************** */
void usage(char *program_name)
{
    printf("Usage: %s [OPTION]\n"
           "\t-r <wdith>x<height>\t--resolution\tResolution\n"
           "\t-f\t\t\t--fullscreen\tRun fullscreen\n"
           "\t-h\t\t\t--help\t\tThis help\n\n"
           "While active press key to clear screen, ESC to exit.\n",
           program_name);
}

/* ************************************************************** */
int parse_arguments(int argc, char **argv, struct graphics *gfx)
{
    int further = TRUE;
    int argument_index = 0;
    int argument;
    static struct option loptions[] = {
        {"help", 0, 0, 'h'},
        {"resolution", 1, 0, 'r'},
        {"fullscreen", 0, 0, 'f'},
        {0, 0, 0, 0}
    };

    while (TRUE) {
        argument =
            getopt_long(argc, argv, "-hr:f", loptions, &argument_index);
        if(argument == -1)
            break;

        switch (argument) {
            /*
             * display help text 
             */
        case 'h':
            usage(argv[0]);
            further = FALSE;
            break;

            /*
             * fullscreen 
             */
        case 'f':
            gfx->sdl_flags |= SDL_FULLSCREEN;
            break;

        case 'r':
            if(!optarg) {
                printf("** Please provide resolution (<WIDTH>x<HEIGHT>)\n");
                further = FALSE;
            }
            else {
                sscanf(optarg, "%dx%d", &gfx->width, &gfx->height);
                if(gfx->width <= 0 || gfx->height <= 0) {
                    printf("Invalid resolution.\n");
                    further = FALSE;
                }
            }
            break;

            /*
             * eek! this may never happen 
             */
        default:
            fprintf(stderr,
                    "**FATAL ERROR\n getopt returned character code 0%o\n",
                    argument);
            further = FALSE;
            break;
        }
    }
    return further;
}

/* ************************************************************** */
int gfx_init(struct graphics *gfx)
{
    int error = 0;

    /*
     * init SDL 
     */
    if(SDL_Init(SDL_INIT_VIDEO) < 0) {
        fprintf(stderr, "SDL-init error: \"%s\"\n", SDL_GetError());
        error = -1;
    }
    /*
     * sdl-init successful 
     */
    else {
        /*
         * Quit SDL when we exit 
         */
        atexit(SDL_Quit);

        /*
         * Set video mode 
         */
        if(!
           (gfx->display =
            SDL_SetVideoMode(gfx->width, gfx->height, gfx->depth,
                             gfx->sdl_flags))) {
            fprintf(stderr, "sdl-display open error: \"%s\"\n",
                    SDL_GetError());
            error = -2;
        }
        else {
            /*
             * Ignore key events 
             */
            // SDL_EventState(SDL_MOUSEMOTION, SDL_IGNORE);
            SDL_EventState(SDL_ACTIVEEVENT, SDL_IGNORE);

            /*
             * get screen-memory 
             */
            gfx->pixelbuf = (void *) gfx->display->pixels;

            /*
             * hide mouse-pointer 
             */
            // SDL_ShowCursor(SDL_DISABLE);

            gfx->mouse_button = 0;
            error = 0;

        }
    }
    return error;
}

/* ************************************************************** */
void blur_rect(Uint8 * buffer, int width, int x1, int y1, int x2, int y2)
{
    int x_count, y_count;
    int tmp;

    for(y_count = 1; y_count < y2 - 1; y_count++) {
        for(x_count = 2; x_count < x2 - 2; x_count++) {
            tmp = (int) buffer[y_count * width + x_count - 2];
            tmp += (int) buffer[y_count * width + x_count + 2];
            tmp += (int) buffer[y_count * width + x_count - 1];
            tmp += (int) buffer[y_count * width + x_count + 1];
            tmp += (int) buffer[(y_count - 1) * width + x_count];
            tmp += (int) buffer[(y_count + 1) * width + x_count];
            tmp /= 6;
            buffer[y_count * width + x_count] = (Uint8) tmp;
        }
    }
}

/* ************************************************************** */
int myrand(int min, int max)
{
    int val = rand();
    if(val > max)
        val %= max;
    if(val % 2 == 0)
        val = -val;
    if(val < min)
        val += abs(min);
    return val;
}

/* ************************************************************** */
void draw_brush(struct graphics *gfx, int x, int y)
{
#define BRUSH_WIDTH 16
#define BRUSH_HEIGHT 16

    int lines, cols, x_n, y_n;

    x_n = x - BRUSH_WIDTH / 2;
    y_n = y - BRUSH_HEIGHT / 2;

    /*
     * clipping 
     */
    if(x_n > 1 &&
       y_n > 1 &&
       (x_n + BRUSH_WIDTH) < gfx->width - 1 &&
       (y_n + BRUSH_HEIGHT) < gfx->height - 1) {

        Uint8 *buffer = &gfx->pixelbuf[y_n * (gfx->width) + x_n];
        for(lines = 0; lines < BRUSH_HEIGHT; lines++) {
            for(cols = 0; cols < BRUSH_WIDTH; cols++) {
                buffer[cols] |= header_data[lines * BRUSH_WIDTH + cols];
            }
            buffer += gfx->width;
        }
    }
}

/* ************************************************************** */
void drawLine(struct graphics *gfx, register int x1, register int y1,
              register int x2, register int y2)
{
    register int deltaX, deltaY;
    register int addX, addY;
    register int counter, error;

    // default step right 
    addX = 1;
    // ...and down
    addY = 1;
    // first calculate delta values
    deltaX = x2 - x1;
    deltaY = y2 - y1;

    // check for negative delta-value
    if(deltaX < 0) {
        addX = -addX;
        deltaX = -deltaX;
    }
    if(deltaY < 0) {
        addY = -addY;
        deltaY = -deltaY;
    }

    // special case for horizontal and vertical lines...
    // this will speed up things a bit in these cases
    if(deltaX == 0) {
        for(counter = deltaY; counter > 0; counter--) {
            if(y1 >= 0 && y1 <= gfx->height)
                draw_brush(gfx, x1, y1);
            y1 += addY;
        }
        return;
    }
    // horizontal line
    if(deltaY == 0) {
        for(counter = deltaX; counter > 0; counter--) {
            if(x1 <= gfx->width && x1 >= 0)
                draw_brush(gfx, x1, y1);
            x1 += addX;
        }
        return;
    }
    if(deltaX > deltaY) {
        // calc initial error-value
        error = deltaX >> 1;
        for(counter = deltaX; counter > 0; counter--) {
            // adjust error-value
            error += deltaY;
            // set pixel with clipping
            if(x1 <= gfx->width && x1 >= 0 && y1 <= gfx->height && y1 >= 0)
                draw_brush(gfx, x1, y1);
            // if error is out of range, go one line further up or down
            if(error >= deltaX) {
                error -= deltaX;
                y1 += addY;
            }
            x1 += addX;
        }
    }
    else {
        // calc initial error-value
        error = deltaY >> 1;
        for(counter = deltaY; counter >= 0; counter--) {
            // adjust error-value
            error += deltaX;
            // set pixel with clipping
            if(x1 <= gfx->width && x1 >= 0 && y1 <= gfx->height && y1 >= 0)
                draw_brush(gfx, x1, y1);
            // if error is out of range, go one line further left or right
            if(error >= deltaY) {
                error -= deltaY;
                x1 += addX;
            }
            y1 += addY;
        }
    }
}

/* ************************************************************** */
void draw_frame(struct graphics *gfx)
{
    /*
     * lock memory area 
     */
    SDL_LockSurface(gfx->display);

    /*
     * draw brush if button is down 
     */
    if(gfx->mouse_button) {
        drawLine(gfx, gfx->old_x, gfx->old_y, gfx->mouse_x, gfx->mouse_y);
        gfx->old_x = gfx->mouse_x;
        gfx->old_y = gfx->mouse_y;
        draw_brush(gfx, gfx->mouse_x, gfx->mouse_y);
    }

    /*
     * blur 
     */
    blur_rect(gfx->pixelbuf, gfx->width, 0, 0, gfx->width, gfx->height);

    /*
     * unlock memory area 
     */
    SDL_UnlockSurface(gfx->display);
    SDL_UpdateRect(gfx->display, 0, 0, 0, 0);
    // SDL_Flip(gfx->display);
}

/* **************************************************************
   ************************************************************** */
int main(int argc, char *argv[])
{
    int error;
    struct graphics gfx;
    SDL_Event event;
    SDL_Rect rect;

    /*
     * initialize default gfx-structure 
     */
    gfx.width = 1024;
    gfx.height = 768;
    gfx.depth = 8;
    gfx.sdl_flags = SDL_SWSURFACE | SDL_HWPALETTE;
    rect.x = 0;
    rect.y = 0;
    rect.w = gfx.width;
    rect.h = gfx.height;

    /*
     * parse arguments 
     */
    if(parse_arguments(argc, argv, &gfx)) {

        /*
         * initialize sdl graphic stuff 
         */
        if(gfx_init(&gfx)) {
            error = -1;
            goto m_error;
        }

        /*
         * set gimp-palette 
         */
        SDL_SetColors(gfx.display, (SDL_Color *) & header_data_cmap, 0, 256);

        /*
         * main loop 
         */
        while (1) {
            /*
             * draw frame 
             */
            draw_frame(&gfx);

            /*
             * poll events 
             */
            while (SDL_PollEvent(&event)) {
                switch (event.type) {
                    /*
                     * quit 
                     */
                case SDL_QUIT:
                    printf("Quit...\n");
                    goto m_exit;

                    /*
                     * keypress 
                     */
                case SDL_KEYDOWN:
                    if(event.key.keysym.sym == SDLK_ESCAPE)
                        goto m_exit;
                    else
                        SDL_FillRect(gfx.display, &rect, 0);
                    break;

                    /*
                     * mouse-motion 
                     */
                case SDL_MOUSEMOTION:
                    gfx.mouse_x = (int) event.motion.x;
                    gfx.mouse_y = (int) event.motion.y;
                    if(!gfx.mouse_button) {
                        gfx.old_x = gfx.mouse_x;
                        gfx.old_y = gfx.mouse_y;
                    }
                    break;

                    /*
                     * mouse-up 
                     */
                case SDL_MOUSEBUTTONUP:
                    gfx.mouse_button = 0;
                    break;

                    /*
                     * mouse-down 
                     */
                case SDL_MOUSEBUTTONDOWN:
                    gfx.mouse_button = 1;
                    break;

                    /*
                     * oOops! 
                     */
                default:
                    printf
                        ("Unknown SDL event received?! No one knows what happens from here...\n");
                    break;
                }
            }
        }
    }

  m_exit:
    SDL_ShowCursor(SDL_ENABLE);
    error = 0;

  m_error:

    return (error);
}
