/*
 * Logserver
 * Copyright (C) 2017-2025 Joel Reardon
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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 <https://www.gnu.org/licenses/>.
 */

#include "test.h"

#include <random>

#include "explored_range.h"

using namespace std;

set<size_t> setify(size_t start, size_t end) {
	if (end < start) {
		size_t swp = end;
		end = start;
		start = swp;
	}
	set<size_t> ret;
	for (size_t i = start; i < end; ++i) {
		ret.insert(i);
	}
	return ret;
}

void check_complete(const ExploredRange& range, size_t len) {
	set<size_t> lines;
	range.disjunctive_join(&lines);
	REQUIRE(lines.size() == len);
	for (size_t i = 0; i < len; ++i) {
		CHECK(lines.count(i));
	}
}

TEST_CASE("exploreange random whence") {
	mt19937 rng(random_device{}());
        uniform_int_distribution<int> range_dist(1, 10);
        uniform_int_distribution<int> length_dist(1, 20);
        uniform_int_distribution<int> coin_dist(0, 1);
	for (int i = 0; i < 100; ++i) {
		size_t len = length_dist(rng);
		len *= len;
	        uniform_int_distribution<int> whence_dist(0, len);
		size_t count = 0;
		size_t start = G::NO_POS;
		size_t end = G::NO_POS;
		size_t search = range_dist(rng);
		ExploredRange range(search);
		size_t whence = 0;
		range.mark_end(len);
		while (!range.completed()) {
			if (coin_dist(rng)) whence = whence_dist(rng);
			++count;

			range.explore(whence, &start, &end);
			CHECK(start != G::NO_POS);
			CHECK(end != G::NO_POS);
			CHECK(start <= len);
			CHECK(end <= len);
			if (start != end) range.extend(start, end, setify(start, end));
		}
		check_complete(range, len);
	}
}

TEST_CASE("exploreange top whence") {
	mt19937 rng(random_device{}());
        uniform_int_distribution<int> range_dist(1, 10);
        uniform_int_distribution<int> length_dist(1, 20);
	for (int i = 0; i < 100; ++i) {
		size_t len = length_dist(rng);
		len *= len;
		size_t count = 0;
		size_t start = G::NO_POS;
		size_t end = G::NO_POS;
		size_t search = range_dist(rng);
		ExploredRange range(search);
		size_t whence = 0;
		range.mark_end(len);
		while (!range.completed()) {
			++count;
			range.explore(whence, &start, &end);
			CHECK(start != G::NO_POS);
			CHECK(end != G::NO_POS);
			CHECK(start <= len + search);
			CHECK(end <= len + search);
			if (end > len) end = len;
			if (start != end) range.extend(start, end, setify(start, end));
		}
		check_complete(range, len);
	}
}

TEST_CASE("exploreange bottom whence") {
	mt19937 rng(random_device{}());
        uniform_int_distribution<int> range_dist(1, 10);
        uniform_int_distribution<int> length_dist(1, 20);
	for (int i = 0; i < 100; ++i) {
		size_t len = length_dist(rng);
		len *= len;
		size_t count = 0;
		size_t start = G::NO_POS;
		size_t end = G::NO_POS;
		size_t search = range_dist(rng);
		ExploredRange range(search);
		size_t whence = len;
		range.mark_end(len);
		while (!range.completed()) {
			++count;
			range.explore(whence, &start, &end);
			CHECK(start != G::NO_POS);
			CHECK(end != G::NO_POS);
			CHECK(start <= len + search);
			CHECK(end <= len + search);
			if (end > len) end = len;
			if (start != end) range.extend(start, end, setify(start, end));
		}
		check_complete(range, len);
	}
}

TEST_CASE("explore range insert lines") {
	ExploredRange range(5);
	size_t start, end;
	range.explore(0, &start, &end);
	CHECK(start == 0);
	CHECK(end == 5);
	set<size_t> expected = {2};
	range.extend(start, end, expected);

	set<size_t> findings;
	range.disjunctive_join(&findings);
	CHECK(findings == expected);
	range.insert_or_delete_lines(3, 10, true);
	findings.clear();
	range.disjunctive_join(&findings);
	CHECK(findings == expected);
	range.insert_or_delete_lines(1, 5, true);
	findings.clear();
	range.disjunctive_join(&findings);
	expected = {7};
	CHECK(findings == expected);
}

