/*
 * Copyright (C) 2009 Canonical Ltd
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 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/>.
 *
 * Authored by Mirco Müller <mirco.mueller@canonical.com>
 *
 */

#include <math.h>
#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <clutk/clutk.h>

#define SECONDS        5
#define MAX_ACTORS   100
#define MAX_SIZE     300
#define MAX_CHILDREN   3

#define DROP_SHADOW_OFFSET 4

typedef struct _FramesPerSecond {
	guint  seconds; // seconds passed sofar
	guint  all;     // all frames rendered sofar
	guint  cur;     // current fps-rate
	guint  min;     // minimum fps-rate sofar
	gfloat avg;     // avgerage fps-rate sofar
	guint  max;     // maximum fps-rate sofar
} FramesPerSecond;

typedef enum _ActorType {
	SINGLE = 0,
	COMPOSED
} ActorType;

typedef enum _AnimationType {
	STATIC = 0,
	ANIMATED
} AnimationType;

typedef enum _EffectType {
	BLUR = 0,
	GLOW,
	SHADOW
} EffectType;

// prototypes
void
test_effect_perf (guint         iter,
		  guint         num,
		  guint         size,
		  guint         seconds,
		  ActorType     actor_type,
		  AnimationType animation_type,
		  EffectType    effect_type,
		  gchar*        version,
		  gchar*        gpu,
		  gchar*        ogl_version,
		  guint         current,
		  guint         all);

gint
main (gint    argc,
      gchar** argv)
{
	ActorType     actor_type     = SINGLE;
	AnimationType animation_type = STATIC;
	EffectType    effect_type    = BLUR;

	g_type_init (); 
	g_test_init (&argc, &argv, NULL);

	if (argc != 13)
	{
		g_print ("Usage:\n\t%s <iteration> <number> <size> <seconds> "
			 "single|composed animated|static blur|glow|shadow "
			 "<mod-version> <gpu> <ogl-version> <current> <all>\n"
			 "Example:\n\t%s 0 "
			 "10 125 5 single animated shadow 0.3.2 GMA950 2.1 "
			 "1 10\n",
			 "test-clutk-perf",
			 "test-clutk-perf");
		return 0;
	}

	// make sure rendering is not throttled by sync-to-vblank
	g_setenv ("CLUTTER_VBLANK", "none", FALSE);

	ctk_init (&argc, &argv);

	// determine which performance-test to run
	actor_type = !g_strcmp0 (argv[5], "single") ? SINGLE : COMPOSED;
	animation_type = !g_strcmp0 (argv[6], "static") ? STATIC : ANIMATED;
	if (!g_strcmp0 (argv[7], "blur"))
		effect_type = BLUR;
	if (!g_strcmp0 (argv[7], "glow"))
		effect_type = GLOW;
	if (!g_strcmp0 (argv[7], "shadow"))
		effect_type = SHADOW;

	test_effect_perf ((guint) atoi (argv[1]),
			  (guint) atoi (argv[2]),
			  (guint) atoi (argv[3]),
			  (guint) atoi (argv[4]),
			  actor_type,
			  animation_type,
			  effect_type,
			  argv[8],
			  argv[9],
			  argv[10],
			  (guint) atoi (argv[11]),
			  (guint) atoi (argv[12]));

	return g_test_run ();
}

void
on_new_frame (ClutterTimeline* timeline,
	      gint             msecs,
	      gpointer         data)
{
	FramesPerSecond* fps;

	if (!data)
		return;

	fps = (FramesPerSecond*) data;
	fps->all += 1;
	fps->cur += 1;
}

gboolean
on_second_passed (gpointer data)
{
	FramesPerSecond* fps = NULL;

	if (!data)
		return TRUE;

	fps = (FramesPerSecond*) data;

	// increase seconds counter
	fps->seconds++;

	// check for new minimum fps-rate
	if (fps->cur < fps->min)
		fps->min = fps->cur;

	// determine average fps-rate sofar
	fps->avg = (gfloat) fps->all / (gfloat) fps->seconds;

	// check for new maximum fps-rate
	if (fps->cur > fps->max)
		fps->max = fps->cur;

	// reset current frame-counter
	fps->cur = 0;

	return TRUE;
}

