#include "vis-core.h"

static int ranges_comparator(const void *a, const void *b) {
	const Filerange *r1 = a, *r2 = b;
	if (!text_range_valid(r1))
		return text_range_valid(r2) ? 1 : 0;
	if (!text_range_valid(r2))
		return -1;
	return (r1->start < r2->start || (r1->start == r2->start && r1->end < r2->end)) ? -1 : 1;
}

void vis_mark_normalize(Array *a) {
	array_sort(a, ranges_comparator);
	Filerange *prev = NULL, *r = array_get(a, 0);
	for (size_t i = 0; r; r = array_get(a, i)) {
		if (text_range_size(r) == 0) {
			array_remove(a, i);
		} else if (prev && text_range_overlap(prev, r)) {
			*prev = text_range_union(prev, r);
			array_remove(a, i);
		} else {
			prev = r;
			i++;
		}
	}
}

bool vis_mark_equal(Array *a, Array *b) {
	size_t len = array_length(a);
	if (len != array_length(b))
		return false;
	for (size_t i = 0; i < len; i++) {
		if (!text_range_equal(array_get(a, i), array_get(b, i)))
			return false;
	}

	return true;
}

void mark_init(Array *arr) {
	array_init_sized(arr, sizeof(SelectionRegion));
}

void mark_release(Array *arr) {
	if (!arr)
		return;
	array_release(arr);
}

static Array *mark_from(Vis *vis, enum VisMark id) {
	if (!vis->win)
		return NULL;
	if (id == VIS_MARK_SELECTION)
		return &vis->win->saved_selections;
	File *file = vis->win->file;
	if (id < LENGTH(file->marks))
		return &file->marks[id];
	return NULL;
}

enum VisMark vis_mark_used(Vis *vis) {
	return vis->action.mark;
}

void vis_mark(Vis *vis, enum VisMark mark) {
	if (mark < LENGTH(vis->win->file->marks))
		vis->action.mark = mark;
}

static Array mark_get(Win *win, Array *mark) {
	Array sel;
	array_init_sized(&sel, sizeof(Filerange));
	if (!mark)
		return sel;
	View *view = win->view;
	size_t len = array_length(mark);
	array_reserve(&sel, len);
	for (size_t i = 0; i < len; i++) {
		SelectionRegion *sr = array_get(mark, i);
		Filerange r = view_regions_restore(view, sr);
		if (text_range_valid(&r))
			array_add(&sel, &r);
	}
	vis_mark_normalize(&sel);
	return sel;
}

Array vis_mark_get(Win *win, enum VisMark id) {
	return mark_get(win, mark_from(win->vis, id));
}

static void mark_set(Win *win, Array *mark, Array *sel) {
	if (!mark)
		return;
	array_clear(mark);
	View *view = win->view;
	for (size_t i = 0, len = array_length(sel); i < len; i++) {
		SelectionRegion ss;
		Filerange *r = array_get(sel, i);
		if (view_regions_save(view, r, &ss))
			array_add(mark, &ss);
	}
}

void vis_mark_set(Win *win, enum VisMark id, Array *sel) {
	mark_set(win, mark_from(win->vis, id), sel);
}

void marklist_init(MarkList *list, size_t max) {
	Array mark;
	mark_init(&mark);
	array_init_sized(&list->prev, sizeof(Array));
	array_reserve(&list->prev, max);
	array_add(&list->prev, &mark);
	array_init_sized(&list->next, sizeof(Array));
	array_reserve(&list->next, max);
}

void marklist_release(MarkList *list) {
	for (size_t i = 0, len = array_length(&list->prev); i < len; i++)
		array_release(array_get(&list->prev, i));
	array_release(&list->prev);
	for (size_t i = 0, len = array_length(&list->next); i < len; i++)
		array_release(array_get(&list->next, i));
	array_release(&list->next);
}

static bool marklist_push(Win *win, MarkList *list, Array *sel) {
	Array *top = array_peek(&list->prev);
	if (top) {
		Array top_sel = mark_get(win, top);
		bool eq = vis_mark_equal(&top_sel, sel);
		array_release(&top_sel);
		if (eq)
			return true;
	}

	for (size_t i = 0, len = array_length(&list->next); i < len; i++)
		array_release(array_get(&list->next, i));
	array_clear(&list->next);
	Array arr;
	mark_init(&arr);
	if (array_length(&list->prev) >= array_capacity(&list->prev)) {
		Array *tmp = array_get(&list->prev, 0);
		arr = *tmp;
		array_remove(&list->prev, 0);
	}
	mark_set(win, &arr, sel);
	return array_push(&list->prev, &arr);
}

bool vis_jumplist_save(Vis *vis) {
	View *view = vis->win->view;
	Array sel = view_selections_get_all(view);
	bool ret = marklist_push(vis->win, &vis->win->jumplist, &sel);
	array_release(&sel);
	return ret;
}

static bool marklist_prev(Win *win, MarkList *list) {
	View *view = win->view;
	bool restore = false;
	Array cur = view_selections_get_all(view);
	bool anchored = view_selections_anchored(view_selections_primary_get(view));
	Array *top = array_peek(&list->prev);
	if (!top)
		goto out;
	Array top_sel = mark_get(win, top);
	restore = !vis_mark_equal(&top_sel, &cur);
	if (restore)
		view_selections_set_all(view, &top_sel, anchored);
	array_release(&top_sel);
	if (restore)
		goto out;

	while (array_length(&list->prev) > 1) {
		Array *prev = array_pop(&list->prev);
		array_push(&list->next, prev);
		prev = array_peek(&list->prev);
		Array sel = mark_get(win, prev);
		restore = array_length(&sel) > 0;
		if (restore)
			view_selections_set_all(view, &sel, anchored);
		array_release(&sel);
		if (restore)
			goto out;
	}
out:
	array_release(&cur);
	return restore;
}

static bool marklist_next(Win *win, MarkList *list) {
	View *view = win->view;
	bool anchored = view_selections_anchored(view_selections_primary_get(view));
	for (;;) {
		Array *next = array_pop(&list->next);
		if (!next)
			return false;
		Array sel = mark_get(win, next);
		if (array_length(&sel) > 0) {
			view_selections_set_all(view, &sel, anchored);
			array_release(&sel);
			array_push(&list->prev, next);
			return true;
		}
		array_release(next);
	}
}

bool vis_jumplist_prev(Vis *vis) {
	return marklist_prev(vis->win, &vis->win->jumplist);
}

bool vis_jumplist_next(Vis *vis) {
	return marklist_next(vis->win, &vis->win->jumplist);
}

enum VisMark vis_mark_from(Vis *vis, char mark) {
	if (mark >= 'a' && mark <= 'z')
		return VIS_MARK_a + mark - 'a';
	for (size_t i = 0; i < LENGTH(vis_marks); i++) {
		if (vis_marks[i].name == mark)
			return i;
	}
	return VIS_MARK_INVALID;
}

const MarkDef vis_marks[] = {
	[VIS_MARK_DEFAULT]        = { '\'', VIS_HELP("Default mark")    },
	[VIS_MARK_SELECTION]      = { '^',  VIS_HELP("Last selections") },
};
