/*********************************************************************
 * Colours! - A colourful demonstration of Win32 graphics using C.   *
 * Copyright (C) November 2004 Neil Fraser                           *
 * http://neil.fraser.name/                                          *
 *                                                                   *
 * 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.                     *
 *                                                                   *
 * 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 (www.gnu.org) for more details.        *
 *                                                                   *
 * Generic window code by Charles Petzold.                           *
 *   http://www.john.findlay1.btinternet.co.uk/winprog/winprog.htm   *
 * Compiles with LCC:                                                *
 *   http://www.cs.virginia.edu/~lcc-win32/                          *
 *                                                                   *
 *********************************************************************/

#include <windows.h>
#include <math.h>
#include <time.h>

int rows;
int cols;
float *grid = NULL;
float c1r, c1g, c1b;
float c2r, c2g, c2b;


/* Pick a random integer between two end values (inclusive). */
float midpoint(float end1, float end2) {
    float swap;
    if (end1 > end2) {
        swap = end1;
        end1 = end2;
        end2 = swap;
    }
    /* I'm losing much of the resolution of rand().  What's a better way? */
    return (float)(rand()%1000)/1000.0 * (end2 - end1) + end1;
}


/* Recall the value of a pixel */
float getpoint(int x, int y) {
    return *(grid + x*rows + y);
}


/* Set one pixel and record its value */
void setpoint(int x, int y, float value) {
    *(grid + x*rows + y) = value;
}


/* fillrow(row, endleft, endright) */
/* Recursively fill one row. */
/* Used to bootstrap the recursive area fill. */
/* Also used to fudge our way out of ?x2 rectangles. */
void fillrow(row, endleft, endright) {
    int middleX;
    if (endleft + 1 == endright)
        return;
    /* Set the dot in the middle. */
    middleX = (int)((endright - endleft) / 2 + endleft);
    setpoint(middleX, row, midpoint(getpoint(endleft, row), getpoint(endright, row)));
    /* Go fill the two new sub-rows */
    fillrow(row, endleft, middleX);
    fillrow(row, middleX, endright);
}


/* fillcol(col, endtop, endbottom) */
/* Recursively fill one column. */
/* Used to bootstrap the recursive area fill. */
/* Also used to fudge our way out of ?x2 rectangles. */
void fillcol(col, endtop, endbottom) {
    int middleY;
    if (endtop + 1 == endbottom)
        return;
    /* Set the dot in the middle. */
    middleY = (int)((endbottom - endtop) / 2 + endtop);
    setpoint(col, middleY, midpoint(getpoint(col, endtop), getpoint(col, endbottom)));
    /* Go fill the two new sub-columns */
    fillcol(col, endtop, middleY);
    fillcol(col, middleY, endbottom);
}


/* fillarea(endtop, endbottom, endleft, endright) */
/* Recursively fill the area. */
void fillarea(endtop, endbottom, endleft, endright) {
    int middleX, middleY;
    float centre1, centre2;
    if ((endleft + 1 == endright) && (endtop + 1 == endbottom)) {
        /* This is just a 2x2 square.  Nothing to do. */
    } else if (endleft + 1 == endright) {
        /* This is just a 2x? rectangle.  Fill in some vertical space. */
        fillcol(endright, endtop, endbottom);
    } else if (endtop + 1 == endbottom) {
        /* This is just a ?x2 rectangle.  Fill in some horizontal space. */
        fillrow(endbottom, endleft, endright);
    } else {
        /* Wide open space; something to sink our recursive teeth into... */
        /* Set the dot half way along the bottom row. */
        middleX = (int)((endright - endleft) / 2 + endleft);
        setpoint(middleX, endbottom, midpoint(getpoint(endleft, endbottom), getpoint(endright, endbottom)));

        /* Set the dot half way along the right column. */
        middleY = (int)((endbottom - endtop) / 2 + endtop);
        setpoint(endright, middleY, midpoint(getpoint(endright, endtop), getpoint(endright, endbottom)));

        /* Set the dot in the middle (midpoint of two midpoints). */
        centre1 = midpoint(getpoint(middleX, endtop), getpoint(middleX, endbottom));
        centre2 = midpoint(getpoint(endright, middleY), getpoint(endleft, middleY));
        setpoint(middleX, middleY, midpoint(centre1, centre2));

        /* Go fill the four new quadrants. */
        fillarea(endtop, middleY, endleft, middleX);
        fillarea(middleY, endbottom, endleft, middleX);
        fillarea(endtop, middleY, middleX, endright);
        fillarea(middleY, endbottom, middleX, endright);
    }
}


