/*
 * Dot.cpp
 *
 *  Created on: 16. 8. 2017
 *	  Author: Jan Travnicek, Tomas Pecka
 */

#include "Dot.h"
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <numeric>
#include <sys/wait.h>
#include <unistd.h>

#include <alib/algorithm>
#include <exception/CommonException.h>
#include <registration/AlgoRegistration.hpp>

namespace cli::builtin {

std::vector < std::string> Dot::allowedOutputTypes { "dot", "xdot", "ps", "pdf", "svg", "svgz", "fig", "png", "gif", "jpg", "jpeg", "json", "imap", "cmapx" };

void Dot::run ( const std::string & data, const std::string & outputType, const std::optional < std::string > & outputFile, bool runInBackground ) {
	int fd [ 2 ];
	if ( pipe ( fd ) != 0 )
		throw exception::CommonException ( "Dot: Failed to initialize communication pipe." );

	pid_t pid = fork ( );

	if ( pid < 0 ) {
		throw exception::CommonException ( "Dot: Failed to fork." );
	} else if ( pid == 0 ) { // child
		close ( STDIN_FILENO );
		close ( STDOUT_FILENO );
		dup2 ( fd [ 0 ], STDIN_FILENO );
		close ( fd [ 0 ] );
		close ( fd [ 1 ] );

		const char* cmd [ 6 ] = { "dot", "-T", outputType.c_str ( ), nullptr };
		if ( outputFile.has_value ( ) ) {
			cmd [ 3 ] = "-o";
			cmd [ 4 ] = outputFile->c_str ( );
			cmd [ 5 ] = nullptr;
		}

		if ( execvp ( "dot", const_cast < char* const* > ( cmd ) ) == -1 ) // I do not like the const_cast but this is a real issue when mixing C with C++
			throw exception::CommonException ( "Dot: Failed to spawn child process." );
	} else {
		close ( fd [ 0 ] );
		if ( write ( fd [ 1 ], data.c_str ( ), data.size ( ) + 1 ) != ( ssize_t ) data.size ( ) + 1 )
			throw exception::CommonException ( "Dot: Failed to write data to dot child process." );

		close ( fd [ 1 ] );

		if ( ! runInBackground ) {
			int status;
			waitpid ( pid, &status, 0 );
		}
	}
}

void Dot::dot ( const std::string & data, bool runsInBackground ) {
	run ( data, "x11", std::nullopt, runsInBackground );
}

void Dot::dot2 ( const std::string & data ) {
	dot ( data, true );
}

void Dot::dot ( const std::string & data, const std::string& outputType, const std::string & outputFile ) {
	std::string outputTypeLower;
	std::transform ( outputType.begin ( ), outputType.end ( ), outputTypeLower.begin ( ), ::tolower );

	if ( std::find ( allowedOutputTypes.begin( ), allowedOutputTypes.end ( ), outputTypeLower ) == allowedOutputTypes.end ( ) ) {
		throw exception::CommonException ( "Dot: Invalid output type." );
	}

	run ( data, outputTypeLower, outputFile, false );
}



auto DotTx11NonBlocking = registration::AbstractRegister < Dot, void, const std::string & > ( Dot::dot2, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, "dot" ).setDocumentation (
"Cli builtin command for DOT format visualization. Does not block CLI.\n\
\n\
@param dot a string containing dot data" );

auto DotTx11 = registration::AbstractRegister < Dot, void, const std::string &, bool > ( Dot::dot, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, "dot", "background" ).setDocumentation (
"Cli builtin command for DOT format visualization.\n\
\n\
@param dot a string containing dot data\n\
@param background a flag specifying whether to run in background (does not block CLI input)" );

auto DotFile = registration::AbstractRegister < Dot, void, const std::string &, const std::string &, const std::string & > ( Dot::dot, abstraction::AlgorithmCategories::AlgorithmCategory::DEFAULT, "data", "outputType", "outputFile" ).setDocumentation (
"Cli builtin command for DOT format visualization. Runs dot -T<outputType> -o <outputFile> and blocks the input until done.\n\
\n\
@param data a string containing dot data\n\
@param outputType the type of dot created image (dot, xdot, ps, pdf, svg, svgz, fig, png, gif, jpg, jpeg, json, imap, cmapx )\n\
@param outputFile the destination file name" );

} /* namespace cli::builtin */
