// SPDX-License-Identifier: LGPL-2.1-or-later
/* SPDX-FileCopyrightText: 2006-2015  Simon Wunderlich <sw@simonwunderlich.de>
 */

#include <s3d.h>
#include <s3dw.h>
#include <s3dw_int.h>
#include <s3dlib.h>
#include <math.h>

/* the animation stack */
static s3dw_widget *ani_s[MAXANI];
static int ani_n = 0;
static int animation_on = 0;
int ani_need_arr = 0;

/* is item f already on stack? */
int s3dw_ani_stackpos(s3dw_widget *f)
{
	int i;
	for (i = 0; i < ani_n; i++)
		if (ani_s[i] == f)
			return i;  /* already in list */
	return -1;

}

/* add an item on the animation stack */
void s3dw_ani_add(s3dw_widget *f)
{

	s3dprintf(VLOW, "[A]ni ADD (%10p), oid = %d, ani_n = %d, type is %s\n", (void*)f, f->oid, ani_n, s3dw_get_type_string(f->type));
	if ((f->oid == 0) && (f->type != S3DW_TCAM)) {
		s3dprintf(HIGH, "s3dw_ani_add() assert failed: weird, moving cam but its not a cam object?");
		return;
	}
	if ((ani_n < MAXANI) && (animation_on)) {
		if (s3dw_ani_stackpos(f) != -1)
			return;  /* already in list */
		ani_s[ani_n] = f;
		s3dw_ani_iterate(f);
		ani_n++;
	} else /* no place, finish now */
		s3dw_ani_finish(f, -1);
}

/* delete an item from the animation stack */
void s3dw_ani_del(int i)
{
	if ((i >= 0) && (i < ani_n)) {
		s3dprintf(VLOW, "[A]ni DEL %d, ani_n = %d\n", i, ani_n);
		ani_n--;
		ani_s[i] = ani_s[ani_n]; /* that should also work if i is the last one */
	} else {
		s3dprintf(MED, "[F]ATAL: can't delete animation!\n");
	}
}

/* apply the animation */
void s3dw_ani_doit(s3dw_widget *f)
{
	if ((f->oid == 0) && (f->type != S3DW_TCAM)) {
		s3dprintf(HIGH, "s3dw_ani_doit() assert failed: weird, moving cam but its not a cam object?");
	} else {
		if (f->oid == 0) {
			s3dprintf(HIGH, "moving cam");
		}
		s3d_translate(f->oid, f->ax, f->ay, f->az);
		s3d_rotate(f->oid, f->arx, f->ary, f->arz);
		s3d_scale(f->oid, f->as);
	}
}

/* finish an animation on the stack, stack index i */
void s3dw_ani_finish(s3dw_widget *f, int i)
{
	f->ax = f->x;
	f->ay = f->y;
	f->az = f->z;
	f->arx = f->rx;
	f->ary = f->ry;
	f->arz = f->rz;
	f->as = f->s;
	s3dw_ani_doit(f);
	if (i != -1)
		s3dw_ani_del(i);
}

/* do one step of the animation */
void s3dw_ani_iterate(s3dw_widget *f)
{
	f->ax = (f->x + f->ax * ZOOMS) / (ZOOMS + 1);
	f->ay = (f->y + f->ay * ZOOMS) / (ZOOMS + 1);
	f->az = (f->z + f->az * ZOOMS) / (ZOOMS + 1);
	f->arx = (f->rx + f->arx * ZOOMS) / (ZOOMS + 1);
	f->ary = (f->ry + f->ary * ZOOMS) / (ZOOMS + 1);
	f->arz = (f->rz + f->arz * ZOOMS) / (ZOOMS + 1);
	f->as = (f->s + f->as * ZOOMS) / (ZOOMS + 1);

}

/* checks if f is good enough */
int s3dw_ani_check(s3dw_widget *f)
{
	float x, y, z, rx, ry, rz;
	x = f->ax - f->x;
	y = f->ay - f->y;
	z = f->az - f->z;
	rx = f->arx - f->rx;
	ry = f->ary - f->ry;
	rz = f->arz - f->rz;

	if (((fabs(f->as - f->s) / f->s) > 0.01) || (sqrt(x*x + y*y + z*z) > 0.01) || (sqrt(rx*rx + ry*ry + rz*rz) > 0.01))
		return 0;
	return 1;
}

/* need an arrangement ... */
void s3dw_ani_needarr(void)
{
	ani_need_arr = 1;
	if (!animation_on) {
		while (ani_need_arr) s3dw_arrange();
	}

}

/** \brief doing the whole animation thing
 *
 * Just call this in your mainloop if you want some nice window sliding
 * animations. it's somewhat bloating, but you don't want to miss it ;)
 *
 * \code
 * #include <time.h>   // nanosleep()
 * static struct timespec t={0.33*1000*1000}; // 33 mili seconds
 * void mainloop()
 * {
 *         // keep this in your mainloop. this will do smooth animations for you ...
 *         s3dw_ani_mate();
 *         nanosleep(&t,NULL);
 * }
 *
 * ....
 * s3d_mainloop(mainloop);
 * \endcode
 */
void s3dw_ani_mate(void)
{
	int i;
	s3dw_widget *f;
	animation_on = 1; /* animation is activated */
	if (ani_need_arr) s3dw_arrange();
	for (i = 0; i < ani_n; i++) {
		f = ani_s[i];
		s3dw_ani_iterate(f);
		if (s3dw_ani_check(f)) {
			s3dw_ani_finish(f, i);
			i--; /* a new widget is here now, take care in the next iteration */
		} else {
			s3dw_ani_doit(f);
		}
	}
}
