#include <catch2/catch.hpp>

#include <alib/map>

#include "regexp/unbounded/UnboundedRegExp.h"

#include "regexp/simplify/RegExpOptimize.h"

#include <factory/StringDataFactory.hpp>
#include <regexp/string/UnboundedRegExp.h>

TEST_CASE ( "RegExp Optimize", "[unit][algo][regexp][simplify]" ) {
	SECTION ( "Test simple axioms" ) {
		auto testcase = GENERATE (
				std::make_pair ("a+a", "a" ),
				std::make_pair ( "(a+a)b + (#0 b + (#0 a + (#0 b + a)))", "a ( b + #E )" ),
				//std::make_pair ( "a z + a b* b z", "a b* z" ),
				std::make_pair ( "a***", "a*" ),
				std::make_pair ( "(a*+b*)*", "(a+b)*" ),
				std::make_pair ( "(a*b*)*", "(a+b)*" ),
				std::make_pair ( "#0*+a*", "a*" ),
				std::make_pair ( "a+(a+a)", "a" ),
				std::make_pair ( "(a+b*)(a+b*)*", "(a+b)*" ),
				std::make_pair ( "(a+b+d)*+e*+a+b+c+e", "c+e*+(a+b+d)*" ),

				std::make_pair ( "#E+x*x", "x*" ),
				std::make_pair ( "#E+(x y)*(x y)", "(x y)*" ),
				std::make_pair ( "#E+(x y)* x y z", "#E + x y (x y)* z" ),
				std::make_pair ( "#E+x*x y", "#E + x x* y" ),
				std::make_pair ( "(x+#E)*(x+#E)", "x*" ),
				std::make_pair ( "(x y)*x", "x(y x)*" ),
				std::make_pair ( "(a x y)* a x z", "a x ( y a x )* z" )
				);

		regexp::UnboundedRegExp < > orig = factory::StringDataFactory::fromString ( testcase.first );
		regexp::UnboundedRegExp < > origOpt = regexp::simplify::RegExpOptimize::optimize ( orig );
		regexp::UnboundedRegExp < > expected = factory::StringDataFactory::fromString ( testcase.second );

		CAPTURE ( testcase.first, testcase.second );
		CAPTURE ( orig, origOpt, expected );
		CHECK ( origOpt == expected );
	}

	SECTION ( "Test optimize templated" ) {
		regexp::UnboundedRegExpSymbol < char > a ( 'a' );
		regexp::UnboundedRegExpSymbol < char > b ( 'b' );
		regexp::UnboundedRegExpSymbol < char > z ( 'z' );

		{
			regexp::UnboundedRegExpAlternation < char > alt;
			alt.appendElement ( a );
			alt.appendElement ( a );

			regexp::UnboundedRegExp < char > regexp ( regexp::UnboundedRegExpStructure < char > { alt } );
			regexp::UnboundedRegExp < char > res = regexp::simplify::RegExpOptimize::optimize(regexp);
			regexp::UnboundedRegExp < char > regexpRes( regexp::UnboundedRegExpStructure < char > { a } );

			CAPTURE ( res, regexpRes );
			CHECK ( regexpRes == res );
		}
		{
			regexp::UnboundedRegExpAlternation < char > alt1;
			alt1.appendElement ( a );
			alt1.appendElement ( a );
			regexp::UnboundedRegExpConcatenation < char > con1;
			con1.appendElement ( alt1 );
			con1.appendElement ( b );

			regexp::UnboundedRegExpConcatenation < char > con2;
			con2.appendElement ( regexp::UnboundedRegExpEmpty < char > { } );
			con2.appendElement ( b );
			regexp::UnboundedRegExpAlternation < char > alt2;
			alt2.appendElement ( con2 );
			alt2.appendElement ( a );

			regexp::UnboundedRegExpConcatenation < char > con3;
			con3.appendElement ( regexp::UnboundedRegExpEmpty < char > { } );
			con3.appendElement ( a );
			regexp::UnboundedRegExpAlternation < char > alt3;
			alt3.appendElement ( con3 );
			alt3.appendElement ( alt2 );

			regexp::UnboundedRegExpConcatenation < char > con4;
			con4.appendElement ( regexp::UnboundedRegExpEmpty < char > { } );
			con4.appendElement ( b );
			regexp::UnboundedRegExpAlternation < char > alt4;
			alt4.appendElement ( con4 );
			alt4.appendElement ( alt3 );

			regexp::UnboundedRegExpAlternation < char > alt5;
			alt5.appendElement ( con1 );
			alt5.appendElement ( alt4 );

			regexp::UnboundedRegExp < char > regexp ( regexp::UnboundedRegExpStructure < char > { alt5 } );

			regexp::UnboundedRegExp < char > res = regexp::simplify::RegExpOptimize::optimize ( regexp );

			regexp::UnboundedRegExpAlternation < char > alt6;
			alt6.appendElement ( b );
			alt6.appendElement ( regexp::UnboundedRegExpEpsilon < char > { } );
			regexp::UnboundedRegExpConcatenation < char > con6;
			con6.appendElement ( a );
			con6.appendElement ( alt6 );

			regexp::UnboundedRegExp < char > regexpRes( regexp::UnboundedRegExpStructure < char > { con6 } );

			CAPTURE ( regexp, res, regexpRes );
			CHECK ( regexpRes == res );
		}
		{
			regexp::UnboundedRegExpSymbol < char > symb { 'b' };
			regexp::UnboundedRegExpIteration < char > iter { symb };

			regexp::UnboundedRegExpConcatenation < char > con1;
			con1.appendElement ( symb );
			con1.appendElement ( iter );

			regexp::UnboundedRegExpAlternation < char > alt1;
			alt1.appendElement ( con1 );

			regexp::UnboundedRegExpAlternation < char > alt2;

			regexp::UnboundedRegExpConcatenation < char > con2;
			con2.appendElement ( alt1 );
			con2.appendElement ( alt2 );

			regexp::UnboundedRegExpAlternation < char > alt3;

			regexp::UnboundedRegExpAlternation < char > alt4;
			alt4.appendElement ( alt3 );
			alt4.appendElement ( con2 );

			regexp::UnboundedRegExp < char > regexp( regexp::UnboundedRegExpStructure < char > { alt4 } );
			regexp::UnboundedRegExp < char > res = regexp::simplify::RegExpOptimize::optimize(regexp);
			regexp::UnboundedRegExp < char > regexpRes( regexp::UnboundedRegExpStructure < char > { } );

			CAPTURE ( regexp, res, regexpRes );
			CHECK ( regexpRes == res );
		}
	}
}