/* Compute the grid of pixels as well as the master colours. */
void make_grid(int x, int y) {
    /* Height and width of the image. */
    /* All values work, but 2^n+1 squares are ideal (65x65, 129x129, 257x257, etc). */
    rows = y;
    cols = x;

    /* The grid of points that make up the image. */
    if (grid != NULL)
        free(grid);
    if ((grid = (float *) malloc(cols * rows * sizeof(float)) ) == NULL) {
        /* Unable to allocate memory for image. */
        return;
    }

    /* Flip a coin to determine which diagonally opposite corners get the master colours. */
    if (rand()%2==1) {
        setpoint(0, 0, 0.0);
        setpoint(cols-1, rows-1, 1.0);
        setpoint(cols-1, 0, midpoint(0.0, 1.0));
        setpoint(0, rows-1, midpoint(0.0, 1.0));
    } else {
        setpoint(0, rows-1, 0.0);
        setpoint(cols-1, 0, 1.0);
        setpoint(0, 0, midpoint(0.0, 1.0));
        setpoint(cols-1, rows-1, midpoint(0.0, 1.0));
    }

    /* Draw the top row and left column. */
    fillrow(0, 0, cols-1);
    fillcol(0, 0, rows-1);

    /* Now we have the top edge, the left edge, and the bottom/right dot. */
    /* We are all setup to fill the remaining area. */
    fillarea(0, rows-1, 0, cols-1);

    /* RGB colour declarations for the master colours. */
    do {
        c1r = midpoint(0.0, 1.0);
        c1g = midpoint(0.0, 1.0);
        c1b = midpoint(0.0, 1.0);
        c2r = midpoint(0.0, 1.0);
        c2g = midpoint(0.0, 1.0);
        c2b = midpoint(0.0, 1.0);
        /* Keep looping until you get disimilar colours. */
    } while ((fabs(c1r - c2r) + fabs(c1g - c2g) + fabs(c1b - c2b)) < 1.0);
}


/* Display the image on screen. */
void render_image(HDC *hdc) {
    int x, y;
    int r, g, b;
    float f;

    if (grid == NULL)
        return;

    /* Draw the pixel for each grid element */
    for (y=0; y<rows; y++) {
        for (x=0; x<cols; x++) {
            f = getpoint(x, y);
            r = (int)((c1r * f + c2r * (1.0 - f))*256.0);
            g = (int)((c1g * f + c2g * (1.0 - f))*256.0);
            b = (int)((c1b * f + c2b * (1.0 - f))*256.0);
            SetPixel(*hdc, x+1, y+1, (255 << 24) | (r << 16) | (g << 8) | b);
        }
    }
}


LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) {
    static TCHAR szAppName[] = TEXT ("HelloWin");
    HWND         hwnd;
    MSG          msg;
    WNDCLASS     wndclass;

    wndclass.style         = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc   = WndProc;
    wndclass.cbClsExtra    = 0;
    wndclass.cbWndExtra    = 0;
    wndclass.hInstance     = hInstance;
    wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION);
    wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH) GetStockObject (BLACK_BRUSH);
    wndclass.lpszMenuName  = NULL;
    wndclass.lpszClassName = szAppName;

    if (!RegisterClass (&wndclass)) {
        MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR);
        return 0;
    }

    hwnd = CreateWindow(szAppName,              // window class name
                        TEXT("Colours!"),       // window caption
                        WS_OVERLAPPEDWINDOW,    // window style
                        CW_USEDEFAULT,          // initial x position
                        CW_USEDEFAULT,          // initial y position
                        515,                    // initial x size
                        259,                    // initial y size
                        NULL,                   // parent window handle
                        NULL,                   // window menu handle
                        hInstance,              // program instance handle
                        NULL);                  // creation parameters

    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    HDC         hdc;
    PAINTSTRUCT ps;
    RECT        rect;

    switch(message) {
    case WM_CREATE:
        /* Window opening */
        srand(time(0));  /* Seed the random number generator with the time */
        srand(rand());   /* Make the generator less linear */
        return 0;

    case WM_SIZE:
        /* Fall through. */
        /* A timer added here could supress repeated redraws on resizing. */
    case WM_LBUTTONDOWN:
        /* Throw out the grid and signal to rebuild it. */
        grid = NULL;
        RedrawWindow(&hwnd, NULL, NULL, RDW_INVALIDATE);
        return 0;

    case WM_PAINT:
        /* Repaint the image, calculating a new grid if none exists. */
        /* A cache could be added here to speed up subsequent repaints. */
        hdc = BeginPaint(hwnd, &ps);

        GetClientRect(hwnd, &rect);
        SetCursor(LoadCursor(NULL, IDC_WAIT));
        if (grid == NULL)
            make_grid(rect.right - rect.left - 2, rect.bottom - rect.top - 2);
        render_image(hdc);
        SetCursor(LoadCursor(NULL, IDC_ARROW));
        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        /* Window closing. */
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

