/* xscreensaver, Copyright (c) 1999 Jamie Zawinski <jwz@jwz.org>
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or
 * implied warranty.
 *
 * Matrix -- simulate the text scrolls from the movie "The Matrix".
 *
 * The movie people distribute their own Windows/Mac screensaver that does
 * a similar thing, so I wrote one for Unix.  However, that version (the
 * Windows/Mac version at http://www.whatisthematrix.com/) doesn't match my
 * memory of what the screens in the movie looked like, so my `xmatrix'
 * does things differently.
 */

#include <stdio.h>
#include "images/small.xpm"
#include "images/medium.xpm"
#include "images/large.xpm"
#include "images/matrix.xbm"
/*
#define CHAR_HEIGHT 4
*/

#include "matrix.h"
/*
#include "resources.h"
*/

extern GC NormalGC;
extern GC EraseGC;
extern Pixel back_pix, fore_pix;
extern int PixmapSize;
int CHAR_HEIGHT;

static void load_images(m_state * state)
{

	if (state->xgwa.depth > 1) {

		XpmAttributes xpmattrs;
		int result;

		xpmattrs.valuemask = 0;
		xpmattrs.valuemask |= XpmCloseness;
		xpmattrs.closeness = 40000;
		xpmattrs.valuemask |= XpmVisual;
		xpmattrs.visual = state->xgwa.visual;
		xpmattrs.valuemask |= XpmDepth;
		xpmattrs.depth = state->xgwa.depth;
		xpmattrs.valuemask |= XpmColormap;
		xpmattrs.colormap = state->xgwa.colormap;

		if (PixmapSize == 1) {

			CHAR_HEIGHT = 4;
			result = XpmCreatePixmapFromData(state->dpy, state->window, small, &state->images, 0 /* mask */ ,
											 &xpmattrs);
		} else if (PixmapSize == 2) {

			CHAR_HEIGHT = 6;
			result = XpmCreatePixmapFromData(state->dpy, state->window, medium, &state->images, 0 /* mask */ ,
											 &xpmattrs);
		} else {

			CHAR_HEIGHT = 8;
			result = XpmCreatePixmapFromData(state->dpy, state->window, large, &state->images, 0 /* mask */ ,
											 &xpmattrs);
		}

		if (!state->images || (result != XpmSuccess && result != XpmColorError))
			state->images = 0;

		state->image_width = xpmattrs.width;
		state->image_height = xpmattrs.height;
		state->nglyphs = state->image_height / CHAR_HEIGHT;

	} else {

		state->image_width = matrix_width;
		state->image_height = matrix_height;
		state->nglyphs = state->image_height / CHAR_HEIGHT;

		state->images = XCreatePixmapFromBitmapData(state->dpy, state->window,
													(char *)matrix_bits,
													state->image_width, state->image_height, back_pix, fore_pix, state->xgwa.depth);
	}

}

m_state *init_matrix(Display * dpy, Window window)
{

	m_state *state = (m_state *) calloc(sizeof(*state), 1);

	state->dpy = dpy;
	state->window = window;

	XGetWindowAttributes(dpy, window, &state->xgwa);
	load_images(state);

	state->draw_gc = NormalGC;
	state->erase_gc = EraseGC;

	state->char_width = state->image_width / 2;
	state->char_height = CHAR_HEIGHT;

	state->grid_width = state->xgwa.width / state->char_width;
	state->grid_height = state->xgwa.height / state->char_height;
	state->grid_width++;
	state->grid_height++;

	state->cells = (m_cell *) calloc(sizeof(m_cell), state->grid_width * state->grid_height);
	state->feeders = (m_feeder *) calloc(sizeof(m_feeder), state->grid_width);

	state->density = 40;

	state->insert_top_p = False;
	state->insert_bottom_p = True;

	return state;

}

static void insert_glyph(m_state * state, int glyph, int x, int y)
{

	Bool bottom_feeder_p = (y >= 0);
	m_cell *from, *to;

	if (y >= state->grid_height)
		return;

	if (bottom_feeder_p) {

		to = &state->cells[state->grid_width * y + x];

	} else {

		for (y = state->grid_height - 1; y > 0; y--) {

			from = &state->cells[state->grid_width * (y - 1) + x];
			to = &state->cells[state->grid_width * y + x];
			*to = *from;
			to->changed = True;

		}
		to = &state->cells[x];

	}

	to->glyph = glyph;
	to->changed = True;

	if (!to->glyph) ;
	else if (bottom_feeder_p)
		to->glow = 1 + (random() % 2);
	else
		to->glow = 0;

}

