#include "CliTest.h"

#include <alib/iostream>

#include <lexer/Lexer.h>
#include <parser/Parser.h>

#include <primitive/Integer.h>

#include <sys/stat.h>
#include <registry/AlgorithmRegistry.hpp>
#include <registration/AlgoRegistration.hpp>

CPPUNIT_TEST_SUITE_NAMED_REGISTRATION ( CliTest, "common" );
CPPUNIT_TEST_SUITE_REGISTRATION ( CliTest );

void CliTest::setUp ( ) {
}

void CliTest::tearDown ( ) {
}

class One {
public:
	static int one ( ) {
		return 1;
	}
};

class Two {
public:
	static int two ( ) {
		return 2;
	}
};

class Add {
public:
	static int add ( int a, const int & b ) {
		return a + b;
	}

	static int add2 ( const primitive::Integer & a, int b ) {
		return a.getData ( ) + b;
	}
};

class Neg {
public:
	static int neg ( int a ) {
		return - a;
	}
};

class IntToDouble {
public:
	static double cast ( int a ) {
		return a;
	}
};

class Divide {
public:
	static double divide ( double a, double b ) {
		return a / b;
	}

	static int divide2 ( int a, int b ) {
		return a / b;
	}
};

void CliTest::testCreateUnique ( ) {
	abstraction::AlgorithmRegistry::registerAlgorithm < One > ( One::one, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, std::array < std::string, 0 > ( ) );
	abstraction::AlgorithmRegistry::registerAlgorithm < Two > ( Two::two, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, std::array < std::string, 0 > ( ) );
	abstraction::AlgorithmRegistry::registerAlgorithm < Add > ( Add::add, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, std::array < std::string, 2 > ( ) );
	abstraction::AlgorithmRegistry::registerAlgorithm < Add > ( Add::add2, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, std::array < std::string, 2 > ( ) );
	abstraction::AlgorithmRegistry::registerAlgorithm < Neg > ( Neg::neg, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, std::array < std::string, 1 > ( ) );
	abstraction::AlgorithmRegistry::registerAlgorithm < Divide > ( Divide::divide, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, std::array < std::string, 2 > ( ) );
	abstraction::AlgorithmRegistry::registerAlgorithm < Divide > ( Divide::divide2, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, std::array < std::string, 2 > ( ) );

	mkdir ( "local", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH );

	// execute automaton::Determinize < aaa.xml <( parseString aaa.text ) <(decodeTree aaa.raw) > res.xml // determinizuj, parametry podle ocekavaneho typu z xml, string z textove reprezentace, strom z raw reprezentace; zapis jako xml do souboru
	// execute automaton::RandomAutomaton DFA 2 2 2 // nahodny automat DFA s danymi vlastnostmi, vypis na konzoli
	// execute string::Normalize < str.xml | string::PatternMatch - < pattern.xml > match_result.xml // - je predchozi vysledek

	cli::Environment environment;
	environment.setBinding ( "1", "1" );
	cli::Parser parser ( cli::Lexer ( "execute One | Add <( Add (int) #1 <(One) ) - | Neg - > local/xxx.xml" ) );
	parser.parse ( )->run ( environment );

	environment.setBinding ( "2", "local/xxx.xml" );
	parser = cli::Parser ( cli::Lexer ( "execute One | Add <( Add (int) <#2 <(One) ) - | Neg (double) - | Divide (double) - <(One | (double) Add <(One) - )" ) );
	parser.parse ( )->run ( environment );

	parser = cli::Parser ( cli::Lexer ( "execute <:int #2" ) );
	parser.parse ( )->run ( environment );

	parser = cli::Parser ( cli::Lexer ( "execute One > $res" ) );
	parser.parse ( )->run ( environment );

	parser = cli::Parser ( cli::Lexer ( "execute $res" ) );
	parser.parse ( )->run ( environment );

}

class Source {
public:
	static std::unique_ptr < int > source ( ) {
		return std::make_unique < int > ( 1 );
	}
};

class Sink {
public:
	static void sink ( std::unique_ptr < int > val ) {
		std::cout << * val << std::endl;
	}
};

void CliTest::testMove ( ) {
	abstraction::AlgorithmRegistry::registerAlgorithm < Source > ( Source::source, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, std::array < std::string, 0 > ( ) );
	abstraction::AlgorithmRegistry::registerAlgorithm < Sink > ( Sink::sink, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, std::array < std::string, 1 > ( ) );

	cli::Environment environment;
	cli::Parser parser ( cli::Lexer ( "execute Source | Sink ^ - >" ) );
	parser.parse ( )->run ( environment );
}

static std::unique_ptr < int > source;
static std::unique_ptr < int > target;

class RvalueReferenceProvider {
public:
	static std::unique_ptr < int > && foo ( ) {
		return std::move ( source );
	}
};

class RvalueReferenceAcceptor {
public:
	static void bar ( std::unique_ptr < int > && out ) {
		target = std::move ( out );
	}
};

void CliTest::testRvalueReferencePassing ( ) {
	abstraction::AlgorithmRegistry::registerAlgorithm < RvalueReferenceProvider > ( RvalueReferenceProvider::foo, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, std::array < std::string, 0 > ( ) );
	abstraction::AlgorithmRegistry::registerAlgorithm < RvalueReferenceAcceptor > ( RvalueReferenceAcceptor::bar, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, std::array < std::string, 1 > ( ) );

	try {
		source = std::make_unique < int > ( 1 );
		cli::Environment environment;
		cli::Parser parser ( cli::Lexer ( "execute RvalueReferenceProvider | RvalueReferenceAcceptor - >" ) );
		parser.parse ( )->run ( environment );

		CPPUNIT_ASSERT ( false );
	} catch ( ... ) {
		CPPUNIT_ASSERT ( true );
	}

	source = std::make_unique < int > ( 1 );
	cli::Environment environment;
	cli::Parser parser ( cli::Lexer ( "execute RvalueReferenceProvider | RvalueReferenceAcceptor ^ - >" ) );
	parser.parse ( )->run ( environment );

	CPPUNIT_ASSERT ( * target == 1 );
	CPPUNIT_ASSERT ( source == nullptr ); /* implementation specific */
}

