#include <assert.h>
#include "sway/animation_manager.h"
#include "sway/config.h"
#include "sway/output.h"
#include "sway/server.h"
#include "sway/tree/arrange.h"
#include "sway/tree/container.h"
#include "sway/tree/node.h"
#include "sway/tree/root.h"

struct animation_manager {
	float tick_time;
	float progress_delta;
	struct wl_event_source *tick;
	struct wl_list animations;
} animation_manager;

struct animation init_animation(void *data) {
	return (struct animation){
		.data = data,
		.progress = 0.0f,
		.multiplier = 0.0f,
		.initialized = false,
		.update = NULL,
		.complete = NULL,
	};
}

static float ease_out_cubic(float p) {
	return pow(p - 1, 3) + 1;
}

static int animation_timer() {
	struct animation *animation, *tmp;
	wl_list_for_each_reverse_safe(animation, tmp, &animation_manager.animations, link) {
		animation->progress = MIN(animation->progress + animation_manager.progress_delta, 1.0f);
		animation->multiplier = ease_out_cubic(animation->progress);

		if (animation->update) {
			animation->update(animation->data);
		}

		if (animation->progress == 1.0f) {
			finish_animation(animation);
			if (animation->complete) {
				animation->complete(animation->data);
			}
		}
	}

	if (!wl_list_empty(&animation_manager.animations)) {
		wl_event_source_timer_update(animation_manager.tick,
				animation_manager.tick_time);
	}
	return 0;
}

void add_animation(struct animation *animation, void (*update_callback)(void *),
		void (*complete_callback)(void *)) {
	if (!config->animation_duration_ms) {
		if (complete_callback) {
			complete_callback(animation->data);
		}
		return;
	}
	// remove previous instances of this animation
	if (animation->initialized) {
		wl_list_remove(&animation->link);
	}

	animation->progress = 0.0f;
	animation->multiplier = 0.0f;
	animation->initialized = true;
	animation->update = update_callback;
	animation->complete = complete_callback;
	wl_list_insert(&animation_manager.animations, &animation->link);
}

void finish_animation(struct animation *animation) {
	if (animation->initialized) {
		wl_list_remove(&animation->link);
		animation->initialized = false;
	}
	animation->progress = 1.0f;
	animation->multiplier = 1.0f;
}

void start_animations() {
	if (!config->animation_duration_ms) {
		return;
	}
	assert(animation_manager.tick);

	wl_event_source_timer_update(animation_manager.tick, 1);
}

static float get_fastest_output_refresh_ms() {
	float fastest_output_refresh_ms = 16.6667; // fallback to 60 Hz
	for (int i = 0; i < root->outputs->length; ++i) {
		struct sway_output *output = root->outputs->items[i];
		if (output->refresh_nsec > 0) {
			float output_refresh_ms = output->refresh_nsec / 1000000.0;
			fastest_output_refresh_ms = MIN(fastest_output_refresh_ms, output_refresh_ms);
		}
	}
	return fastest_output_refresh_ms;
}

void refresh_animation_manager_timing() {
	if (!config || !root) {
		return;
	}
	animation_manager.tick_time = get_fastest_output_refresh_ms();
	animation_manager.progress_delta = animation_manager.tick_time / config->animation_duration_ms;
}

void animation_manager_init(struct sway_server *server) {
	animation_manager.tick = wl_event_loop_add_timer(server->wl_event_loop,
			animation_timer, NULL);
	wl_list_init(&animation_manager.animations);
	refresh_animation_manager_timing();
}

static float lerp(float a, float b, float t) {
	return a * (1.0 - t) + b * t;
}

float get_animated_value(float from, float to, const struct animation *animation) {
	if (!config->animation_duration_ms || !animation->initialized) {
		return to;
	}
	return lerp(from, to, animation->multiplier);
}

