/*
 * aql.cpp
 *
 *  Created on: 1. 7. 2017
 *	  Author: Jan Travnicek
 */

#include <version.hpp>

#include <fstream>

template < class T, class U >
std::istream& operator>> ( std::istream & in, std::pair < T, U > & value ) {
	std::string tmp;
	in >> tmp;
	size_t posEqual = tmp.find ( '=' );

	if ( posEqual == std::string::npos )
		in.setstate(std::ios_base::failbit);

	value.first = tmp.substr ( 0, posEqual );
	value.second = tmp.substr ( posEqual + 1, tmp.size ( ) );

	if ( value.first.empty ( ) || value.second.empty ( ) )
		in.setstate(std::ios_base::failbit);

	return in;
}

#include <string>

#include <alib/exception>

#include <tclap/CmdLine.h>
#include <global/GlobalData.h>

#include "prompt/Prompt.h"
#include "prompt/ReadlineLineInterface.h"

#include <readline/IstreamLineInterface.h>
#include <readline/StringLineInterface.h>

#include <prompt/ReadlinePromptHistory.h>

#include <common/ResultInterpret.h>

namespace TCLAP {

template < class T, class U >
struct ArgTraits < std::pair < T, U > > {
	typedef ValueLike ValueCategory;
};

} /* namespace TCLAP */

class InteractiveVisitor : public TCLAP::Visitor {
public:
	void visit ( ) override {
		Prompt::getPrompt ( ).appendCharSequence ( ReadlineLineInterface ( true ) );
	}
};

class MultiArgVisitor : public TCLAP::Visitor {
protected:
	TCLAP::MultiArg < std::string > * m_arg;

public:
	void setArg ( TCLAP::MultiArg < std::string > * arg ) {
		m_arg = arg;
	}
};

class FileVisitor : public MultiArgVisitor {
public:
	void visit ( ) override {
		std::ifstream ifs ( m_arg->getValue ( ).back ( ) );

		if ( ! ifs.is_open ( ) )
			throw exception::CommonException ( "File '" + m_arg->getValue ( ).back ( ) + "' not found." );

		Prompt::getPrompt ( ).appendCharSequence ( cli::IstreamLineInterface < std::ifstream > ( std::move ( ifs ) ) );
	}
};

class QueriesVisitor : public MultiArgVisitor {
public:
	void visit ( ) override {
		Prompt::getPrompt ( ).appendCharSequence ( cli::StringLineInterface ( m_arg->getValue ( ).back ( ) ) );
	}
};

int main ( int argc, char * argv[] ) {
	alib::ExceptionHandler::addHandler < 2 > ( [] ( alib::ExceptionHandler::NestedExceptionContainer & exceptions, const TCLAP::ArgException & exception ) {
		exceptions.push_back ( exception.error ( ) );
	} );

	const char* envHome = std::getenv ( "HOME" );
	std::unique_ptr < ReadlinePromptHistory > historyHandler;
	if ( envHome != nullptr ) {
		historyHandler = std::make_unique < ReadlinePromptHistory > ( std::string ( envHome ) + "/.aql_history" );
	}

	try {

		common::GlobalData::argc = argc;
		common::GlobalData::argv = argv;

		TCLAP::CmdLine cmd ( "Algorithms Query Language shell", ' ', ALIB_VERSION ); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall)

		TCLAP::SwitchArg verbose ( "v", "verbose", "Sets verbose mode (set verbose 1)", false );
		cmd.add ( verbose );

		TCLAP::MultiArg < std::pair < std::string, std::string > > params ( "e", "env", "Environment variable", false, "key = val");
		cmd.add ( params );

		InteractiveVisitor * interactiveVisitor = new InteractiveVisitor ( );
		TCLAP::SwitchArg interactive ( "i", "interactive", "Stay in interactive mode after -c or -f", false, interactiveVisitor );
		cmd.add ( interactive );

		FileVisitor * fileVisitor = new FileVisitor ( );
		TCLAP::MultiArg < std::string > files ( "f", "file", "Loads an aql file", false, "FILE", fileVisitor );
		fileVisitor->setArg ( & files );
		cmd.add ( files );

		QueriesVisitor * queriesVisitor = new QueriesVisitor ( );
		TCLAP::MultiArg < std::string > queries ( "c", "command", "Query to execute. Multiple queries execute in sequential order.", false, "string", queriesVisitor );
		queriesVisitor->setArg ( & queries );
		cmd.add ( queries );

		cmd.parse ( argc, argv );

		common::GlobalData::verbose = verbose.isSet();

		/* --------------------------------------------------------------------------------------------------------- */

		cli::Environment & environment = Prompt::getPrompt ( ).getEnvironment ( );

		environment.setBinding ( "stdin", "-" );
		environment.setBinding ( "stdout", "-" );

		for ( const std::pair < std::string, std::string > & param : params.getValue ( ) ) {
			environment.setBinding ( param.first, param.second );
		}

		/* --------------------------------------------------------------------------------------------------------- */

		// if no -f, -c, or -i, go interactive.
		if ( ! queries.isSet ( ) && ! files.isSet ( ) && ! interactive.isSet ( ) ) {
			Prompt::getPrompt ( ).appendCharSequence ( ReadlineLineInterface ( true ) );
		}

		cli::CommandResult res = Prompt::getPrompt ( ).run ( );

		/* --------------------------------------------------------------------------------------------------------- */

		delete queriesVisitor;
		delete fileVisitor;
		delete interactiveVisitor;

		if ( res == cli::CommandResult::QUIT || res == cli::CommandResult::RETURN )
			return cli::ResultInterpret::cli ( Prompt::getPrompt ( ).getEnvironment ( ).getResult ( ) );
		else if ( res == cli::CommandResult::EOT || res == cli::CommandResult::OK )
			return 0;
		else
			return 4;
	} catch ( ... ) {
		return alib::ExceptionHandler::handle ( common::Streams::err );
	}
}
