/*
 *  Copyright (C) 2022 - Vito Caputo - <vcaputo@pengaru.com>
 *
 *  This program is free software: you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License version 2 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 for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/* The impetus for adding this is a desire for adding a variety of shapes
 * to modules/checkers.  I had started open-coding shapes like circle,
 * rhombus, pinwheel, and star, directly into checkers with a new style=
 * setting for choosing which to use instead of the plain filled square.
 *
 * But it seemed silly to bury this directly in checkers, when checkers
 * could trivially call into another module for rendering the filled
 * fragment.  And as the shapes became more interesting like star/pinwheel,
 * it also became clear that parameterizing them to really take advantage
 * of their procedural implementation would be a lot of fun.  Exposing
 * those parameters only as checkers settings, and knobs once available,
 * only within checkers, seemed like it'd be really selling things short.
 *
 * So here we are, shapes is its own module, it's kind of boring ATM.  Its
 * addition will likely be followed by checkers getting a filler module
 * setting, which could then invoke shapes - or any other module.
 *
 * TODO:
 *
 * - Add more interesting shapes
 *
 * - Parameterize more things, stuff like twist for the radial shapes
 *   comes to mind.  Twist at glance seems substantially complicated
 *   actually, since things are no longer just pinched/stretch circles with
 *   a single radial test to check.  It's like the non-convex polygon
 *   problem...
 *
 * - Go threaded, for ease of implementation this is currently simple
 *   non-threaded code.  In the checkers use case, the individual checkers
 *   are already being rendered concurrently, so as-is this still becomes
 *   threaded there.  It's just full-frame shapes situations where it
 *   hurts.
 *
 */


#include <assert.h>
#include <errno.h>
#include <math.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

#include "til.h"
#include "til_fb.h"
#include "til_module_context.h"

#define SHAPES_DEFAULT_TYPE		SHAPES_TYPE_PINWHEEL
#define SHAPES_DEFAULT_SCALE		1
#define SHAPES_DEFAULT_POINTS		5
#define SHAPES_DEFAULT_SPIN		.1
#define SHAPES_DEFAULT_PINCH		0
#define SHAPES_DEFAULT_PINCH_SPIN	.5
#define SHAPES_DEFAULT_PINCHES		2

#define SHAPES_SPIN_BASE		.0025f

typedef struct shapes_radcache_t shapes_radcache_t;

typedef enum shapes_type_t {
	SHAPES_TYPE_CIRCLE,
	SHAPES_TYPE_PINWHEEL,
	SHAPES_TYPE_RHOMBUS,
	SHAPES_TYPE_STAR,
} shapes_type_t;

typedef struct shapes_setup_t {
	til_setup_t		til_setup;
	shapes_type_t		type;
	float			scale;
	float			pinch;
	float			pinch_spin;
	unsigned		n_pinches;
	unsigned		n_points;
	float			spin;
} shapes_setup_t;

typedef struct shapes_context_t {
	til_module_context_t	til_module_context;
	shapes_setup_t		*setup;
	shapes_radcache_t	*radcache;
} shapes_context_t;

struct shapes_radcache_t {
	shapes_radcache_t	*next, *prev;
	unsigned		width, height;
	unsigned		refcount;
	unsigned		initialized:1;
	float			rads[];
};

static struct {
	shapes_radcache_t	*head;
	pthread_mutex_t		lock;
} shapes_radcache_list = { .lock = PTHREAD_MUTEX_INITIALIZER };


static void * shapes_radcache_unref(shapes_radcache_t *radcache)
{
	if (!radcache)
		return NULL;

	if (__sync_fetch_and_sub(&radcache->refcount, 1) == 1) {

		pthread_mutex_lock(&shapes_radcache_list.lock);
		if (radcache->prev)
			radcache->prev->next = radcache->next;
		else
			shapes_radcache_list.head = radcache->next;

		if (radcache->next)
			radcache->next->prev = radcache->prev;
		pthread_mutex_unlock(&shapes_radcache_list.lock);

		free(radcache);
	}

	return NULL;
}


