/*
 * simple-drawing.c - demonstrate drawing of pixels, lines, arcs, etc.
 *                    on a window. All drawings are done in black color
 *                    over a white background.
 */
/* RUNS UNDER LINUX */
/* compile with:
   gcc mandx11.c -lX11 -o mandx11
*/

#include <X11/Xlib.h>

#include <stdio.h>
#include <stdlib.h>		/* getenv(), etc. */
#include <unistd.h>		/* sleep(), etc.  */

typedef int bool;

#define false 0
#define true 1

#define PIXELS_HIGH 1000
#define PIXELS_WIDE 1600
#define CENTER_PIXEL_X (PIXELS_WIDE / 2)
#define CENTER_PIXEL_Y (PIXELS_HIGH / 2)

#define DRAW_GRID 1

/*
 * function: create_simple_window. Creates a window with a white background
 *           in the given size.
 * input:    display, size of the window (in pixels), and location of the window
 *           (in pixels).
 * output:   the window's ID.
 * notes:    window is created with a black border, 2 pixels wide.
 *           the window is automatically mapped after its creation.
 */

#define MAXINTS 300

#define IN_MANDELBROT_SET(x, y) (inMandelbrot(x, y) == MAXINTS)


Window
create_simple_window(Display* display, int width, int height, int x, int y)
{
    int screen_num = DefaultScreen(display);
    int win_border_width = 2;
    Window win;

    /* create a simple window, as a direct child of the screen's */
    /* root window. Use the screen's black and white colors as   */
    /* the foreground and background colors of the window,       */
    /* respectively. Place the new window's top-left corner at   */
    /* the given 'x,y' coordinates.                              */
    win = XCreateSimpleWindow(display, RootWindow(display, screen_num),
                              x, y, width, height, win_border_width,
                              BlackPixel(display, screen_num),
                              WhitePixel(display, screen_num));

    /* make the window actually appear on the screen. */
    XMapWindow(display, win);

    /* flush all pending requests to the X server. */
    XFlush(display);

    return win;
}

GC
create_gc(Display* display, Window win, int reverse_video)
{
    GC gc;				/* handle of newly created GC.  */
    unsigned long valuemask = 0;		/* which values in 'values' to  */
    /* check when creating the GC.  */
    XGCValues values;			/* initial values for the GC.   */
    unsigned int line_width = 2;		/* line width for the GC.       */
    int line_style = LineSolid;		/* style for lines drawing and  */
    int cap_style = CapButt;		/* style of the line's edje and */
    int join_style = JoinBevel;		/*  joined lines.		*/
    int screen_num = DefaultScreen(display);

    gc = XCreateGC(display, win, valuemask, &values);
    if (gc < 0)
    {
	fprintf(stderr, "XCreateGC: \n");
    }

    /* allocate foreground and background colors for this GC. */
    if (reverse_video)
    {
        XSetForeground(display, gc, WhitePixel(display, screen_num));
        XSetBackground(display, gc, BlackPixel(display, screen_num));
    }
    else
    {
        XSetForeground(display, gc, BlackPixel(display, screen_num));
        XSetBackground(display, gc, WhitePixel(display, screen_num));
    }

    /* define the style of lines that will be drawn using this GC. */
    XSetLineAttributes(display, gc,
                       line_width, line_style, cap_style, join_style);

    /* define the fill style for the GC. to be 'solid filling'. */
    XSetFillStyle(display, gc, FillSolid);

    return gc;
}

unsigned int
inMandelbrot(long double origReal, long double origImaginaryCoefficient)
{
    unsigned int j;
    long double imagSquared, rSquared;
    long double newReal, newImaginaryCoefficient;

    for (j = 0, newReal = origReal, newImaginaryCoefficient = origImaginaryCoefficient;
         j < MAXINTS;
         j++)
    {
        rSquared = newReal * newReal;
        imagSquared = newImaginaryCoefficient * newImaginaryCoefficient;

        if (rSquared + imagSquared > 10)
	{
            break;
	}

        /* (Ii + R)(Ii + R) = (I**2)(i**2) + 2RIi + R**2 =
         * -(I**2) + 2RIi + R**2 =
         * 2RIi + R**2 - I**2
         * so the new imaginary coefficient is 2RI
         * and the new real is R**2 - I**2.
         * Then, don't forget, we add the original numbers back in.
         */

        newImaginaryCoefficient = (2 * newImaginaryCoefficient * newReal) +
            origImaginaryCoefficient;
        newReal = (rSquared - imagSquared) + origReal;
    }

    return j;
}

