#include <catch2/catch.hpp>

#include <core/components.hpp>

#include <alib/set>
#include <alib/linear_set>

struct GeneralAlphabet {
};

struct NonlinearAlphabet {
};

struct SubtreeWildcard {
};

class A : public core::Components < A, ext::linear_set < std::string >, component::Set, std::tuple < GeneralAlphabet, NonlinearAlphabet >, std::string, component::Value, SubtreeWildcard > {
public:
	A ( std::string string ) : core::Components < A, ext::linear_set < std::string >, component::Set, std::tuple < GeneralAlphabet, NonlinearAlphabet >, std::string, component::Value, SubtreeWildcard > ( ext::linear_set < std::string > { string, "aaa" }, ext::linear_set < std::string > { "aaa" }, string ) {
	}
};

namespace core {

template < >
class SetConstraint< A, std::string, GeneralAlphabet > {
public:
	static bool used ( const A & a, const std::string & str ) {
		return a.accessComponent < NonlinearAlphabet > ( ).get ( ).count ( str ) || a.accessComponent < SubtreeWildcard > ( ).get ( ) == str;
	}

	static bool available ( const A &, const std::string & ) {
		return true;
	}

	static void valid ( const A &, const std::string & ) {
	}
};

template < >
class SetConstraint< A, std::string, NonlinearAlphabet > {
public:
	static bool used ( const A &, const std::string & ) {
		return false;
	}

	static bool available ( const A & a, const std::string & str ) {
		return a.accessComponent < GeneralAlphabet > ( ).get ( ).count ( str );
	}

	static void valid ( const A & a, const std::string & str ) {
		if ( a.accessComponent < SubtreeWildcard > ( ).get ( ) == str )
			throw::exception::CommonException ( "Symbol " + ( std::string ) str + "cannot be set as nonlinear variable since it is already a subtree wildcard" );
	}
};

template < >
class ElementConstraint< A, std::string, SubtreeWildcard > {
public:
	static bool available ( const A & a, const std::string & str ) {
		return a.accessComponent < GeneralAlphabet > ( ).get ( ).count ( str );
	}

	static void valid ( const A & a, const std::string & str ) {
		if ( a.accessComponent < NonlinearAlphabet > ( ).get ( ).count ( str ) )
			throw::exception::CommonException ( "Symbol " + ( std::string ) str + "cannot be set as subtree wildcard since it is already a nonlinear variable" );
	}
};

} /* namespace core */

class B : public core::Components < B, ext::set < std::string >, component::Set, GeneralAlphabet, ext::set < std::string >, component::Set, NonlinearAlphabet > {
};

namespace core {

template < >
class SetConstraint< B, std::string, GeneralAlphabet > {
public:
	static bool used ( const B &, const std::string & ) {
		return false;
	}

	static bool available ( const B &, const std::string & ) {
		return true;
	}

	static void valid ( const B &, const std::string & ) {
	}
};

template < >
class SetConstraint< B, std::string, NonlinearAlphabet > {
public:
	static bool used ( const B &, const std::string & ) {
		return false;
	}

	static bool available ( const B &, const std::string & ) {
		return true;
	}

	static void valid ( const B &, const std::string & ) {
	}
};

} /* namespace core */


TEST_CASE ( "Compontents", "[unit][core]" ) {
	A tmp ( "2" );

	SECTION ( "Test Add" ) {
		CHECK_THROWS_AS ( tmp.accessComponent < NonlinearAlphabet > ( ).add ( "1" ), exception::CommonException );
		tmp.accessComponent < GeneralAlphabet > ( ).add ( "1" );
		tmp.accessComponent < NonlinearAlphabet > ( ).add ( "1" );

		B tmp2 = B ( );
	}

	SECTION ( "Test Remove" ) {
		tmp.accessComponent < GeneralAlphabet > ( ).add ( "1" );
		tmp.accessComponent < NonlinearAlphabet > ( ).add ( "1" );
		REQUIRE_THROWS_AS ( tmp.accessComponent < GeneralAlphabet > ( ).remove ( "1" ), exception::CommonException );
		REQUIRE_THROWS_AS ( tmp.accessComponent < GeneralAlphabet > ( ).remove ( ext::linear_set < std::string > { "1" } ), exception::CommonException );
		REQUIRE_NOTHROW ( tmp.accessComponent < GeneralAlphabet > ( ).set ( ext::linear_set < std::string > { "1", "2", "aaa", "3" } ) );
		REQUIRE_THROWS_AS ( tmp.accessComponent < GeneralAlphabet > ( ).set ( ext::linear_set < std::string > { } ), exception::CommonException );

		REQUIRE ( tmp.accessComponent < GeneralAlphabet > ( ).get ( ).size ( ) == 4 );

		REQUIRE_NOTHROW ( tmp.accessComponent < NonlinearAlphabet > ( ).set ( ext::linear_set < std::string > { "1", "3" } ) );
		REQUIRE_THROWS_AS ( tmp.accessComponent < NonlinearAlphabet > ( ).set ( ext::linear_set < std::string > { "1", "4" } ), exception::CommonException );

		REQUIRE_NOTHROW ( tmp.accessComponent < NonlinearAlphabet > ( ).remove ( "1" ) );
		REQUIRE_NOTHROW ( tmp.accessComponent < GeneralAlphabet > ( ).remove ( "1" ) );
	}
}