static void feed_matrix(m_state * state)
{

	int x;

	/*
	 *  Update according to current feeders.
	 */
	for (x = 0; x < state->grid_width; x++) {

		m_feeder *f = &state->feeders[x];

		if (f->throttle) {		/* this is a delay tick, synced to frame. */

			f->throttle--;

		} else if (f->remaining > 0) {	/* how many items are in the pipe */

			int g = (random() % state->nglyphs) + 1;
			insert_glyph(state, g, x, f->y);
			f->remaining--;
			if (f->y >= 0)
				f->y++;			/* bottom_feeder_p */

		} else {				/* if pipe is empty, insert spaces */

			insert_glyph(state, 0, x, f->y);
			if (f->y >= 0)
				f->y++;			/* bottom_feeder_p */

		}

		if ((random() % 10) == 0) {	/* randomly change throttle speed */

			f->throttle = ((random() % 5) + (random() % 5));

		}

	}

}

static int densitizer(m_state * state)
{

	/* Horrid kludge that converts percentages (density of screen coverage)
	   to the parameter that actually controls this.  I got this mapping
	   empirically, on a 1024x768 screen.  Sue me. */
	if (state->density < 10)
		return 85;
	else if (state->density < 15)
		return 60;
	else if (state->density < 20)
		return 45;
	else if (state->density < 25)
		return 25;
	else if (state->density < 30)
		return 20;
	else if (state->density < 35)
		return 15;
	else if (state->density < 45)
		return 10;
	else if (state->density < 50)
		return 8;
	else if (state->density < 55)
		return 7;
	else if (state->density < 65)
		return 5;
	else if (state->density < 80)
		return 3;
	else if (state->density < 90)
		return 2;
	else
		return 1;

}

static void hack_matrix(m_state * state)
{

	int x;

	/* Glow some characters. */
	if (!state->insert_bottom_p) {

		int i = random() % (state->grid_width / 2);
		while (--i > 0) {

			int x = random() % state->grid_width;
			int y = random() % state->grid_height;
			m_cell *cell = &state->cells[state->grid_width * y + x];
			if (cell->glyph && cell->glow == 0) {

				cell->glow = random() % 10;
				cell->changed = True;
			}

		}
	}

	/* Change some of the feeders. */
	for (x = 0; x < state->grid_width; x++) {

		m_feeder *f = &state->feeders[x];
		Bool bottom_feeder_p;

		if (f->remaining > 0)	/* never change if pipe isn't empty */
			continue;

		if ((random() % densitizer(state)) != 0)	/* then change N% of the time */
			continue;

		f->remaining = 3 + (random() % state->grid_height);
		f->throttle = ((random() % 5) + (random() % 5));

		if ((random() % 4) != 0)
			f->remaining = 0;

		if (state->insert_top_p && state->insert_bottom_p)
			bottom_feeder_p = (random() & 1);
		else
			bottom_feeder_p = state->insert_bottom_p;

		if (bottom_feeder_p)
			f->y = random() % (state->grid_height / 2);
		else
			f->y = -1;
	}
}

void draw_matrix(m_state * state, int d)
{

	int x, y;
	int count = 0;

	state->density = d;
	feed_matrix(state);
	hack_matrix(state);

	for (y = 0; y < state->grid_height; y++) {
		for (x = 0; x < state->grid_width; x++) {

			m_cell *cell = &state->cells[state->grid_width * y + x];

			if (cell->glyph)
				count++;

			if (!cell->changed)
				continue;

			if (cell->glyph == 0) {

				XFillRectangle(state->dpy, state->window, state->erase_gc,
							   x * state->char_width, y * state->char_height, state->char_width, state->char_height);
			} else {

				XCopyArea(state->dpy, state->images, state->window, state->draw_gc,
						  (cell->glow ? state->char_width : 0), (cell->glyph - 1) * state->char_height,
						  state->char_width, state->char_height, x * state->char_width, y * state->char_height);

			}

			cell->changed = False;

			if (cell->glow > 0) {

				cell->glow--;
				cell->changed = True;

			}

		}
	}

#if 0
	{
		static int i = 0;
		static int ndens = 0;
		static int tdens = 0;
		i++;
		if (i > 50) {
			int dens = (100.0 * (((double)count) / ((double)(state->grid_width * state->grid_height))));
			tdens += dens;
			ndens++;
			printf("density: %d%% (%d%%)\n", dens, (tdens / ndens));
			i = 0;
		}
	}
#endif

}