static shapes_radcache_t * shapes_radcache_find(unsigned width, unsigned height)
{
	shapes_radcache_t	*radcache;

	pthread_mutex_lock(&shapes_radcache_list.lock);
	for (radcache = shapes_radcache_list.head; radcache; radcache = radcache->next) {
		if (radcache->width == width &&
		    radcache->height == height) {
			/* if we race with removal, refcount will be zero and we can't use it */
			if (!__sync_fetch_and_add(&radcache->refcount, 1))
				radcache = NULL;
			break;
		}
	}
	pthread_mutex_unlock(&shapes_radcache_list.lock);

	return radcache;
}


static shapes_radcache_t * shapes_radcache_new(unsigned width, unsigned height)
{
	size_t			size = width * height;
	shapes_radcache_t	*radcache;

	radcache = malloc(sizeof(shapes_radcache_t) + size * sizeof(radcache->rads[0]));
	assert(radcache);
	radcache->initialized = 0;
	radcache->width = width;
	radcache->height = height;
	radcache->refcount = 1;
	radcache->prev = NULL;

	pthread_mutex_lock(&shapes_radcache_list.lock);
	radcache->next = shapes_radcache_list.head;
	if (radcache->next)
		radcache->next->prev = radcache;
	pthread_mutex_unlock(&shapes_radcache_list.lock);

	return radcache;
}


static til_module_context_t * shapes_create_context(const til_module_t *module, til_stream_t *stream, unsigned seed, unsigned ticks, unsigned n_cpus, til_setup_t *setup)
{
	shapes_context_t	*ctxt;

	ctxt = til_module_context_new(module, sizeof(shapes_context_t), stream, seed, ticks, n_cpus, setup);
	if (!ctxt)
		return NULL;

	ctxt->setup = (shapes_setup_t *)setup;

	return &ctxt->til_module_context;
}


static void shapes_destroy_context(til_module_context_t *context)
{
	shapes_context_t	*ctxt = (shapes_context_t *)context;

	shapes_radcache_unref(ctxt->radcache);
}


static void shapes_prepare_frame(til_module_context_t *context, til_stream_t *stream, unsigned ticks, til_fb_fragment_t **fragment_ptr, til_frame_plan_t *res_frame_plan)
{

	*res_frame_plan = (til_frame_plan_t){ .fragmenter = til_fragmenter_slice_per_cpu };

	/* TODO:
	 * I've implemented this ad-hoc here for shapes, but I think there's a case to be made that
	 * such caching should be generalized and added to til_stream_t in a generalized manner.
	 *
	 * So shapes should be able to just register a cache of arbitrary type and dimensions with
	 * some identifier which can then be discovered by shapes and others via that potentially
	 * well-known identifier.
	 *
	 * In a sense this is just a prototype of what part of that might look like... it's pretty clear
	 * that something like "atan2() of every pixel coordinate in a centered origin coordinate system"
	 * could have cached value to many modules
	 */
	{ /* radcache maintenance */
		til_fb_fragment_t	*fragment = *fragment_ptr;
		shapes_context_t	*ctxt = (shapes_context_t *)context;
		shapes_radcache_t	*radcache = ctxt->radcache;

		if (radcache &&
		    (radcache->width != fragment->frame_width ||
		     radcache->height != fragment->frame_height))
			radcache = ctxt->radcache = shapes_radcache_unref(radcache);

		if (!radcache)
			radcache = shapes_radcache_find(fragment->frame_width, fragment->frame_height);

		if (!radcache)
			radcache = shapes_radcache_new(fragment->frame_width, fragment->frame_height);

		ctxt->radcache = radcache;
	}
}