void
drawMand(int level,
	 long double centerUnitX,
	 long double centerUnitY,
	 long double unitsWide,
	 Display* display,
	 Window * win,
	 GC * gc)
{
    int x, y;
    long double scaleMultiplier = unitsWide/PIXELS_WIDE;
    long double real, imaginary;

    printf("level %d: X (real): %Lf to %Lf; Y (imaginary) %Lf to %Lf\n",
           level,
           ((0 - CENTER_PIXEL_X) * scaleMultiplier) + centerUnitX,
           (CENTER_PIXEL_X * scaleMultiplier) + centerUnitX,
           (CENTER_PIXEL_Y * scaleMultiplier) + centerUnitY,
           ((0 - CENTER_PIXEL_Y) * scaleMultiplier) + centerUnitY);

#if DRAW_GRID
    {
        /* draw a grid, with squares one unit by one unit */
        long double leastXunit = ((0 - CENTER_PIXEL_X) * scaleMultiplier) + centerUnitX;
        long double leastYunit = ((0 - CENTER_PIXEL_Y) * scaleMultiplier) + centerUnitY;
        long double greatestXunit = (CENTER_PIXEL_X * scaleMultiplier) + centerUnitX;
        long double greatestYunit = (CENTER_PIXEL_Y * scaleMultiplier) + centerUnitY;
        long double inverseScale = PIXELS_WIDE/unitsWide;

        long lxu = leastXunit >= 0.0 ? (long) leastXunit : -1 * ((long) (-1.0 * leastXunit));
        long lyu = leastYunit >= 0.0 ? (long) leastYunit : -1 * ((long) (-1.0 * leastYunit));
        long gxu = greatestXunit >= 0.0 ? (long) greatestXunit : -1 * ((long) (-1.0 * greatestXunit));
        long gyu = greatestYunit >= 0.0 ? (long) greatestYunit : -1 * ((long) (-1.0 * greatestYunit));
        long xu, yu;

        printf("x: %Lf to %Lf (%ld to %ld); y: %Lf to %Lf (%ld to %ld)\n",
               leastXunit, greatestXunit, lxu, gxu,
               leastYunit, greatestYunit, lyu, gyu);

        for (yu = lyu; yu <= gyu; yu++)
        {
            y = ((yu - centerUnitY) * inverseScale) + CENTER_PIXEL_Y;
            XDrawLine(display, *win, *gc, 0, PIXELS_HIGH - y, PIXELS_WIDE, PIXELS_HIGH - y);
        }

        for (xu = lxu; xu <= gxu; xu++)
        {
            x = ((xu - centerUnitX) * inverseScale) + CENTER_PIXEL_X;
            XDrawLine(display, *win, *gc, x, 0, x, PIXELS_HIGH);
        }
    } /* scoping */

#endif /* DRAW_GRID */

    for (y = 0; y < PIXELS_HIGH; y++)
    {
        for (x = 0; x < PIXELS_WIDE; x++)
	{
            real = ((x - CENTER_PIXEL_X) * scaleMultiplier) + centerUnitX;
            imaginary = ((y - CENTER_PIXEL_Y) * scaleMultiplier) + centerUnitY;

            if (IN_MANDELBROT_SET(real, imaginary))
	    {
                /* PIXELS_HIGH - y because X thinks of y as growing down,
                 * contrary to a normal Cartesian grid. */
                XDrawPoint(display, *win, *gc, x, PIXELS_HIGH - y);
	    }
	}
    }
}

