#include "../AnimationRender/AnimationRender.h"
#include "../AnimationRender/F_Draw.h"
#include "../AnimationRender/F_FrameRange.h"
#include "../AnimationRender/F_Interpolate.h"
#include "../AnimationRender/FrameCallback.h"
#include "../FrameRender/P_Circle.h"
#include "../FrameRender/P_Square.h"
#include "../FrameRender/P_TransformCenter.h"
#include "../FrameRender/PixelCallback.h"
#include <stdlib.h>

struct FrameCallbackArray {
	FrameCallback** arr;
	u32 count;
	u32 allocated;

	const u32* elements;
	const TransformCenter* positions;
	const PixelCallback** callbacksPositions;
};

void
FrameCallbackArray_push(struct FrameCallbackArray* fca, u32 from, u32 to, u32 start, u32 end) {
	// resize
	if (fca->count + 5 >= fca->allocated) {
		fca->allocated = fca->allocated == 0 ? 8 : fca->allocated << 1;
		FrameCallback** arrLarger = calloc(sizeof(FrameCallback*), fca->allocated);

		for (unsigned i = 0; i < fca->count; ++i)
			arrLarger[i] = fca->arr[i];
		free(fca->arr);
		fca->arr = arrLarger;
	}

	fca->arr[fca->count++] = NULL;
	fca->arr[fca->count++] = FrameRange_setup((FrameRange){
		.startFrame = start, .endFrame = end,
	});
	fca->arr[fca->count++] = Interpolate_setup((Interpolate){
		.startValue = fca->positions + from, .endValue = fca->positions + to,
		.value = fca->callbacksPositions[fca->elements[from]]->callback_self,
		.interpolate = (f_interpolate)Interpolate_TransformCenter,
	});
}

struct SwapCallbackArgs {
	struct FrameCallbackArray* fca;
	u32* frames;
	u32 duration;
	u32 rest;
};

void
swap_callback(struct SwapCallbackArgs* sca, u32 from, u32 to) {
	FrameCallbackArray_push(sca->fca,
		from, to, *sca->frames, *sca->frames + sca->duration);
	FrameCallbackArray_push(sca->fca,
		to, from, *sca->frames, *sca->frames + sca->duration);
	*sca->frames += sca->duration + sca->rest;
}

void
swap_u32(struct SwapCallbackArgs* sca, u32* a, u32 i, u32 j) {
	swap_callback(sca, i, j);
	u32 temp = a[i];
	a[i] = a[j];
	a[j] = temp;
}

void
bubble_sort(u32* a, u32 length, struct SwapCallbackArgs* sca) {
	for (u32 i = 0; i < length - 1; ++i) {
		for (u32 j = 0; j < length - 1 - i; ++j) {
			// Sorts in ascending order
			if (a[j] > a[j+1])
				swap_u32(sca, a, j, j+1);
		}
	}
}

void
insertion_sort(u32* a, u32 length, struct SwapCallbackArgs* sca) {
	for (u32 i = 1; i < length; ++i) {
		u32 j = i;
		// Sorts in descending order
		while (j > 0 && a[j-1] < a[j]) {
			swap_u32(sca, a, j, j-1);
			--j;
		}
	}
}

void
selection_sort(u32* a, u32 length, struct SwapCallbackArgs* sca) {
	for (u32 i = 0; i < length - 1; ++i) {
		int minind = i;
		for (int j = i + 1; j < length; ++j) {
			// Sorts in ascending order
			if (a[j] < a[minind])
				minind = j;
		}

		if (minind != i)
			swap_u32(sca, a, i, minind);
	}
}

int
main() {
	/* Sesttings */

	// Animation settings
	u32 width = 960;
	u32 height = 320;
	const u32 FPS = 60;

	// Elements settings
	u32 elements[] = { 5, 4, 3, 2, 1, 0 };
	#define ELEMENTS_COUNT (sizeof(elements) / sizeof(*elements))
	u32 elementSpacing = 40;

	/* Implementation */

	// Spaced out elements
	u32 minRadius = (width / ELEMENTS_COUNT) / 4 - elementSpacing / 2;
	u32 maxRadius = (width / ELEMENTS_COUNT) / 2 - elementSpacing / 2;

	TransformCenter positions[ELEMENTS_COUNT];
	for (unsigned i = 0; i < ELEMENTS_COUNT; ++i) {
		positions[i].centerX = i * width / ELEMENTS_COUNT + maxRadius + elementSpacing / 2;
		positions[i].centerY = height / 2;
	}

	PixelCallback* callbacksPositions[ELEMENTS_COUNT];
	PixelCallback* callbacksElements[1 + ELEMENTS_COUNT * 3] = {
		/* Background */
		Square_setup((Square){
			.size = width, .color = 0xFFF0F0F0,
		}),
	};
	for (unsigned i = 0; i < ELEMENTS_COUNT; ++i) {
		double p = elements[i] / (ELEMENTS_COUNT - 1.0);

		callbacksElements[1+i*3] = NULL;
		callbacksElements[1+i*3+1] = callbacksPositions[elements[i]] = TransformCenter_setup(positions[i]);
		callbacksElements[1+i*3+2] = Circle_setup((Circle){
			.radius = minRadius * (1-p) + maxRadius * p,
			.color = 0xFF000000 + 0xFFFF * p,
		});
	}
	u32 callbacksElementsSize = 1 + ELEMENTS_COUNT * 3;

	// Elements movement
	u32 frames = FPS;

	struct FrameCallbackArray fca = {
		.arr = NULL,
		.elements = elements,
		.positions = positions,
		.callbacksPositions = (const PixelCallback**)callbacksPositions,
	};
	struct SwapCallbackArgs sca = {
		.fca = &fca,
		.frames = &frames,
		.duration = FPS * 1,
		.rest = FPS * 0.5,
	};

	// Sort

	bubble_sort(elements, ELEMENTS_COUNT, &sca);
	insertion_sort(elements, ELEMENTS_COUNT, &sca);
	selection_sort(elements, ELEMENTS_COUNT, &sca);

	frames += FPS;

	// Render

	fca.arr[fca.count++] = NULL;
	fca.arr[fca.count++] = Draw_setup((Draw){
		.callbacks = (const PixelCallback**)callbacksElements, .sizeCallbacks = callbacksElementsSize,
	});

	FrameCallback** callbacksMove = fca.arr;
	u32 callbacksMoveSize = fca.count;
	RenderAnimation(width, height, frames, (const FrameCallback**)callbacksMove, callbacksMoveSize);

	// Free
	for (u32 i = 0; i < callbacksElementsSize; ++i) {
		if (callbacksElements[i] == NULL) continue;
		free(callbacksElements[i]->callback_self);
		free(callbacksElements[i]);
	}
	for (u32 i = 0; i < callbacksMoveSize; ++i) {
		if (callbacksMove[i] == NULL) continue;
		free(callbacksMove[i]->callback_self);
		free(callbacksMove[i]);
	}
	free(callbacksMove);

	return 0;
}
