/*
 * 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 <iostream>
#include <list>
#include <random>
#include <vector>

#include "test.h"

#include "huge_vector.h"

using namespace std;

class string_holder {
public:
	string_holder(const string& init) : _s(init) {}

	inline const string& get() const { return _s; }
	string_view view() const { return _s; }
protected:
	string _s;
};

/* huge_vector should function like a normal vector, so we can just randomly do
 * ops and make sure they are equal */
TEST_CASE("random use") {
	mt19937 rng(random_device{}());
	uniform_int_distribution<int> action(0, 9);
	uniform_int_distribution<unsigned char> chardist('a', 'z');
	for (int i = 0; i < 10; ++i) {
		huge_vector<unique_ptr<string_holder>, 64> test;
		vector<string> _control;
		uniform_int_distribution<int> event_dist(10, 1000);
		int total_events = event_dist(rng);
		for (int j = 0; j < total_events; ++j) {
			test.sanity();
			int cur = action(rng);
			CHECK(test.length() == _control.size());
			if (cur < 6 || !_control.size()) {
				string s = "";
				s += chardist(rng);
				s += chardist(rng);
				s += chardist(rng);
				_control.push_back(s);
				test.add(make_unique<string_holder>(s));
			} else if (cur < 8) {
				uniform_int_distribution<int>
					pos_dist(0, _control.size() - 1);
				size_t pos = pos_dist(rng);
				_control.erase(_control.begin() + pos);
				test.remove(pos);
			} else if (cur == 8) {
				uniform_int_distribution<int>
					pos_dist(0, _control.size() - 1);
				size_t amount = action(rng);
				size_t pos = pos_dist(rng);
				list<string> to_add;
				list<unique_ptr<string_holder>> to_add_test;
				for (size_t subpos = 0; subpos < amount; ++subpos) {
					string s = "";
					s += chardist(rng);
					s += chardist(rng);
					to_add.push_back(s);
					to_add_test.push_back(make_unique<string_holder>(s));
				}
				_control.insert(_control.begin() + pos,
						to_add.begin(),
						to_add.end());
				test.insert(to_add_test, pos);
			} else {
				uniform_int_distribution<int>
					pos_dist(0, _control.size() - 1);
				size_t pos = pos_dist(rng);
				string s = "";
				s += chardist(rng);
				s += chardist(rng);
				_control.insert(_control.begin() + pos, s);
				test.insert(make_unique<string_holder>(s), pos);
			}
			for (size_t pos = 0; pos < _control.size(); ++pos) {
				CHECK(test.valid(pos));
				CHECK(_control[pos] == test[pos]->get());
				CHECK(test[pos] == test.at(pos));
			}
			for (size_t pos = 0; pos < 100; ++pos) {
				CHECK(!test.valid(_control.size() + pos));
				CHECK(!test.valid(_control.size() + pos +
						   10000));
			}
		}
	}
}

TEST_CASE("iterate empty") {
	huge_vector<unique_ptr<string_holder>, 2> test;
	for (const auto& x : test) {
		CHECK(x != x);

	}
}

TEST_CASE("iterate") {
	huge_vector<unique_ptr<string_holder>, 2> test;
	test.add(make_unique<string_holder>("hello"));
	test.add(make_unique<string_holder>("there"));
	test.add(make_unique<string_holder>("world"));
	test.add(make_unique<string_holder>("and"));
	test.add(make_unique<string_holder>("goodday"));
	test.add(make_unique<string_holder>("as"));
	test.add(make_unique<string_holder>("well"));
	test.add(make_unique<string_holder>("as"));
	test.add(make_unique<string_holder>("farewell"));
	list<string> results = {"hello", "there", "world",
		"and", "goodday", "as", "well", "as",
		"farewell"};

	size_t i = 0;
	pagepos_t pp;
	pp.page = 0;
	pp.off = 0;
	auto it = test.begin();
	for (const auto& x : test) {
		CHECK(test.valid(pp));
		CHECK(test.valid(i));
		CHECK(x == *it);
		CHECK(x == test[i]);
		CHECK(x->get() == results.front());
		results.pop_front();
		test.next(&pp);
		++i;
		++it;
	}
	CHECK(results.empty());
	CHECK(!test.valid(pp));
	CHECK(!test.valid(i));
	CHECK(it == test.end());
}

TEST_CASE("clear") {
	huge_vector<unique_ptr<string_holder>, 2> test;
        test.add(make_unique<string_holder>("hello"));
        test.add(make_unique<string_holder>("there"));
        test.add(make_unique<string_holder>("world"));
        test.add(make_unique<string_holder>("and"));
	test.clear();
	CHECK(test.length() == 0);
        test.add(make_unique<string_holder>("goodday"));
	CHECK(test[0]->get() == "goodday");
}

TEST_CASE("clear single") {
	huge_vector<unique_ptr<string_holder>, 8> test;
        test.add(make_unique<string_holder>("hello"));
        test.add(make_unique<string_holder>("there"));
        test.add(make_unique<string_holder>("world"));
        test.add(make_unique<string_holder>("and"));
	test.clear();
	CHECK(test.length() == 0);
        test.add(make_unique<string_holder>("goodday"));
	CHECK(test[0]->get() == "goodday");
}

TEST_CASE("write") {
	huge_vector<unique_ptr<string_holder>, 8> test;
        test.add(make_unique<string_holder>("hello"));
        test.add(make_unique<string_holder>("there"));
        test.add(make_unique<string_holder>("world"));
        test.add(make_unique<string_holder>("and"));
        test.add(make_unique<string_holder>("goodday"));
	stringstream ss;
	test.write(ss);
	CHECK(ss.str() == "hello\nthere\nworld\nand\ngoodday\n");
}