/* simple approximation taken from https://mazzo.li/posts/vectorized-atan2.html */
static inline float atan_scalar_approximation(float x) {
	/* TODO: this is probably more terms/precision than needed, but when I try just dropping some,
	 * the circle+slight pinches gets artifacts.  So leaving for now, probably needs slightly different
	 * values for fewer terms.
	 */
	float	a1  =  0.99997726f;
	float	a3  = -0.33262347f;
	float	a5  =  0.19354346f;
	float	a7  = -0.11643287f;
	float	a9  =  0.05265332f;
	float	a11 = -0.01172120f;
	float	x_sq = x*x;

	return x * (a1 + x_sq * (a3 + x_sq * (a5 + x_sq * (a7 + x_sq * (a9 + x_sq * a11)))));
}


static float atan2_approx(float y, float x) {
	// Ensure input is in [-1, +1]
	int swap = fabs(x) < fabs(y);
	float atan_input = (swap ? x : y) / (swap ? y : x);

	// Approximate atan
	float res = atan_scalar_approximation(atan_input);

	// If swapped, adjust atan output
	res = swap ? (atan_input >= 0.0f ? M_PI_2 : -M_PI_2) - res : res;

	// Adjust quadrants
	if (x <  0.0f && y >= 0.0f)
		res =  M_PI + res; // 2nd quadrant
	else if (x <  0.0f && y <  0.0f)
		 res = -M_PI + res; // 3rd quadrant

	return res;
}