class ConstReferenceProvider {
public:
	static const std::string & foo ( ) {
		static std::string dummy = "dummy";
		return dummy;
	}
};

class ConstReferenceAcceptor {
public:
	static void bar ( const std::string & str ) {
		std::cout << str << std::endl;
	}
};

void CliTest::testConstReferencePassing ( ) {
	abstraction::AlgorithmRegistry::registerAlgorithm < ConstReferenceProvider > ( ConstReferenceProvider::foo, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, std::array < std::string, 0 > ( ) );
	abstraction::AlgorithmRegistry::registerAlgorithm < ConstReferenceAcceptor > ( ConstReferenceAcceptor::bar, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, std::array < std::string, 1 > ( ) );

	cli::Environment environment;
	cli::Parser parser ( cli::Lexer ( "execute ConstReferenceProvider | ConstReferenceAcceptor - >" ) );
	parser.parse ( )->run ( environment );
}

class ReferenceProvider {
public:
	static std::ostream & foo ( ) {
		return std::cout;
	}
};

class ReferenceAcceptor {
public:
	static void bar ( std::ostream & out ) {
		out << "yay" << std::endl;
	}
};

void CliTest::testReferencePassing ( ) {
	abstraction::AlgorithmRegistry::registerAlgorithm < ReferenceProvider > ( ReferenceProvider::foo, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, std::array < std::string, 0 > ( ) );
	abstraction::AlgorithmRegistry::registerAlgorithm < ReferenceAcceptor > ( ReferenceAcceptor::bar, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, std::array < std::string, 1 > ( ) );

	cli::Environment environment;
	cli::Parser parser ( cli::Lexer ( "execute ReferenceProvider | ReferenceAcceptor - >" ) );
	parser.parse ( )->run ( environment );
}

class ConstRvalueReferenceProvider {
public:
	static const std::string && foo ( ) {
		static std::string dummy = "dummy";
		return std::move ( dummy );
	}
};

class ConstRvalueReferenceAcceptor {
public:
	static void bar ( const std::string && str ) {
		std::cout << str << std::endl;
	}
};

void CliTest::testConstRvalueReferencePassing ( ) {
	abstraction::AlgorithmRegistry::registerAlgorithm < ConstRvalueReferenceProvider > ( ConstRvalueReferenceProvider::foo, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, std::array < std::string, 0 > ( ) );
	abstraction::AlgorithmRegistry::registerAlgorithm < ConstRvalueReferenceAcceptor > ( ConstRvalueReferenceAcceptor::bar, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, std::array < std::string, 1 > ( ) );

	cli::Environment environment;
	cli::Parser parser ( cli::Lexer ( "execute ConstRvalueReferenceProvider | ConstRvalueReferenceAcceptor ^ - >" ) );
	parser.parse ( )->run ( environment );
}

class Print {
public:
	static void print ( ext::set < int > theSet ) {
		for ( int value : theSet )
			std::cout << value << ", ";
		std::cout << std::endl;
	}
};

void CliTest::testSetConstruction ( ) {
	abstraction::AlgorithmRegistry::registerAlgorithm < Print > ( Print::print, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, std::array < std::string, 1 > ( ) );

	cli::Environment environment;
	cli::Parser parser ( cli::Lexer ( "execute { :int 1 2 3 } > $set" ) );
	parser.parse ( )->run ( environment );
	std::cout << environment.getVariable ( "set" )->getReturnType ( ) << std::endl;
	parser = cli::Parser ( cli::Lexer ( "execute $set | Print -" ) );
	parser.parse ( )->run ( environment );

	parser = cli::Parser ( cli::Lexer ( "execute $set >local/yyy.xml" ) );
	parser.parse ( )->run ( environment );

	parser = cli::Parser ( cli::Lexer ( "execute <{ :int }local/yyy.xml > $set2" ) );
	parser.parse ( )->run ( environment );
	std::cout << environment.getVariable ( "set2" )->getReturnType ( ) << std::endl;
	parser = cli::Parser ( cli::Lexer ( "execute $set2 | Print -" ) );
	parser.parse ( )->run ( environment );
}

class Foo {
	int m_base;
public:
	Foo ( int base ) : m_base ( base ) {
	}

	int bar ( int value ) {
		return m_base + value;
	}

	int base ( ) {
		return m_base;
	}

	static Foo make_foo ( int base ) {
		return Foo ( base );
	}
};

namespace {

auto fooBar = registration::MethodRegister < Foo, int, Foo, int > ( & Foo::bar, "bar" );

} /* namespace */

void CliTest::testMember ( ) {
	abstraction::AlgorithmRegistry::registerAlgorithm < Foo > ( Foo::make_foo, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, std::array < std::string, 1 > ( ) );
	abstraction::AlgorithmRegistry::registerMethod < Foo > ( & Foo::base, "base", std::array < std::string, 0 > ( ) );

	cli::Environment environment;
	cli::Parser parser ( cli::Lexer ( "execute Foo 3 | Foo::base -" ) );
	parser.parse ( )->run ( environment );
	parser = cli::Parser ( cli::Lexer ( "execute Foo 3 | Foo::bar - 2" ) );
	parser.parse ( )->run ( environment );
}