/*******************************************************************************
 * MacMain.cpp
 * POV-Ray_3.7_MacCommandLine
 * Created by yvo on 23/11/13.
 *
 * Adapted from unixconsole.cpp
 *
 * ---------------------------------------------------------------------------
 * Persistence of Vision Ray Tracer ('POV-Ray') version 3.7.
 * Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd.
 *
 * POV-Ray is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * POV-Ray is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * ---------------------------------------------------------------------------
 * POV-Ray is based on the popular DKB raytracer version 2.12.
 * DKBTrace was originally written by David K. Buck.
 * DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
 * ---------------------------------------------------------------------------
 *******************************************************************************/


#include "MacMain.h"

vfeMacSession   *gVfeSession=NULL;
#import <Foundation/Foundation.h>
#include <unistd.h>

bool gCancelRender = false;
void SignalHandler (void)
{
	sigset_t sigset;
	int      signum;

	while(true)
	{
		sigfillset(&sigset);
		sigwait(&sigset, &signum);  // wait till a signal is caught
		boost::mutex::scoped_lock lock(gSignalMutex);
		gSignalNumber = signum;
	}
}


//---------------------------------------------------------------------
// main
//---------------------------------------------------------------------
int main (int argc, char **argv)
{
	int result=0;
	vfeStatusFlags    currentStatusFlags;

 	vfeRenderOptions  opts;
	ReturnValue       retval = RETURN_OK;
	bool              running_benchmark = false;
	string            bench_ini_name;
	string            bench_pov_name;
	sigset_t          sigset;
	boost::thread    *sigthread;


	NSAutoreleasePool *pool=[[NSAutoreleasePool alloc]init];

	// block some signals for this thread as well as those created afterwards
	sigemptyset(&sigset);

	#ifdef SIGQUIT
		sigaddset(&sigset, SIGQUIT);
	#endif
	#ifdef SIGTERM
		sigaddset(&sigset, SIGTERM);
	#endif
	#ifdef SIGINT
		sigaddset(&sigset, SIGINT);
	#endif
	#ifdef SIGPIPE
		sigaddset(&sigset, SIGPIPE);
	#endif
	#ifdef SIGCHLD
		sigaddset(&sigset, SIGCHLD);
	#endif

	pthread_sigmask(SIG_BLOCK, &sigset, NULL);

	// create the signal handling thread
	sigthread = new boost::thread(SignalHandler);

/*	for (int x=0; x<argc; x++)
				fprintf (stderr, "Option:%s\n",argv[x]);*/

	// open mac session
	gVfeSession = new vfeMacSession();
 	if (gVfeSession->Initialize(NULL, NULL) != vfeNoError)
		ErrorExit(gVfeSession);
  gVfeSession->OptimizeForConsoleOutput(true);
  gVfeSession->SetDisplayCreator(MacDisplayCreator);
  gVfeSession->Clear();
  gVfeSession->ClearOptions();
	
	// default number of work threads: number of CPUs or 4
	int nthreads = 1;
	#ifdef _SC_NPROCESSORS_ONLN  // online processors
		nthreads = sysconf(_SC_NPROCESSORS_ONLN);
	#endif
	#ifdef _SC_NPROCESSORS_CONF  // configured processors
		if (nthreads < 2)
			nthreads = sysconf(_SC_NPROCESSORS_CONF);
	#endif
	if (nthreads < 2)
		nthreads = 4;
	opts.SetThreadCount(nthreads);

	if (isOptionSet("help", argv) || isOptionSet("-help",argv))
	{
		gVfeSession->Shutdown() ;
		PrintStatus (gVfeSession) ;
		// TODO: general usage display (not yet in core code)
		PrintOptions();
		delete sigthread;
		delete gVfeSession;
		return RETURN_OK;
	}

	//print version?
	else if (isOptionSet("version", argv) || isOptionSet("-version",argv))
	{
		gVfeSession->Shutdown() ;
		PrintVersion();
		delete sigthread;
		delete gVfeSession;
		return RETURN_OK;
	}
	//option set to run a benchmarkt?
	else if (isOptionSet("benchmark", argv) || isOptionSet("-benchmark",argv))
	{
		retval = PrepareBenchmark(gVfeSession, opts, bench_ini_name, bench_pov_name, argc, argv);
		if (retval == RETURN_OK)
			running_benchmark = true;
		else
		{
			gVfeSession->Shutdown();
			delete sigthread;
			delete gVfeSession;
			return retval;
		}
	}

	// process INI settings
	if (running_benchmark)
	{
		// read only the provided INI file
		// extra lib paths and threadcount are set in
		// PrepareBenchmark if set on the command line
		opts.AddINI(bench_ini_name.c_str());
		opts.SetSourceFile(bench_pov_name.c_str());
	}
	else if ( argc == 1 ) // no options, search for /application support/povray /povray.ini
	{
		fprintf(stderr, "\nNo arguments on the command line\n");

		NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
		NSString *applicationSupportDirectory = [paths firstObject];
		NSString *inifileAtApplicationSupport=[applicationSupportDirectory stringByAppendingString:@"/Povray 3.7 Unofficial/povray.ini"];

		paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
		applicationSupportDirectory = [paths firstObject];
		NSString *inifileAtDocumentes=[applicationSupportDirectory stringByAppendingString:@"/Povray 3.7 Unofficial/povray.ini"];

		NSString *inifileAtHome=[NSHomeDirectory() stringByAppendingString:@"/Povray 3.7 Unofficial/povray.ini"];

		if ([[NSFileManager defaultManager]fileExistsAtPath:inifileAtApplicationSupport])
		{
			fprintf(stdout,"using default povray.ini file at ~/Library/Povray 3.7 Unofficial/\n");
			opts.AddINI([inifileAtApplicationSupport cStringUsingEncoding:NSUTF8StringEncoding]);
		}
		else if ([[NSFileManager defaultManager]fileExistsAtPath:inifileAtDocumentes])
		{
			fprintf(stdout,"using default povray.ini file at ~/Documents/Povray 3.7 Unofficial/\n");
			opts.AddINI([inifileAtDocumentes cStringUsingEncoding:NSUTF8StringEncoding]);
		}
		else if ([[NSFileManager defaultManager]fileExistsAtPath:inifileAtHome])
		{
			fprintf(stdout,"using default povray.ini file at ~/Povray 3.7 Unofficial/\n");
			opts.AddINI([inifileAtHome cStringUsingEncoding:NSUTF8StringEncoding]);
		}

	}
	else
	{
		// not doing benchmark
		// set all options from the command line
		gVfeSession->SetDisplayCreator(MacDisplayCreator);
		for (int i = 1; i < argc; i++)
			opts.AddCommand(argv[i]);
	}


	try 
	{ 
		result = gVfeSession->SetOptions(opts);
			
    if (result == vfeNoInputFile)
      throw POV_EXCEPTION_STRING("No source file specified, either directly or via an INI file.");
    else if (result != vfeNoError)      
      throw POV_EXCEPTION_STRING (gVfeSession->GetErrorString());

		gVfeSession->SetEventMask(~stNone);  // immediatly notify this event
		
		if (gVfeSession->StartRender() != vfeNoError)
			ErrorExit(gVfeSession);
		
		while (((currentStatusFlags = gVfeSession->GetStatus(true, -1)) & stRenderShutdown) == 0)
		{
			ProcessSignal();
			if (gCancelRender)
			{
				CancelRender(gVfeSession);
				break;
			}
			if (currentStatusFlags & stAnimationStatus)
				fprintf(stderr, "\nRendering frame %d of %d\n", gVfeSession->GetCurrentFrame(), gVfeSession->GetTotalFrames());
			if (currentStatusFlags & stAnyMessage)
				PrintStatus (gVfeSession);
			if (currentStatusFlags & stBackendStateChanged)
			PrintStatusChanged (gVfeSession);
			}
	}
	
  catch (std::exception& e)
  {
				retval=RETURN_ERROR;

		fprintf (stderr, "\nFailed to start render: %s\n", e.what());
		gVfeSession->CancelRender();
		// make sure there aren't any outstanding messages
		while ((currentStatusFlags = gVfeSession->GetStatus(true, 0)))
		{
	 //   ProcessSession(flags);
		}
	}
	
		if (running_benchmark)
		CleanupBenchmark(gVfeSession, bench_ini_name, bench_pov_name);

	if (gVfeSession->Succeeded() == false)
		retval = gCancelRender ? RETURN_USER_ABORT : RETURN_ERROR;
	gVfeSession->Shutdown();
	PrintStatus (gVfeSession);
	delete sigthread;
	delete gVfeSession;
[pool release];
	return retval;
}