static void shapes_render_fragment(til_module_context_t *context, til_stream_t *stream, unsigned ticks, unsigned cpu, til_fb_fragment_t **fragment_ptr)
{
	shapes_context_t	*ctxt = (shapes_context_t *)context;
	til_fb_fragment_t	*fragment = *fragment_ptr;

	unsigned		size = MIN(fragment->frame_width, fragment->frame_height) * ctxt->setup->scale;
	unsigned		xoff = (fragment->frame_width - size) >> 1;
	unsigned		yoff = (fragment->frame_height - size) >> 1;
	unsigned		yskip = (fragment->y > yoff ? (fragment->y - yoff) : 0);
	unsigned		xskip = (fragment->x > xoff ? (fragment->x - xoff) : 0);
	unsigned		ystart = MAX(fragment->y, yoff), yend = MIN(yoff + size, fragment->y + fragment->height);
	unsigned		xstart = MAX(fragment->x, xoff), xend = MIN(xoff + size, fragment->x + fragment->width);
	shapes_radcache_t	*radcache = ctxt->radcache;
	float			*rads = radcache->rads;

	if (!fragment->cleared) {
		/* when {letter,pillar}boxed we need to clear the padding */
		if (xoff > fragment->x) {
			for (int y = fragment->y; y < fragment->y + fragment->height; y++) {
				for (int x = fragment->x; x < xoff; x++)
					til_fb_fragment_put_pixel_unchecked(fragment, 0, x, y, 0x0);
				for (int x = fragment->frame_width - (size + xoff); x < fragment->x + fragment->width; x++)
					til_fb_fragment_put_pixel_unchecked(fragment, 0, x, y, 0x0);
			}
		}

		if (yoff > fragment->y) {
			for (int y = fragment->y; y < yoff; y++)
				for (int x = fragment->x; x < fragment->x + fragment->width; x++)
					til_fb_fragment_put_pixel_unchecked(fragment, 0, x, y, 0x0);

			for (int y = fragment->frame_height - (size + yoff); y < fragment->y + fragment->height; y++)
				for (int x = fragment->x; x < fragment->x + fragment->width; x++)
					til_fb_fragment_put_pixel_unchecked(fragment, 0, x, y, 0x0);
		}
	}

	/* eventually these should probably get broken out into functions,
	 * but it's not too unwieldy for now.
	 */
	switch (ctxt->setup->type) {
	case SHAPES_TYPE_CIRCLE: {
		int	r_sq = (size >> 1) * (size >> 1);
		float	s = 2.f / (float)size;
		float	XX, YY, YYY;
		int	X, Y;
		float	n_pinches, pinch, pinch_s;

		n_pinches = ctxt->setup->n_pinches;
		pinch_s = ctxt->setup->pinch;
		pinch = (float)ticks * ctxt->setup->pinch_spin * SHAPES_SPIN_BASE;

		YY = -1.f + yskip * s;
		Y = -(size >> 1) + yskip;
		for (unsigned y = ystart; y < yend; y++, Y++, YY += s) {
			XX = -1.f + xskip * s;
			X = -(size >> 1) + xskip;
			YYY = Y * Y;
			if (!radcache->initialized) {
				for (unsigned x = xstart; x < xend; x++, X++, XX += s) {
					float	a = rads[y * radcache->width + x] = atan2_approx(YY, XX);

					if (YYY+X*X < r_sq * (1.f - fabsf(cosf(n_pinches * a + pinch)) * pinch_s))
						til_fb_fragment_put_pixel_unchecked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x, y, 0xffffffff); /* TODO: stop relying on checked for clipping */
					else if (!fragment->cleared)
						til_fb_fragment_put_pixel_unchecked(fragment, 0, x, y, 0x0);

				}
			} else {
				float	*rads = radcache->rads;
				for (unsigned x = xstart; x < xend; x++, X++, XX += s) {
					float	a = rads[y * radcache->width + x];

					if (YYY+X*X < r_sq * (1.f - fabsf(cosf(n_pinches * a + pinch)) * pinch_s))
						til_fb_fragment_put_pixel_unchecked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x, y, 0xffffffff); /* TODO: stop relying on checked for clipping */
					else if (!fragment->cleared)
						til_fb_fragment_put_pixel_unchecked(fragment, 0, x, y, 0x0);

				}
			}
		}
		break;
	}

	case SHAPES_TYPE_PINWHEEL: {
		float	s = 2.f / (float)size;
		float	XX, YY, YYYY, pinch, spin, pinch_s;
		float	n_points, n_pinches;

		n_points = ctxt->setup->n_points;
		n_pinches = ctxt->setup->n_pinches;
		pinch_s = ctxt->setup->pinch;
		spin = (float)ticks * ctxt->setup->spin * SHAPES_SPIN_BASE;
		pinch = (float)ticks * ctxt->setup->pinch_spin * SHAPES_SPIN_BASE;

		YY = -1.f + yskip * s;
		for (unsigned y = ystart; y < yend; y++, YY += s) {
			XX = -1.f + xskip * s;
			YYYY = YY * YY;
			if (!radcache->initialized) {
				for (unsigned x = xstart; x < xend; x++, XX += s) {
					float	a = rads[y * radcache->width + x] = atan2_approx(YY, XX);
					float	r = cosf(n_points * (a + spin)) * .5f + .5f;

					r *= 1.f - fabsf(cosf(n_pinches * (a + pinch))) * pinch_s;

					if (XX * XX + YYYY < r * r)
						til_fb_fragment_put_pixel_unchecked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x, y, 0xffffffff);
					else if (!fragment->cleared)
						til_fb_fragment_put_pixel_unchecked(fragment, 0, x, y, 0x0);

				}
			} else {
				for (unsigned x = xstart; x < xend; x++, XX += s) {
					float	a = rads[y * radcache->width + x];
					float	r = cosf(n_points * (a + spin)) * .5f + .5f;

					r *= 1.f - fabsf(cosf(n_pinches * (a + pinch))) * pinch_s;

					if (XX * XX + YYYY < r * r)
						til_fb_fragment_put_pixel_unchecked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x, y, 0xffffffff);
					else if (!fragment->cleared)
						til_fb_fragment_put_pixel_unchecked(fragment, 0, x, y, 0x0);

				}
			}
		}
		break;
	}

	case SHAPES_TYPE_RHOMBUS: {
		float	s = 2.f / (float)size;
		int	r = (size >> 1);
		float	XX, YY;
		int	X, Y;
		float	n_pinches, pinch, pinch_s;

		n_pinches = ctxt->setup->n_pinches;
		pinch_s = ctxt->setup->pinch;
		pinch = (float)ticks * ctxt->setup->pinch_spin * SHAPES_SPIN_BASE;

		YY = -1.f + yskip * s;
		Y = -(size >> 1) + yskip;
		for (unsigned y = ystart; y < yend; y++, Y++, YY += s) {
			float	*rads = radcache->rads;
			XX = -1.f + xskip * s;
			X = -(size >> 1) + xskip;
			if (!radcache->initialized) {
				for (unsigned x = xstart; x < xend; x++, X++, XX += s) {
					float	a = rads[y * radcache->width + x] = atan2_approx(YY, XX);

					if (abs(Y) + abs(X) < r * (1.f - fabsf(cosf(n_pinches * a + pinch)) * pinch_s))
						til_fb_fragment_put_pixel_unchecked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x, y, 0xffffffff);
					else if (!fragment->cleared)
						til_fb_fragment_put_pixel_unchecked(fragment, 0, x, y, 0x0);

				}
			} else {
				for (unsigned x = xstart; x < xend; x++, X++, XX += s) {
					float	a = rads[y * radcache->width + x];

					if (abs(Y) + abs(X) < r * (1.f - fabsf(cosf(n_pinches * a + pinch)) * pinch_s))
						til_fb_fragment_put_pixel_unchecked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x, y, 0xffffffff);
					else if (!fragment->cleared)
						til_fb_fragment_put_pixel_unchecked(fragment, 0, x, y, 0x0);

				}
			}
		}
		break;
	}

	case SHAPES_TYPE_STAR: {
		float	s = 2.f / (float)size;
		float	XX, YY, YYYY, pinch, spin, pinch_s;
		float	n_points, n_pinches;

		n_points = ctxt->setup->n_points;
		n_pinches = ctxt->setup->n_pinches;
		pinch_s = ctxt->setup->pinch;
		spin = (float)ticks * ctxt->setup->spin * SHAPES_SPIN_BASE;
		pinch = (float)ticks * ctxt->setup->pinch_spin * SHAPES_SPIN_BASE;

		YY = -1.f + yskip * s;
		for (unsigned y = ystart; y < yend; y++, YY += s) {
			XX = -1.f + xskip * s;
			YYYY = YY * YY;
			if (!radcache->initialized) {
				for (unsigned x = xstart; x < xend; x++, XX += s) {
					float	a = rads[y * radcache->width + x] = atan2_approx(YY, XX);
					float	r = (M_2_PI * asinf(sinf(n_points * (a + spin)) * .5f + .5f)) * .5f + .5f;
						/*   ^^^^^^^^^^^^^^^^^^^ approximates a triangle wave */

					r *= 1.f - fabsf(cosf(n_pinches * a + pinch)) * pinch_s;

					if (XX * XX + YYYY < r * r)
						til_fb_fragment_put_pixel_unchecked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x, y, 0xffffffff);
					else if (!fragment->cleared)
						til_fb_fragment_put_pixel_unchecked(fragment, 0, x, y, 0x0);
				}
			} else {
				float	*rads = radcache->rads;
				for (unsigned x = xstart; x < xend; x++, XX += s) {
					float	a = rads[y * radcache->width + x];
					float	r = (M_2_PI * asinf(sinf(n_points * (a + spin)) * .5f + .5f)) * .5f + .5f;
						/*   ^^^^^^^^^^^^^^^^^^^ approximates a triangle wave */

					r *= 1.f - fabsf(cosf(n_pinches * a + pinch)) * pinch_s;

					if (XX * XX + YYYY < r * r)
						til_fb_fragment_put_pixel_unchecked(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, x, y, 0xffffffff);
					else if (!fragment->cleared)
						til_fb_fragment_put_pixel_unchecked(fragment, 0, x, y, 0x0);
				}
			}
		}
		break;
	}
	}
}