TEST_CASE("explore range gapped") {
	ExploredRange range(10);
	range.mark_end(30);
	size_t start, end;
	range.explore(0, &start, &end);
	CHECK(start == 0);
	CHECK(end == 10);
	set<size_t> expected = {2, 5, 7};
	range.extend(start, end, expected);
	range.explore(30, &start, &end);
	CHECK(end == 20);
	CHECK(start == 30);
	expected = {22, 26, 29};
	range.extend(start, end, expected);

	CHECK(range.next_match(0, G::DIR_DOWN) == 2);
	CHECK(range.next_match(0, G::DIR_UP) == G::NO_POS);
	CHECK(range.next_match(3, G::DIR_DOWN) == 5);
	CHECK(range.next_match(3, G::DIR_UP) == 2);
	CHECK(range.next_match(8, G::DIR_DOWN) == G::NO_POS);
	CHECK(range.next_match(8, G::DIR_UP) == 7);
	CHECK(range.next_match(15, G::DIR_DOWN) == G::NO_POS);
	CHECK(range.next_match(15, G::DIR_UP) == G::NO_POS);
	CHECK(range.next_match(20, G::DIR_DOWN) == 22);
	CHECK(range.next_match(20, G::DIR_UP) == G::NO_POS);
	CHECK(range.next_match(24, G::DIR_DOWN) == 26);
	CHECK(range.next_match(24, G::DIR_UP) == 22);

	range.insert_or_delete_lines(6, 10, true);
	set<size_t> findings;
	range.disjunctive_join(&findings);
	expected = {2, 5, 17, 32, 36, 39};
	CHECK(findings == expected);
}

TEST_CASE("explore range next range") {
	ExploredRange range(10);
	range.mark_end(30);
	size_t start, end;
	range.explore(5, &start, &end);
	CHECK(start == 5);
	CHECK(end == 15);
	range.extend(start, end, set<size_t>());
	range.explore(28, &start, &end);
	CHECK(end == 18);
	CHECK(start == 28);
	range.extend(start, end, set<size_t>());

	CHECK(range.next_range(0, G::DIR_DOWN) == G::NO_POS);
	CHECK(range.next_range(0, G::DIR_UP) == G::NO_POS);
	CHECK(range.next_range(3, G::DIR_DOWN) == G::NO_POS);
	CHECK(range.next_range(3, G::DIR_UP) == G::NO_POS);
	CHECK(range.next_range(5, G::DIR_DOWN) == 15);
	CHECK(range.next_range(5, G::DIR_UP) == 5);
	CHECK(range.next_range(15, G::DIR_DOWN) == 15);
	CHECK(range.next_range(15, G::DIR_UP) == 5);
	CHECK(range.next_range(10, G::DIR_DOWN) == 15);
	CHECK(range.next_range(10, G::DIR_UP) == 5);
	CHECK(range.next_range(17, G::DIR_DOWN) == G::NO_POS);
	CHECK(range.next_range(17, G::DIR_UP) == G::NO_POS);
	CHECK(range.next_range(22, G::DIR_DOWN) == 28);
	CHECK(range.next_range(22, G::DIR_UP) == 18);
}

TEST_CASE("explored insert nogap") {
	ExploredRange range(10);
	range.mark_end(30);
	size_t start, end;
	range.explore(30, &start, &end);
	CHECK(start == 30);
	CHECK(end == 20);
	set<size_t> expected = {22, 27};
	range.extend(start, end, expected);

	range.post_end(33);
	range.post_end(38);

	CHECK(range.next_range(40, G::DIR_DOWN) == G::EOF_POS);
	CHECK(range.next_range(40, G::DIR_UP) == 20);
	CHECK(range.next_match(35, G::DIR_DOWN) == 38);
	CHECK(range.next_match(35, G::DIR_UP) == 33);
	CHECK(range.next_match(31, G::DIR_DOWN) == 33);
	CHECK(range.next_match(31, G::DIR_UP) == 27);
	CHECK(range.next_match(29, G::DIR_DOWN) == 33);
	CHECK(range.next_match(29, G::DIR_UP) == 27);

	CHECK(range.next_range(22, G::DIR_DOWN) == G::EOF_POS);
	CHECK(range.next_range(22, G::DIR_UP) == 20);
}

TEST_CASE("explored insert gap") {
	ExploredRange range(10);
	range.mark_end(30);
	size_t start, end;
	range.explore(17, &start, &end);
	CHECK(start == 17);
	CHECK(end == 27);
	set<size_t> expected = {22, 25};
	range.extend(start, end, expected);

	range.post_end(33);
	range.post_end(38);

	CHECK(range.next_range(40, G::DIR_DOWN) == G::EOF_POS);
	CHECK(range.next_range(40, G::DIR_UP) == 30);
	CHECK(range.next_match(35, G::DIR_DOWN) == 38);
	CHECK(range.next_match(35, G::DIR_UP) == 33);
	CHECK(range.next_match(31, G::DIR_DOWN) == 33);
	CHECK(range.next_match(31, G::DIR_UP) == G::NO_POS);
	CHECK(range.next_match(29, G::DIR_DOWN) == G::NO_POS);
	CHECK(range.next_match(29, G::DIR_UP) == G::NO_POS);
	CHECK(range.next_range(22, G::DIR_DOWN) == 27);
	CHECK(range.next_range(22, G::DIR_UP) == 17);
}