//*******************************************************************************
//   ErrorExit()
//*******************************************************************************
void ErrorExit(vfeMacSession *session)
{
	fprintf(stderr, "%s\n", session->GetErrorString());
	session->Shutdown();
	delete session;
	exit(RETURN_ERROR);
}

//*******************************************************************************
//   PrintStatus()
//*******************************************************************************
void PrintStatus (vfeMacSession *session)
{
	string str;
	vfeSession::MessageType type;
	static vfeSession::MessageType lastType = vfeSession::mUnclassified;

	while (session->GetNextCombinedMessage (type, str))
	{
		if (type != vfeSession::mGenericStatus)
		{
			if (lastType == vfeSession::mGenericStatus)
				fprintf (stderr, "\n") ;
			fprintf (stderr, "%s\n", str.c_str());
		}
		else
			fprintf (stderr, "%s\r", str.c_str());
		lastType = type;
	}
}

//*******************************************************************************
//   PrintStatusChanged()
//*******************************************************************************
void PrintStatusChanged (vfeMacSession *session, State force )
{
	if (force == kUnknown)
		force = session->GetBackendState();
	switch (force)
	{
		case kParsing:
			fprintf (stderr, "==== [Parsing...] ==========================================================\n");
			break;
		case kRendering:
			fprintf (stderr, "==== [Rendering...] ========================================================\n");
			break;
		case kPausedRendering:
			fprintf (stderr, "==== [Paused] ==============================================================\n");
			break;
		case kDone:
			fprintf (stderr, "==== [Done] ==============================================================\n");
			break;
		default:
			break;
	}
}