static void shapes_finish_frame(til_module_context_t *context, til_stream_t *stream, unsigned int ticks, til_fb_fragment_t **fragment_ptr)
{
	shapes_context_t	*ctxt = (shapes_context_t *)context;

	/* XXX: note that in rendering, initialized is checked racily and it's entirely possible
	 * for multiple contexts to be rendering and populating the radcache when !initialized
	 * simultaneously... but since they'd be producing identical data for the cache anyways,
	 * it seems mostly harmless for now.  What should probably be done is make initialized a
	 * tri-state that's atomically advanced towards initialized wiht an "intializing" mid-state
	 * that only one renderer can enter, then the others treat "initializing" as !radcache at all
	 * TODO FIXME
	 */
	ctxt->radcache->initialized = 1;
}


static int shapes_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup)
{
	const char	*type;
	const char	*points;
	const char	*spin;
	const char	*scale;
	const char	*pinch;
	const char	*pinch_spin;
	const char	*pinches;
	const char	*type_values[] = {
				"circle",
				"pinwheel",
				"rhombus",
				"star",
				NULL
			};
	const char	*points_values[] = {
				"3",
				"4",
				"5",
				"6",
				"7",
				"8",
				"9",
				"10",
				"11",
				"12",
				"13",
				"14",
				"15",
				"16",
				"17",
				"18",
				"19",
				"20",
				NULL
			};
	const char	*spin_values[] = {
				"-1",
				"-.9",
				"-.75",
				"-.5",
				"-.25",
				"-.1",
				"-.01",
				"0",
				".01",
				".1",
				".25",
				".5",
				".75",
				".9",
				"1",
				NULL
			};
	const char	*scale_values[] = {
				/* It's unclear to me if this even makes sense, but I can see some
				 * value in permitting something of a margin to exist around the shape.
				 * For that reason I'm not going smaller than 50%.
				 */
				".5",
				".66",
				".75",
				".9",
				"1",
				NULL
			};
	const char	*pinch_values[] = {
				"0",
				".1",
				".25",
				".33",
				".5",
				".66",
				".75",
				".9",
				"1",
				NULL
			};
	const char	*pinches_values[] = {
				"1",
				"2",
				"3",
				"4",
				"5",
				"6",
				"7",
				"8",
				"9",
				"10",
				NULL
			};
	int		r;

	r = til_settings_get_and_describe_value(settings,
						&(til_setting_spec_t){
							.name = "Shape type",
							.key = "type",
							.regex = "[a-zA-Z]+",
							.preferred = type_values[SHAPES_DEFAULT_TYPE],
							.values = type_values,
							.annotations = NULL
						},
						&type,
						res_setting,
						res_desc);
	if (r)
		return r;

	r = til_settings_get_and_describe_value(settings,
						&(til_setting_spec_t){
							.name = "Scaling factor",
							.key = "scale",
							.regex = "(1|0?\\.[0-9]{1,2})",
							.preferred = TIL_SETTINGS_STR(SHAPES_DEFAULT_SCALE),
							.values = scale_values,
							.annotations = NULL
						},
						&scale,
						res_setting,
						res_desc);
	if (r)
		return r;

	r = til_settings_get_and_describe_value(settings,
						&(til_setting_spec_t){
							.name = "Pinch factor",
							.key = "pinch",
							.regex = "(1|0?\\.[0-9]{1,2})",
							.preferred = TIL_SETTINGS_STR(SHAPES_DEFAULT_PINCH),
							.values = pinch_values,
							.annotations = NULL
						},
						&pinch,
						res_setting,
						res_desc);
	if (r)
		return r;

	if (strcasecmp(pinch, "0")) {
		r = til_settings_get_and_describe_value(settings,
							&(til_setting_spec_t){
								.name = "Pinch spin factor",
								.key = "pinch_spin",
								.regex = "-?(0|1|0?\\.[0-9]{1,2})",
								.preferred = TIL_SETTINGS_STR(SHAPES_DEFAULT_PINCH_SPIN),
								.values = spin_values,
								.annotations = NULL
							},
							&pinch_spin,
							res_setting,
							res_desc);
		if (r)
			return r;

		r = til_settings_get_and_describe_value(settings,
							&(til_setting_spec_t){
								.name = "Number of pinches",
								.key = "pinches",
								.regex = "[0-9]+",
								.preferred = TIL_SETTINGS_STR(SHAPES_DEFAULT_PINCHES),
								.values = pinches_values,
								.annotations = NULL
							},
							&pinches,
							res_setting,
							res_desc);
		if (r)
			return r;
	}

	if (!strcasecmp(type, "star") || !strcasecmp(type, "pinwheel")) {
		r = til_settings_get_and_describe_value(settings,
							&(til_setting_spec_t){
								.name = "Number of points",
								.key = "points",
								.regex = "[0-9]+",
								.preferred = TIL_SETTINGS_STR(SHAPES_DEFAULT_POINTS),
								.values = points_values,
								.annotations = NULL
							},
							&points,
							res_setting,
							res_desc);
		if (r)
			return r;

		r = til_settings_get_and_describe_value(settings,
							&(til_setting_spec_t){
								.name = "Spin factor",
								.key = "spin",
								.regex = "-?(0|1|0?\\.[0-9]{1,2})", /* Derived from pixbounce, I'm sure when regexes start getting actually applied we're going to have to revisit all of these and fix them with plenty of lols. */
								.preferred = TIL_SETTINGS_STR(SHAPES_DEFAULT_SPIN),
								.values = spin_values,
								.annotations = NULL
							},
							&spin,
							res_setting,
							res_desc);
		if (r)
			return r;
	}

	if (res_setup) {
		int	i;

		shapes_setup_t	*setup;

		setup = til_setup_new(settings, sizeof(*setup), NULL);
		if (!setup)
			return -ENOMEM;

		for (i = 0; type_values[i]; i++) {
			if (!strcasecmp(type, type_values[i])) {
				setup->type = i;
				break;
			}
		}

		if (!type_values[i]) {
			til_setup_free(&setup->til_setup);
			return -EINVAL;
		}

		sscanf(scale, "%f", &setup->scale); /* TODO: -EINVAL parse errors */
		sscanf(pinch, "%f", &setup->pinch); /* TODO: -EINVAL parse errors */
		if (setup->pinch != 0) {
			sscanf(pinch_spin, "%f", &setup->pinch_spin); /* TODO: -EINVAL parse errors */
			sscanf(pinches, "%u", &setup->n_pinches); /* TODO: -EINVAL parse errors */
		}

		if (setup->type == SHAPES_TYPE_STAR || setup->type == SHAPES_TYPE_PINWHEEL) {
			sscanf(points, "%u", &setup->n_points); /* TODO: -EINVAL parse errors */
			sscanf(spin, "%f", &setup->spin); /* TODO: -EINVAL parse errors */
		}

		*res_setup = &setup->til_setup;
	}

	return 0;
}


til_module_t	shapes_module = {
	.create_context = shapes_create_context,
	.destroy_context = shapes_destroy_context,
	.prepare_frame = shapes_prepare_frame,
	.render_fragment = shapes_render_fragment,
	.finish_frame = shapes_finish_frame,
	.setup = shapes_setup,
	.name = "shapes",
	.description = "Procedural 2D shapes (threaded)",
	.author = "Vito Caputo <vcaputo@pengaru.com>",
	.flags = TIL_MODULE_OVERLAYABLE,
};