#ifndef __SEARCH_RANGE__H__
#define __SEARCH_RANGE__H__

#include <vector>

#include "line_filter_keyword.h"
#include "log_lines.h"

using namespace std;

/* Helper class with static functions used to search multiple keywords for ranges */
class SearchRange {
public:
	// returns the furthest position starting from pos in direction dir such
	// that all keywords have been search until the range. T is either
	// LineFilterKeyword* or unique_ptr<LineFilterKeyword>
	template <typename T>
	static size_t junction_range(size_t pos, bool dir,
				      const vector<T>& keywords,
				      size_t length) {
		static_assert(
			is_same_v<decay_t<T>, LineFilterKeyword*> ||
			is_same_v<decay_t<T>, unique_ptr<LineFilterKeyword>>,
			"T must be LineFilterKeyword* or unique_ptr<LineFilterKeyword>"
		);

		size_t ret = G::NO_POS;
		// search all keywords, get their searched range, and return the
		// minimum, meaning all have been searched to that point
		for (auto &x : keywords) {
			size_t range;
			if (x->empty()) {
				if (!dir) range = 0;
				else range = length - 1;
			} else {
				range = x->next_range(pos, dir);
			}
			if (dir && (ret == G::NO_POS ||
			   (range != G::NO_POS && range < ret)))
				ret = range;
			if (!dir && (ret == G::NO_POS ||
			   (range != G::NO_POS && range > ret)))
				ret = range;
		}
		return ret;
	}

	/* returns a pair of position and range corresponding to the position of
	 * the next keyword starting for pos and searching in direction dir. The
	 * range indicates how far it was able to search. Returns G::NO_POS if
	 * not found within the valid search range. T is either
	 * LineFilterKeyword* or unique_ptr<LineFilterKeyword>. */
	template <typename T>
	static pair<size_t, size_t> keyword_disjunction(
			size_t pos,
			bool dir,
			const vector<T>& keywords,
			size_t total_length) {
                size_t range = junction_range(
                        pos, dir, keywords, total_length);
                size_t ret = G::NO_POS;
                if (range == G::NO_POS) return make_pair(G::NO_POS, G::NO_POS);
                if (pos == G::NO_POS) return make_pair(G::NO_POS, G::NO_POS);
                if (dir && pos > range) return make_pair(G::NO_POS, G::NO_POS);
                for (auto &x : keywords) {
                        if (x->empty()) continue;
                        size_t match = x->next_match(pos, dir);
                        if (dir && (ret == G::NO_POS ||
		           (match != G::NO_POS && match < ret)))
				ret = match;
                        if (!dir && (ret == G::NO_POS ||
			   (match != G::NO_POS && match > ret)))
                                ret = match;
                }
                return make_pair(ret, range);
        }

	/* returns a pair of position and range corresponding to the position of
	 * the next line that has all keywords matching starting for pos and
	 * searching in direction dir. The range indicates how far it was able
	 * to search. Returns G::NO_POS if not found within the valid search
	 * range. T is either LineFilterKeyword* or
	 * unique_ptr<LineFilterKeyword>. */
	template <typename T>
	static pair<size_t, size_t> keyword_conjunction(
			size_t pos,
			bool dir,
			const vector<T>& keywords,
			size_t total_length) {
                size_t range = junction_range(
                        pos, dir, keywords, total_length);

                size_t ret = pos - !dir;
                if (range == G::NO_POS || ret == G::NO_POS || pos == G::NO_POS)
                        return make_pair(G::NO_POS, G::NO_POS);
                if (dir && pos > range) return make_pair(G::NO_POS, G::NO_POS);
                bool dirty = true;
                while (dirty) {
                        dirty = false;
                        for (auto &x : keywords) {
                                if (x->empty()) continue;
                                if (x->is_match(ret)) continue;
                                size_t match = x->next_match(ret, dir);
                                if (match == G::NO_POS)
                                        return make_pair(G::NO_POS, G::NO_POS);
                                if (dir && match > range)
                                        return make_pair(G::NO_POS, G::NO_POS);
                                if (!dir && match < range)
                                        return make_pair(G::NO_POS, G::NO_POS);
                                if (ret != match) {
                                        dirty = true;
                                        ret = match;
                                }
                        }
                }
                return make_pair(ret, range);
        }
};

#endif  // __SEARCH_RANGE__H__