//*******************************************************************************
//   isOptionSet()
//*******************************************************************************
bool isOptionSet(const char *optionToFind, char **argv)
{
	string optionStringLower=string(optionToFind);
	boost::to_lower(optionStringLower);
	
	while (*++argv)
	{
		string s = string(*argv);
		boost::to_lower(s);
		if (boost::equals(s,optionStringLower))
			return true;
	}
	return false;
}

//*******************************************************************************
//   PrepareBenchmark()
//*******************************************************************************
ReturnValue PrepareBenchmark(vfeMacSession *session, vfeRenderOptions& opts, string& ini, string& pov, int argc, char **argv)
{
	// parse command-line options
	string optionString;
	optionString.clear();
	while (*++argv)
	{
		string s = string(*argv);
		boost::to_lower(s);
		// set number of threads to run the benchmark
		if (boost::starts_with(s, "+wt") || boost::starts_with(s, "-wt"))
		{
			s.erase(0, 3);
			int n = atoi(s.c_str());
			if (n)
				opts.SetThreadCount(n);
			else
				fprintf(stderr, "%s: ignoring malformed '%s' command-line option\n", PACKAGE, *argv);
		}
		// add library path
		else if (boost::starts_with(s, "+l") || boost::starts_with(s, "-l"))
		{
			s.erase(0, 2);
			opts.AddLibraryPath(s);
		}
	}

	int benchversion = Get_Benchmark_Version();
	fprintf(stderr, "\
%s %s%s\n\n\
Entering the standard POV-Ray %s benchmark version %x.%02x.\n\n\
There will be neither display nor file output, and\n\
any additional command-line option except setting the number of render\n\
threads (+wtN for N threads) and library paths (+Lpath) will be ignored.\n\
To get an accurate benchmark result you might consider running POV-Ray\n\
with the Unix 'time' command (e.g. 'time povray -benchmark').\n\n\
The benchmark will run using %d render thread(s).\n\
Press <Enter> to continue or <Ctrl-C> to abort.\n\
		",
		PACKAGE_NAME, POV_RAY_VERSION, COMPILER_VER,
		VERSION_BASE, benchversion / 256, benchversion % 256,
		opts.GetThreadCount()
	);

	// wait for user input from stdin (including abort signals)
	while (true)
	{
		ProcessSignal();
		if (gCancelRender)
		{
			fprintf(stderr, "Render cancelled by user\n");
			return RETURN_USER_ABORT;
		}

		fd_set readset;
		struct timeval tv = {0,0};  // no timeout
		FD_ZERO(&readset);
		FD_SET(STDIN_FILENO, &readset);
		if (select(STDIN_FILENO+1, &readset, NULL, NULL, &tv) < 0)
			break;
		if (FD_ISSET(STDIN_FILENO, &readset))  // user input is available
		{
			char s[3];
			read(STDIN_FILENO, s, 1);         // read till <ENTER> is hit
			tcflush(STDIN_FILENO, TCIFLUSH);  // discard unread data
			break;
		}
		Delay(20);
	}

	string basename = UCS2toASCIIString(session->CreateTemporaryFile());
	ini = basename + ".ini";
	pov = basename + ".pov";
	if (pov::Write_Benchmark_File(pov.c_str(), ini.c_str()))
	{
		fprintf(stderr, "%s: creating %s\n", PACKAGE, ini.c_str());
		fprintf(stderr, "%s: creating %s\n", PACKAGE, pov.c_str());
		fprintf(stderr, "Running standard POV-Ray benchmark version %x.%02x\n", benchversion / 256, benchversion % 256);
	}
	else
	{
		fprintf(stderr, "%s: failed to write temporary files for benchmark\n", PACKAGE);
		return RETURN_ERROR;
	}

	return RETURN_OK;
}

