/*
 * 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/>.
 */

#ifndef __MOCK_LOG_LINES_FOR_FILTER__H__
#define __MOCK_LOG_LINES_FOR_FILTER__H__

#include <list>
#include <random>
#include <shared_mutex>
#include <string>

#include "log_lines.h"

using namespace std;

class MockLogLinesForFilter : public LogLines {
public:
	MockLogLinesForFilter(size_t len, int anchor, const string& match_template)
			: _total_checked(0), _anchor(anchor),
			  _match_template(match_template), _flag_check(true) {
		_lines.resize(len);
		_checked.resize(len);
	}

	MockLogLinesForFilter(size_t len, int anchor)
			: _total_checked(0), _anchor(anchor),
			  _flag_check(false) {
		_lines.resize(len);
		_checked.resize(len);
	}

	virtual ~MockLogLinesForFilter() {
	}

	virtual size_t length_locked() const {
                return _lines.size();
        }

	virtual inline string_view get_line_locked(size_t pos) const {
		if (pos >= _lines.size()) return "";
		if (_flag_check) REQUIRE(!_checked.at(pos));
		++_total_checked;
		++_checked[pos];
		if (_lines[pos].empty()) create(pos);
		return _lines[pos];
	}

	virtual size_t range_add_if(size_t start, size_t end,
				    set<size_t>* results,
				    function<bool(const string_view&)> fn) const {
		if (start > end) {
			size_t swp = start;
			start = end;
			end = swp;
		}
		shared_lock<shared_mutex> ul(_m);
		if (start >= _lines.size()) return _lines.size();
		if (end > _lines.size()) end = _lines.size();
		// simulate reading the line
		for (size_t i = start; i < end; ++i) {
			if (fn(get_line_locked(i))) results->insert(i);
		}
		return _lines.size();
	}

	virtual bool checked_all() const {
		unique_lock<shared_mutex> ul(_m);
		return _total_checked == _lines.size();
	}

	virtual bool checked_number(size_t number) const {
		unique_lock<shared_mutex> ul(_m);
		return _total_checked == number;
	}

	virtual void expect_match(size_t pos) {
		unique_lock<shared_mutex> ul(_m);
		stringstream ss;
		if (_anchor & Match::ANCHOR_LEFT) {
			ss << _match_template << " " << pos;
		} else if (_anchor & Match::ANCHOR_RIGHT) {
			ss << pos << " " << _match_template;
		} else {
			ss << pos << " " << _match_template
			   << " ";
		}
		_lines[pos] = ss.str();
	}

	virtual void expect(const string& match, size_t number) {
		unique_lock<shared_mutex> ul(_m);
		mt19937 rng(random_device{}());
		uniform_int_distribution<size_t> dist(
			0, _lines.size() - 1);
		while (number) {
			size_t pos = dist(rng);
			if (_lines[pos].empty()) {
				--number;
				_lines[pos] = match;
			}
		}
	}

	void dump() {
		unique_lock<shared_mutex> ul(_m);
		for (size_t i = 0; i < _lines.size(); ++i) {
			if (_lines[i].empty()) continue;
			cout << i << " " << _lines[i] << endl;
		}
	}


protected:
	void create(size_t pos) const {
		stringstream ss;
		// add half of them being matches for the wrong anchor
		if (pos % 4 == 0) ss << " ";
		if (_anchor & Match::ANCHOR_RIGHT && pos % 2 == 0)
			ss << _match_template;
		ss << "line " << pos;
		if (_anchor & Match::ANCHOR_LEFT && pos % 2 == 0)
			ss << _match_template;
		if (pos % 4 == 0) ss << " ";
		_lines[pos] = ss.str();
	}

	mutable size_t  _total_checked;
	int _anchor;
	mutable vector<size_t> _checked;
	mutable vector<string> _lines;
	string _match_template;

	bool _flag_check;
};

#endif  // __MOCK_LOG_LINES_FOR_FILTER__H__
