#include <libwccl/ops/functions/bool/predicates/weakagreement.h>

namespace Wccl {

std::string WeakAgreement::to_string(const Corpus2::Tagset& tagset) const
{
	std::ostringstream ss;
	ss << name(tagset) << "("
		<< left_pos_expr_->to_string(tagset) << ", "
		<< right_pos_expr_->to_string(tagset) << ", "
		<< attribs_expr_->to_string(tagset) << ")";
	return ss.str();
}

std::ostream& WeakAgreement::write_to(std::ostream& os) const
{
	return os << raw_name() << "("
			<< *left_pos_expr_ << ", "
			<< *right_pos_expr_ << ", "
			<< *attribs_expr_ << ")";
}

WeakAgreement::BaseRetValPtr WeakAgreement::apply_internal(const FunExecContext& context) const
{
	const SentenceContext& sc = context.sentence_context();

	const boost::shared_ptr<const Position>& range_left = left_pos_expr_->apply(context);
	if (range_left->get_value() == Position::Nowhere) {
		return Predicate::False(context);
	}
	const boost::shared_ptr<const Position>& range_right = right_pos_expr_->apply(context);
	if (range_right->get_value() == Position::Nowhere) {
		return Predicate::False(context);
	}
	// Get absolute values for left and right extremes of the range.
	int abs_left = sc.get_abs_position(*range_left);
	int abs_right = sc.get_abs_position(*range_right);
	// Trim range to sentence boundaries
	if (abs_left < 0) {
		abs_left = 0;
	}
	if (abs_right >= sc.size()) {
		abs_right = sc.size() - 1;
	}
	// Proceed only if range isn't empty (range outside of sentence or empty sentence are covered)
	if (abs_left > abs_right) {
		return Predicate::False(context);
	}

	const boost::shared_ptr<const TSet>& attribs = attribs_expr_->apply(context);

	int min_card = attribs->categories_count(tagset_);

	for(int i = abs_left; i <= abs_right; ++i) {
		bool i_has_matched_tag = false;
		foreach (const Corpus2::Lexeme& i_lex, sc.at(i)->lexemes()) {
			const Corpus2::Tag& i_tag = i_lex.tag();
			if (attribs->matching_categories(i_tag) >= min_card) {
				i_has_matched_tag = true;
				for(int j = abs_right; j > i; --j) {
					bool i_agrees_with_j = false;
					bool j_has_matched_tag = false;
					foreach(const Corpus2::Lexeme& j_lex, sc.at(j)->lexemes()) {
						const Corpus2::Tag& j_tag = j_lex.tag();
						if (attribs->matching_categories(i_tag) >= min_card) {
							j_has_matched_tag = true;
							Corpus2::Tag intersection = i_tag.get_masked(j_tag);
							// if the intersection matches enough categories we have agreement
							if (attribs->matching_categories(intersection) >= min_card) {
								i_agrees_with_j = true;
								break;
							}
						}
					}
					if (j_has_matched_tag && !i_agrees_with_j) {
						return Predicate::False(context);
					}
				}
			}
		}
		if (!i_has_matched_tag && (i == abs_left || i == abs_right)) {
			return Predicate::False(context);
		}
	}

	return Predicate::True(context);
}

} /* end ns Wccl */