//*******************************************************************************
//   CleanupBenchmark()
//*******************************************************************************
void CleanupBenchmark(vfeMacSession *session, string& ini, string& pov)
{
	fprintf(stderr, "%s: removing %s\n", PACKAGE, ini.c_str());
	session->DeleteTemporaryFile(ASCIItoUCS2String(ini.c_str()));
	fprintf(stderr, "%s: removing %s\n", PACKAGE, pov.c_str());
	session->DeleteTemporaryFile(ASCIItoUCS2String(pov.c_str()));
}

//*******************************************************************************
//   ProcessSignal()
//*******************************************************************************
void ProcessSignal (void)
{
	boost::mutex::scoped_lock lock(gSignalMutex);

	switch (gSignalNumber)
	{
		case  0:
			break;
#ifdef SIGQUIT
		case SIGQUIT:
			fprintf(stderr, "\n%s: received signal SIGQUIT: Quit; requested render cancel\n", PACKAGE);
			gCancelRender = true;
			break;
#endif
#ifdef SIGTERM
		case SIGTERM:
			fprintf(stderr, "\n%s: received signal SIGTERM: Termination; requested render cancel\n", PACKAGE);
			gCancelRender = true;
			break;
#endif
#ifdef SIGINT
		case SIGINT:
			fprintf(stderr, "\n%s: received signal SIGINT: Interrupt; requested render cancel\n", PACKAGE);
			gCancelRender = true;
			break;
#endif
#ifdef SIGPIPE
		case SIGPIPE:
			fprintf(stderr, "\n%s: received signal SIGPIPE: Broken pipe; requested render cancel\n", PACKAGE);
			gCancelRender = true;
			break;
#endif
		case SIGCHLD:
			// for now, ignore this (side-effect of the shell-out code).
			// once properly implemented, the shell-out code would want to know this has happened, though.
			break;

		default:
			// fprintf(stderr, "\n%s: received signal %d\n", PACKAGE, gSignalNumber);
			break;
	}
	gSignalNumber = 0;
}

//*******************************************************************************
//   PrintOptions()
//*******************************************************************************
void PrintOptions(void)
{
	fprintf(stderr,"Platform specific command line options:\n");
	fprintf(stderr,"\tgeneral options:\n");
	fprintf(stderr,"\t\t-version|version\t\tdisplay program version\n");
	fprintf(stderr,"\t\t-help|help\t\t\tdisplay this (usage information)\n");
	fprintf(stderr,"\t\t-benchmark|benchmark\trun the standard POV-Ray benchmark\n");
}

//*******************************************************************************
//   CancelRender()
//*******************************************************************************
void PrintVersion(void)
{
	fprintf(stderr,
		"%s %s\n\n"
		"%s\n%s\n%s\n"
		"%s\n%s\n%s\n\n",
		PACKAGE_NAME, POV_RAY_VERSION,
		DISTRIBUTION_MESSAGE_1, DISTRIBUTION_MESSAGE_2, DISTRIBUTION_MESSAGE_3,
		POV_RAY_COPYRIGHT, DISCLAIMER_MESSAGE_1, DISCLAIMER_MESSAGE_2
	);
	fprintf(stderr,
		"Built-in features:\n"
		"  I/O restrictions:          %s\n"
		"  X Window display:          %s\n"
		"  Supported image formats:   %s\n"
		"  Unsupported image formats: %s\n\n",
		BUILTIN_IO_RESTRICTIONS, BUILTIN_XWIN_DISPLAY, BUILTIN_IMG_FORMATS, MISSING_IMG_FORMATS
	);
	fprintf(stderr,
		"Compilation settings:\n"
		"  Build architecture:  %s\n"
		"  Built/Optimized for: %s\n"
		"  Compiler version:    %s\n",
		BUILD_ARCH, BUILT_FOR,  COMPILER_VER);
}

//*******************************************************************************
//   CancelRender()
//*******************************************************************************
void CancelRender(vfeMacSession *session)
{
	session->CancelRender();  // request the backend to cancel
	PrintStatus (session);
	while (session->GetBackendState() != kReady)  // wait for the render to effectively shut down
		Delay(10);
	PrintStatus (session);
}