void
main(int argc, char* argv[])
{
    Display* display;		/* pointer to X Display structure.           */
    int screen_num;		/* number of screen to place the window on.  */
    Window win;			/* pointer to the newly created window.      */
    unsigned int display_width,
        display_height;	/* height and width of the X display.        */
    char *display_name = getenv("DISPLAY");  /* address of the X display.      */
    GC gc;			/* GC (graphics context) used for drawing    */
				/*  in our window.			     */
    GC whiteGc;
    long double centerUnitX = 0.0;
    long double centerUnitY = 0.0;
    long double unitsWide = 3.0;
    int level = 0;

    /* open connection with the X server. */
    display = XOpenDisplay(display_name);
    if (display == NULL)
    {
        fprintf(stderr, "%s: cannot connect to X server '%s'\n",
                argv[0], display_name);
        exit(1);
    }

    /* get the geometry of the default screen for our display. */
    screen_num = DefaultScreen(display);
    display_width = DisplayWidth(display, screen_num);
    display_height = DisplayHeight(display, screen_num);

    /* create a simple window, as a direct child of the screen's   */
    /* root window. Use the screen's white color as the background */
    /* color of the window. Place the new window's top-left corner */
    /* at the given 'x,y' coordinates.                             */
    win = create_simple_window(display, PIXELS_WIDE, PIXELS_HIGH, 0, 0);

    /* allocate a new GC (graphics context) for drawing in the window. */
    gc = create_gc(display, win, 0);
    whiteGc = create_gc(display, win, 1);

    drawMand(level, centerUnitX, centerUnitY, unitsWide,
             display, &win, &gc);

    /* flush all pending requests to the X server. */
    XFlush(display);
    XSync(display, False);

    XSelectInput(display, win, ButtonPressMask);

    /* this structure will contain the event's data, once received. */
    XEvent an_event;

    /* enter an "endless" loop of handling events. */
    while (1)
    {
        XNextEvent(display, &an_event);
        switch (an_event.type)
        {
        case ButtonPress:
            /* handle this event type... */
            switch (an_event.xbutton.button)
            {
            case Button1:
                {
                    long double scaleMultiplier = unitsWide/PIXELS_WIDE;

                    XFillRectangle(display, win, whiteGc, 0, 0, PIXELS_WIDE, PIXELS_HIGH);
                    centerUnitX = centerUnitX +
                        ((int) (an_event.xbutton.x - CENTER_PIXEL_X) * scaleMultiplier);
                    centerUnitY = centerUnitY +
                        ((int) (CENTER_PIXEL_Y - an_event.xbutton.y) * scaleMultiplier);

                    unitsWide /= 2;
                    level++;
                    drawMand(level, centerUnitX, centerUnitY, unitsWide,
                             display, &win, &gc);
                    XSync(display, False);
#if 0
                    printf("level %d: button 1, x: %d y %d, centerUnitX %lf (delta %lf), centerUnitY %lf (delta %lf) scaleMultiplier %lf\n",
                           level,
                           an_event.xbutton.x, an_event.xbutton.y,
                           centerUnitX, (int) (an_event.xbutton.x - CENTER_PIXEL_X) * scaleMultiplier,
                           centerUnitY, (int) (CENTER_PIXEL_Y - an_event.xbutton.y) * scaleMultiplier,
                           scaleMultiplier); 
#endif
                }
                break;

            case Button2:
                printf("button 2\n");
                exit(0);
                break;

            case Button3:
                XFillRectangle(display, win, whiteGc, 0, 0, PIXELS_WIDE, PIXELS_HIGH);
                level--;
                printf("level %d: button 3\n", level);
                unitsWide *= 2;
                drawMand(level, centerUnitX, centerUnitY, unitsWide,
                         display, &win, &gc);
                break;

            default: /* ignore this event. */
                break;
            }

            break;
        default: /* unknown event type - ignore it. */
            break;
        }
    }

    /* make a delay for a short period. */
    sleep(4);

    /* close the connection to the X server. */
    XCloseDisplay(display);
}