void
on_completed (ClutterTimeline* timeline,
	      gpointer         data)
{
	FramesPerSecond* fps;

	if (!data)
		return;

	fps = (FramesPerSecond*) data;

	g_print ("%d\t%d\t%d\n", fps->min, (gint) fps->avg, fps->max);
    
	clutter_main_quit ();
}

void
on_timeline_completed (ClutterTimeline* timeline,
		       gpointer         data)
{
	if (clutter_timeline_get_direction (timeline) == CLUTTER_TIMELINE_BACKWARD)
		clutter_timeline_set_direction (timeline,
						CLUTTER_TIMELINE_FORWARD);
	else
		clutter_timeline_set_direction (timeline,
						CLUTTER_TIMELINE_BACKWARD);
}

void
test_effect_perf (guint         iter,
		  guint         num,
		  guint         size,
		  guint         seconds,
		  ActorType     actor_type,
		  AnimationType animation_type,
		  EffectType    effect_type,
		  gchar*        version,
		  gchar*        gpu,
		  gchar*        ogl_version,
		  guint         current,
		  guint         all)
{
	ClutterActor*        stage;
	ClutterColor	     bg_color = {128, 128, 128, 255};
	ClutterActor*        single[MAX_ACTORS];
	ClutterActor*        composed[MAX_ACTORS];
	ClutterActor*        child[MAX_CHILDREN];
	guint                i;
	guint                j;
	ClutterTimeline*     timeline;
	ClutterAlpha*        alpha;
	ClutterBehaviour*    behaviour;
	FramesPerSecond      fps = {0,    // seconds passed sofar
				    0,    // all frames rendered sofar
				    0,    // current fps-rate
				    1000, // minimum fps-rate sofar
				    0.0f, // avgerage fps-rate sofar
				    0};   // maximum fps-rate sofar
	CtkEffectDropShadow* shadow[MAX_ACTORS];
	CtkEffectDropShadow* child_shadow[MAX_CHILDREN];
	CtkEffect*           glow[MAX_ACTORS];
	CtkEffect*           child_glow[MAX_CHILDREN];
	ClutterColor	     glow_color = {0, 255, 0, 255};
	CtkEffect*           blur[MAX_ACTORS];
	CtkEffect*           child_blur[MAX_CHILDREN];
	GRand*               r;
	ClutterAnimation*    animation[MAX_ACTORS];
	GValue               value[MAX_ACTORS];
	gchar*               label_text;
	ClutterActor*        label;
	ClutterColor         text_color;

	// sanity-checks
	if (num > MAX_ACTORS)
		num = MAX_ACTORS;

	if (size > MAX_SIZE)
		size = MAX_SIZE;

	if (all == 0)
		all = 1;

	// print info about this test-run (needed for gnuplot later on)
	g_print ("%04d\t%s\t", iter, version);

	stage = clutter_stage_get_default ();
	clutter_stage_set_color (CLUTTER_STAGE (stage), &bg_color);

	// set desired fps-rate insanely high
	clutter_set_default_frame_rate (1000);

	// create new random-number generator
	r = g_rand_new ();

	for (i = 0; i < num; i++)
	{
		// create a single image-actor
		if (actor_type == SINGLE)
		{
			single[i] = ctk_image_new_from_filename (
						size,
						"./ubuntu.svg");

			switch (effect_type)
			{
				case SHADOW:
					shadow[i] = ctk_effect_drop_shadow_new (
						animation_type == STATIC ? CTK_EFFECT_DROP_SHADOW_MAX_SIZE : CTK_EFFECT_DROP_SHADOW_MIN_SIZE,
						DROP_SHADOW_OFFSET,
						DROP_SHADOW_OFFSET);
					ctk_actor_add_effect (
						CTK_ACTOR (single[i]),
						CTK_EFFECT (shadow[i]));
				break;

				case GLOW:
					// pick a random color for glow
					glow_color.red   = g_rand_int_range (
								r,
								0,
								255);
					glow_color.green = g_rand_int_range (
								r,
								0,
								255);
					glow_color.blue  = g_rand_int_range (
								r,
								0,
								255);

					glow[i] = ctk_effect_glow_new ();
					ctk_effect_glow_set_color (
						CTK_EFFECT_GLOW (glow[i]),
						&glow_color);
					ctk_effect_glow_set_factor (
						CTK_EFFECT_GLOW (glow[i]),
						animation_type == STATIC ? CTK_EFFECT_GLOW_MAX_FACTOR : CTK_EFFECT_GLOW_MIN_FACTOR);
					ctk_actor_add_effect (
						CTK_ACTOR (single[i]),
						CTK_EFFECT (glow[i]));
				break;

				case BLUR:
					blur[i] = ctk_effect_blur_new ();
					ctk_effect_blur_set_factor (
						CTK_EFFECT_BLUR (blur[i]),
						animation_type == STATIC ? CTK_EFFECT_BLUR_MAX_FACTOR : CTK_EFFECT_BLUR_MIN_FACTOR);
					ctk_actor_add_effect (
						CTK_ACTOR (single[i]),
						CTK_EFFECT (blur[i]));
				break;
			}

			clutter_actor_set_position (
				single[i],
				320 + size/2 * sin (2 * G_PI * i / num),
				240 - size/2 * cos (2 * G_PI * i / num));
			clutter_actor_set_anchor_point (single[i],
							size * 0.575f,
							size * 0.5f);
			clutter_container_add_actor (CLUTTER_CONTAINER (stage),
						     single[i]);

			// animate property-value changes
			if (animation_type == ANIMATED)
			{
				animation[i] = clutter_animation_new ();
				clutter_animation_set_mode (animation[i],
							    CLUTTER_EASE_IN_EXPO);
				clutter_animation_set_loop (animation[i], TRUE);
				clutter_animation_set_duration (animation[i],
								750);
				bzero (&value[i], sizeof (GValue));

				switch  (effect_type)
				{
					case SHADOW:
						g_value_init (&value[i],
							      G_TYPE_FLOAT);
						g_value_set_float (
							&value[i],
							CTK_EFFECT_DROP_SHADOW_MAX_SIZE);
						clutter_animation_set_object (
							animation[i],
							G_OBJECT (shadow[i]));
						clutter_animation_bind (
							animation[i],
							"blur-factor",
							&value[i]);
					break;

					case GLOW:
						g_value_init (&value[i],
							      G_TYPE_FLOAT);
						g_value_set_float (
							&value[i],
							CTK_EFFECT_GLOW_MAX_FACTOR);
						clutter_animation_set_object (
							animation[i],
							G_OBJECT (glow[i]));
						clutter_animation_bind (
							animation[i],
							"factor",
							&value[i]);
					break;

					case BLUR:
						g_value_init (&value[i],
							      G_TYPE_FLOAT);
						g_value_set_float (
							&value[i],
							CTK_EFFECT_BLUR_MAX_FACTOR);
						clutter_animation_set_object (
							animation[i],
							G_OBJECT (blur[i]));
						clutter_animation_bind (
							animation[i],
							"factor",
							&value[i]);
					break;
				}
				g_signal_connect (clutter_animation_get_timeline (animation[i]),
						  "completed",
						  G_CALLBACK (on_timeline_completed),
						  NULL);

				clutter_timeline_start (
				    clutter_animation_get_timeline (animation[i]));
			}
		}

		// create a composited actor
		if (actor_type == COMPOSED)
		{
			// create parent
			composed[i] = ctk_vbox_new (0);

			// create children
			for (j = 0; j < MAX_CHILDREN; j++)
				child[j] = ctk_image_new_from_filename (
						size,
						"./ubuntu.svg");

			// apply effect to children of parent/vbox, modulate
			// effect-type so it's different from the one applied to
			// the parent later on
			switch  ((effect_type + 1) % 3)
			{
				case SHADOW:
					for (j = 0; j < MAX_CHILDREN; j++)
					{
						child_shadow[j] = ctk_effect_drop_shadow_new (
									CTK_EFFECT_DROP_SHADOW_MAX_SIZE,
									DROP_SHADOW_OFFSET,
									DROP_SHADOW_OFFSET);
						ctk_actor_add_effect (
							CTK_ACTOR (child[j]),
							CTK_EFFECT (child_shadow[j]));
						ctk_box_pack (CTK_BOX (composed[i]),
							      child[j],
							      FALSE,
							      FALSE);
					}
				break;

				case GLOW:
					for (j = 0; j < MAX_CHILDREN; j++)
					{
						// pick a random color for glow
						glow_color.red   = g_rand_int_range (
									r,
									0,
									255);
						glow_color.green = g_rand_int_range (
									r,
									0,
									255);
						glow_color.blue  = g_rand_int_range (
									r,
									0,
									255);

						child_glow[j] = ctk_effect_glow_new ();
						ctk_effect_glow_set_factor (
							CTK_EFFECT_GLOW (child_glow[j]),
							CTK_EFFECT_GLOW_MAX_FACTOR);
						ctk_actor_add_effect (
							CTK_ACTOR (child[j]),
							CTK_EFFECT (child_glow[j]));
						ctk_box_pack (CTK_BOX (composed[i]),
							      child[j],
							      FALSE,
							      FALSE);
					}
				break;

				case BLUR:
					for (j = 0; j < MAX_CHILDREN; j++)
					{
						child_blur[j] = ctk_effect_blur_new ();
						ctk_effect_blur_set_factor (
							CTK_EFFECT_BLUR (child_blur[j]),
							CTK_EFFECT_BLUR_MAX_FACTOR);
						ctk_actor_add_effect (
							CTK_ACTOR (child[j]),
							CTK_EFFECT (child_blur[j]));
						ctk_box_pack (CTK_BOX (composed[i]),
							      child[j],
							      FALSE,
							      FALSE);
					}
				break;
			}

			// only the effect applied to the parent/vbox is
			// variable
			switch  (effect_type)
			{
				case SHADOW:
					shadow[i] = ctk_effect_drop_shadow_new (
							animation_type == STATIC ? CTK_EFFECT_DROP_SHADOW_MAX_SIZE : CTK_EFFECT_DROP_SHADOW_MIN_SIZE,
							DROP_SHADOW_OFFSET,
							DROP_SHADOW_OFFSET);
					ctk_actor_add_effect (
							CTK_ACTOR (composed[i]),
							CTK_EFFECT (shadow[i]));
				break;

				case GLOW:
					// pick a random color for glow
					glow_color.red   = g_rand_int_range (
								r,
								0,
								255);
					glow_color.green = g_rand_int_range (
								r,
								0,
								255);
					glow_color.blue  = g_rand_int_range (
								r,
								0,
								255);

					glow[i] = ctk_effect_glow_new ();
					ctk_effect_glow_set_color (
						CTK_EFFECT_GLOW (glow[i]),
						&glow_color);
					ctk_effect_glow_set_factor (
						CTK_EFFECT_GLOW (glow[i]),
						animation_type == STATIC ? CTK_EFFECT_GLOW_MAX_FACTOR : CTK_EFFECT_GLOW_MIN_FACTOR);
					ctk_actor_add_effect (
						CTK_ACTOR (composed[i]),
						CTK_EFFECT (glow[i]));
				break;

				case BLUR:
					blur[i] = ctk_effect_blur_new ();
					ctk_effect_blur_set_factor (
						CTK_EFFECT_BLUR (blur[i]),
						animation_type == STATIC ? CTK_EFFECT_BLUR_MAX_FACTOR : CTK_EFFECT_BLUR_MIN_FACTOR);
					ctk_actor_add_effect (
						CTK_ACTOR (composed[i]),
						CTK_EFFECT (blur[i]));
				break;
			}

			// position composed/vbox and add it to stage
			//clutter_actor_set_position (composed[i], 320, 240);
			clutter_actor_set_position (
				composed[i],
				320 + size/2 * sin (2 * G_PI * i / num),
				240 - 3 * size/2 * cos (2 * G_PI * i / num));
			clutter_actor_set_anchor_point (composed[i],
							size * 0.575f,
							3 * size * 0.5f);
			clutter_container_add_actor (CLUTTER_CONTAINER (stage),
						     composed[i]);

			// animate property-value changes
			if (animation_type == ANIMATED)
			{
				animation[i] = clutter_animation_new ();
				clutter_animation_set_mode (animation[i],
							    CLUTTER_EASE_IN_EXPO);
				clutter_animation_set_loop (animation[i], TRUE);
				clutter_animation_set_duration (animation[i],
								750);
				bzero (&value[i], sizeof (GValue));

				switch  (effect_type)
				{
					case SHADOW:
						g_value_init (&value[i],
							      G_TYPE_FLOAT);
						g_value_set_float (
							&value[i],
							CTK_EFFECT_DROP_SHADOW_MAX_SIZE);
						clutter_animation_set_object (
							animation[i],
							G_OBJECT (shadow[i]));
						clutter_animation_bind (
							animation[i],
							"blur-factor",
							&value[i]);
					break;

					case GLOW:
						g_value_init (&value[i],
							      G_TYPE_FLOAT);
						g_value_set_float (
							&value[i],
							CTK_EFFECT_GLOW_MAX_FACTOR);
						clutter_animation_set_object (
							animation[i],
							G_OBJECT (glow[i]));
						clutter_animation_bind (
							animation[i],
							"factor",
							&value[i]);
					break;

					case BLUR:
						g_value_init (&value[i],
							      G_TYPE_FLOAT);
						g_value_set_float (
							&value[i],
							CTK_EFFECT_BLUR_MAX_FACTOR);
						clutter_animation_set_object (
							animation[i],
							G_OBJECT (blur[i]));
						clutter_animation_bind (
							animation[i],
							"factor",
							&value[i]);
					break;
				}

				g_signal_connect (clutter_animation_get_timeline (animation[i]),
						  "completed",
						  G_CALLBACK (on_timeline_completed),
						  NULL);

				clutter_timeline_start (
				    clutter_animation_get_timeline (animation[i]));
			}
		}
	}

	// add GPU/driver label
	label = clutter_text_new ();
	label_text = g_strdup_printf ("OpenGL-renderer: <b>%s</b>\n"
				      "OpenGL-version: <b>%s</b>\n"
				      "# of actors: <b>%d</b>\n"
				      "size of actors: <b>%dx%d</b>\n"
				      "type of actors: <b>%s</b>\n"
				      "type of anim: <b>%s</b>\n"
				      "type of effect: <b>%s</b>\n"
				      "CluTK-version : <b>%s</b>\n"
				      "test-progress: <b>%3.0f%%</b>\n",
				      gpu,
				      ogl_version,
                                      num,
				      size, size,
                                      actor_type == SINGLE ? "single" : "composed",
				      animation_type == STATIC ? "static" : "animated",
				      effect_type == BLUR ? "blur" : effect_type == GLOW ? "glow" : "shadow",
				      version,
				      (gfloat) current / (gfloat) all * 100);

	clutter_text_set_markup (CLUTTER_TEXT (label), label_text);
	text_color.red   = 0;
	text_color.green = 0;
	text_color.blue  = 0;
	text_color.alpha = 255;
	clutter_text_set_color (CLUTTER_TEXT (label), &text_color);
	clutter_actor_set_position (label, 5, 5);
	clutter_container_add_actor (CLUTTER_CONTAINER (stage), label);
	g_free (label_text);

	// make sure we actually see all the actors that have just been created
    	clutter_actor_show_all (stage);

	// free random-number generator
	g_rand_free (r);

	// create timeline, hook up frame-counter
	timeline = clutter_timeline_new (seconds * 1000);
	g_signal_connect (timeline,
			  "new-frame",
			  G_CALLBACK (on_new_frame),
			  (gpointer) &fps);
	g_signal_connect (timeline,
			  "completed",
			  G_CALLBACK (on_completed),
			  (gpointer) &fps);
	g_timeout_add (1000, on_second_passed, (gpointer) &fps);

	// create alpha, hook up to timeline, start timeline
	alpha = clutter_alpha_new_full (timeline, CLUTTER_LINEAR);
	behaviour = clutter_behaviour_rotate_new (alpha,
						  CLUTTER_Z_AXIS,
						  CLUTTER_ROTATE_CW,
						  0.0f,
						  360.f);
	for (i = 0; i < num; i++)
	{
		if (actor_type == SINGLE)
			clutter_behaviour_apply (behaviour, single[i]);

		if (actor_type == COMPOSED)
			clutter_behaviour_apply (behaviour, composed[i]);
	}

	clutter_timeline_start (timeline);
	clutter_timeline_set_loop (timeline, FALSE);

	clutter_main ();
}
