#include <catch2/catch.hpp>

#include <alib/variant>

#include "sax/SaxParseInterface.h"
#include "sax/SaxComposeInterface.h"

#include "string/LinearString.h"
#include "string/xml/LinearString.h"
#include "string/CyclicString.h"
#include "string/xml/CyclicString.h"

#include "factory/XmlDataFactory.hpp"

#include "alphabet/BlankSymbol.h"

#include <primitive/xml/Character.h>
#include <container/xml/ObjectsSet.h>

TEST_CASE ( "String", "[unit][data][string]" ) {
	SECTION ( "LinearString basic usage" ) {
		ext::vector < DefaultSymbolType > expectedContent;
		size_t expectedSize;

		SECTION ( "empty string" ) {
			expectedContent = { };
			expectedSize = 0;
		}

		SECTION ( "string aabac" ) {
			expectedContent = { DefaultSymbolType ( "a" ), DefaultSymbolType ( "a" ), DefaultSymbolType ( "b" ), DefaultSymbolType ( "a" ), DefaultSymbolType ( "c" ) };
			expectedSize = 5;
		}

		string::LinearString < DefaultSymbolType > string ( expectedContent );
		CHECK ( string.getContent ( ) == expectedContent );
		CHECK ( string.size ( ) == expectedSize );
		CHECK ( string.empty ( ) == ( string.size ( ) == 0 ) );
	}

	SECTION ( "Copy constructor" ) {
		string::LinearString < > string;

		string.accessComponent < string::GeneralAlphabet > ( ).set( { DefaultSymbolType ( "1" ), DefaultSymbolType ( "2" ), DefaultSymbolType ( "3" ) } );
		string.setContent ( { DefaultSymbolType ( "1" ), DefaultSymbolType ( "2" ), DefaultSymbolType ( "1" ) } );
		string::LinearString < > string2 ( string );

		CHECK ( string == string2 );

		string::LinearString < > string3 ( std::move ( string ) );

		CHECK ( string2 == string3 );
	}

	SECTION ( "Xml Parser" ) {

		string::LinearString < > string;

		string.accessComponent < string::GeneralAlphabet > ( ).set( { DefaultSymbolType ( "1" ), DefaultSymbolType ( "2" ), DefaultSymbolType ( "3" ) } );
		string.setContent ( { DefaultSymbolType ( "1" ), DefaultSymbolType ( "2" ), DefaultSymbolType ( "1" ) } );

		{
			ext::deque < sax::Token > tokens = factory::XmlDataFactory::toTokens ( string );
			std::string tmp = sax::SaxComposeInterface::composeMemory ( tokens );

			std::cout << tmp << std::endl;

			ext::deque < sax::Token > tokens2 = sax::SaxParseInterface::parseMemory ( tmp );
			string::LinearString < > string2 = factory::XmlDataFactory::fromTokens ( std::move( tokens2 ) );

			CHECK ( string == string2 );
		}
		{
			std::string tmp = factory::XmlDataFactory::toString ( string );
			std::cout << tmp << std::endl;
			string::LinearString < > string2 = factory::XmlDataFactory::fromString ( tmp );

			CHECK ( string == string2 );
		}
	}

	SECTION ( "String in map" ) {
		ext::map < ext::variant < string::LinearString < >, int >, int > testMap;
		ext::variant < string::LinearString < >, int > epsVar {
			string::LinearString < > { }
		};

		CHECK ( string::LinearString < > ( ) == epsVar.get < string::LinearString < > > ( ) );
		CHECK ( epsVar.get < string::LinearString < > > ( ) == string::LinearString < > ( ) );

		std::pair < ext::variant < string::LinearString < >, int >, int > epsVarPair = std::make_pair ( epsVar, 10 );
		CHECK ( string::LinearString < > ( ) == epsVarPair.first.get < string::LinearString < > > ( ) );
		CHECK ( epsVarPair.first.get < string::LinearString < > > ( ) == string::LinearString < > ( ) );

		testMap.insert ( std::make_pair ( ext::variant < string::LinearString < >, int > { string::LinearString < > { }
					}, 10 ) );
		CHECK ( testMap.find ( ext::variant < string::LinearString < >, int > { string::LinearString < > { }
					} ) != testMap.end ( ) );

		for ( const auto & entry : testMap ) {
			CHECK ( entry.first.is < string::LinearString < > > ( ) );

			if ( entry.first.is < string::LinearString < > > ( ) )
				CHECK ( string::LinearString < > ( ) == entry.first.get < string::LinearString < > > ( ) );

			CHECK ( entry.first.get < string::LinearString < > > ( ) == string::LinearString < > ( ) );
		}
	}

	SECTION ( "Normalize" ) {
		{
			string::LinearString < char > s1 ( ext::vector < char > { 'a', 'b' } );
			string::LinearString < > s2 = core::normalize < string::LinearString < char > >::eval ( string::LinearString < char > ( s1 ) );

			std::string tmp1 = factory::XmlDataFactory::toString ( s1 );
			std::cout << tmp1 << std::endl;
			string::LinearString < > s1x = factory::XmlDataFactory::fromString ( tmp1 );

			std::string tmp2 = factory::XmlDataFactory::toString ( s2 );
			std::cout << tmp2 << std::endl;
			string::LinearString < > s2x = factory::XmlDataFactory::fromString ( tmp2 );

			CHECK ( s1x == s2x );
		}
		{
			string::LinearString < object::Object > s1 ( ext::vector < object::Object > { object::Object ( 'a' ), object::Object ( 'b' ) } );
			string::LinearString < > s2 = core::normalize < string::LinearString < object::Object > >::eval ( string::LinearString < object::Object > ( s1 ) );

			std::string tmp1 = factory::XmlDataFactory::toString ( s1 );
			std::cout << tmp1 << std::endl;
			string::LinearString < > s1x = factory::XmlDataFactory::fromString ( tmp1 );

			std::string tmp2 = factory::XmlDataFactory::toString ( s2 );
			std::cout << tmp2 << std::endl;
			string::LinearString < > s2x = factory::XmlDataFactory::fromString ( tmp2 );

			CHECK ( s1 == s1x );
			CHECK ( s1 == s2x );
		}
		{
			string::LinearString < ext::set < char > > s1 ( ext::vector < ext::set < char > > { ext::set < char > { 'a' }, ext::set < char > { 'b' } } );
			string::LinearString < > s2 = core::normalize < string::LinearString < ext::set < char > > >::eval ( string::LinearString < ext::set < char > > ( s1 ) );

			std::string tmp1 = factory::XmlDataFactory::toString ( s1 );
			std::cout << tmp1 << std::endl;
			string::LinearString < > s1x = factory::XmlDataFactory::fromString ( tmp1 );

			std::string tmp2 = factory::XmlDataFactory::toString ( s2 );
			std::cout << tmp2 << std::endl;
			string::LinearString < > s2x = factory::XmlDataFactory::fromString ( tmp2 );

			CHECK ( s1x == s2x );

			string::LinearString < object::Object > ref ( ext::vector < object::Object > { object::Object ( object::AnyObject < ext::set < object::Object > > ( ext::set < object::Object > { object::Object ( object::AnyObject < char > ( 'a' ) ) } ) ), object::Object ( object::AnyObject < ext::set < object::Object > > ( ext::set < object::Object > { object::Object ( object::AnyObject < char > ( 'b' ) ) } ) ) } );

			std::cout << s1x << std::endl;
			std::cout << ref << std::endl;

			CHECK ( s1x == ref );
		}
	}
}
