/*
    This file is part of Mitsuba, a physically based rendering system.

    Copyright (c) 2007-2014 by Wenzel Jakob and others.

    Mitsuba is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License Version 3
    as published by the Free Software Foundation.

    Mitsuba 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <mitsuba/core/platform.h>
#include <QtGui/QtGui>
#include <QtOpenGL/QGLFormat>
#include <mitsuba/core/shvector.h>
#include <mitsuba/core/sched.h>
#include <mitsuba/core/plugin.h>
#include <mitsuba/core/fresolver.h>
#include <mitsuba/core/fstream.h>
#include <mitsuba/core/appender.h>
#include <mitsuba/core/statistics.h>
#include <mitsuba/render/scenehandler.h>

#if defined(__OSX__)
#include <ApplicationServices/ApplicationServices.h>
#endif
#include "mainwindow.h"

#if defined(__LINUX__)
#include <X11/Xlib.h>
#endif

#if !defined(__WINDOWS__)
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>
#else
#include <winsock2.h>
#endif

#if defined(__WINDOWS__)
// Always use the High Performance GPU on machines using NVIDIA Optimus
// http://stackoverflow.com/questions/10535950/forcing-nvidia-gpu-programmatically-in-optimus-laptops [January 2014]
extern "C" {
	_declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
}
#endif

using namespace mitsuba;
MainWindow *mainWindow = NULL;


/// ================================================================
///  Handle application crashes when compiled with MTS_HAS_BREAKPAD
/// ================================================================
#if defined(MTS_HAS_BREAKPAD)
#if defined(__OSX__)

extern void *__mts_init_breakpad_osx();
extern void  __mts_destroy_breakpad_osx(void *);

#elif defined(__WINDOWS__)

#include <breakpad/client/windows/sender/crash_report_sender.h>
#include <breakpad/client/windows/handler/exception_handler.h>

static bool minidumpCallbackWindows(const wchar_t *dump_path,
		const wchar_t *minidump_id, void *context,
		EXCEPTION_POINTERS *exinfo, MDRawAssertionInfo *assertion,
		bool succeeded) {
	if (!dump_path || !minidump_id)
		return false;

	std::wstring filename = std::wstring(dump_path) + std::wstring(L"\\")
		+ std::wstring(minidump_id) + std::wstring(L".dmp");

	google_breakpad::CrashReportSender sender(L"");
	std::map<std::wstring, std::wstring> parameters;

	#if defined(WIN64)
		parameters[L"prod"] = L"Mitsuba/Win64";
	#else
		parameters[L"prod"] = L"Mitsuba/Win32";
	#endif

	std::string version = MTS_VERSION;
	std::wstring wVersion;
	wVersion.assign(version.begin(), version.end());
	parameters[L"ver"] = wVersion;

	std::wstring resultString;

	if (MessageBox(NULL, TEXT("Mitsuba crashed due to an internal error. If you agree below, a brief "
			"report describing the failure will be submitted to the developers. If you would like to "
			"accelerate the debugging process further, please also create a ticket with information on "
			"the steps that led to the problem on https://www.mitsuba-renderer.org/tracker -- thank you!"),
			TEXT("Error"), MB_OKCANCEL | MB_ICONERROR) != IDOK)
		return false;

	google_breakpad::ReportResult result =
		sender.SendCrashReport(L"http://www.mitsuba-renderer.org/bugreport.php",
		parameters, filename, &resultString);

	if (result != google_breakpad::RESULT_SUCCEEDED) {
		MessageBox(NULL, TEXT("The error report could not be submitted due to a lack of "
			"internet connectivity!"), TEXT("Error"), MB_OK | MB_ICONERROR);
		return false;
	}

	return succeeded;
}

#endif
#endif

/// ================================================================

class MitsubaApplication : public QApplication {
public:
	MitsubaApplication(int &argc, char **argv) : QApplication(argc, argv) {
		setOrganizationName("mitsuba-renderer.org");
		setOrganizationDomain("mitsuba-renderer.org");
		setApplicationName("mtsgui");
		setApplicationVersion(MTS_VERSION);
	}

	bool event(QEvent *event) {
		switch (event->type()) {
#if defined(__OSX__)
			case QEvent::Quit:
				quit();
				return true;
#endif
			case QEvent::FileOpen:
				if (mainWindow != NULL)
					mainWindow->loadFile(static_cast<QFileOpenEvent *>(event)->file());
				return true;
			default:
				return QApplication::event(event);
		}
	}

	bool notify(QObject *rec, QEvent *e) {
		try {
			return QApplication::notify(rec, e);
		} catch (const std::exception &e) {
			SLog(EWarn, "Caught exception: %s", e.what());
			QMessageBox::critical(NULL, tr("Critical exception"),
				e.what(), QMessageBox::Ok);
			return false;
		}
	}
};

#if defined(__OSX__)
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif

int main(int argc, char *argv[]) {
	int retval = -1;

	/* Initialize the core framework */
	Class::staticInitialization();
	Object::staticInitialization();
	PluginManager::staticInitialization();
	Statistics::staticInitialization();
	Thread::staticInitialization();
	Logger::staticInitialization();
	FileStream::staticInitialization();
	Thread::initializeOpenMP(getCoreCount());
	Spectrum::staticInitialization();
	Bitmap::staticInitialization();
	Scheduler::staticInitialization();
	SHVector::staticInitialization();
	SceneHandler::staticInitialization();

#if defined(__LINUX__)
	XInitThreads();
#endif

#if defined(__OSX__)
	MTS_AUTORELEASE_BEGIN()
	/* Required for the mouse relocation in GLWidget */
	CGEventSourceRef evsrc =
		CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);
	CGEventSourceSetLocalEventsSuppressionInterval(evsrc, 0.0);
	CFRelease(evsrc);
	MTS_AUTORELEASE_END()
#endif

#if defined(__WINDOWS__)
	/* Initialize WINSOCK2 */
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2,2), &wsaData))
		SLog(EError, "Could not initialize WinSock2!");
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
		SLog(EError, "Could not find the required version of winsock.dll!");
#endif

#if !defined(__WINDOWS__)
	/* Avoid zombies processes when running the server */
	if (signal(SIGCHLD, SIG_IGN) == SIG_ERR)
		SLog(EWarn, "Error in signal(): %s!", strerror(errno));
#endif

	qRegisterMetaType<ELogLevel>("ELogLevel");
	qRegisterMetaType<fs::path>("fs::path");

	MitsubaApplication app(argc, argv);
	try {
		QFile stylesheet(":/resources/stylesheet.css");

		if (!stylesheet.open(QFile::ReadOnly)) {
			QMessageBox::critical(NULL, "Internal error", "Could not open stylesheet!");
			exit(-1);
		}
		app.setStyleSheet(QTextStream(&stylesheet).readAll().toAscii());

#if defined(__OSX__)
		app.setAttribute(Qt::AA_DontShowIconsInMenus);
#endif
		/* Disable the default appenders */
		ref<Logger> logger = Thread::getThread()->getLogger();
		for (size_t i=0; i<logger->getAppenderCount(); ++i) {
			Appender *appender = logger->getAppender(i);
			if (appender->getClass()->derivesFrom(MTS_CLASS(StreamAppender)))
				logger->removeAppender(appender);
		}

#if defined(__OSX__)
		/* Create a log file inside the application bundle */
		MTS_AUTORELEASE_BEGIN()
		logger->addAppender(new StreamAppender(formatString("%s/mitsuba.%s.log",
			__mts_bundlepath().c_str(), getHostName().c_str())));
		MTS_AUTORELEASE_END()

		/* Set application defaults (disable OSX synchronization feature) */
		__mts_set_appdefaults();
#else
		/* Create a log file inside the current working directory */
		logger->addAppender(new StreamAppender(formatString("mitsuba.%s.log", getHostName().c_str())));
#endif

#if !defined(__WINDOWS__)
		/* Correct number parsing on some locales (e.g. ru_RU) */
		setlocale(LC_NUMERIC, "C");
#endif

#if defined(MTS_HAS_BREAKPAD)
	#if defined(__OSX__)
		void *breakpad = __mts_init_breakpad_osx();
	#elif defined(__WINDOWS__)
		_CrtSetReportMode(_CRT_ASSERT, 0);
		std::wstring dump_path;
		dump_path.resize(1024);
		GetTempPathW(1024, &dump_path[0]);

		google_breakpad::ExceptionHandler *breakpad =
			new google_breakpad::ExceptionHandler(
				dump_path, NULL, minidumpCallbackWindows, NULL,
				google_breakpad::ExceptionHandler::HANDLER_ALL,
				MiniDumpNormal, NULL, NULL);
	#endif
#endif

#if !defined(MTS_GUI_SOFTWARE_FALLBACK)
		/* Be a bit more picky about the rendering target */
		QGLFormat fmt;
		fmt.setDepth(true);
		fmt.setStencil(false);
		fmt.setAlpha(true);
		fmt.setStereo(false);
		fmt.setDoubleBuffer(true);
		QGLFormat::setDefaultFormat(fmt);
#endif

		mainWindow = new MainWindow();
		if (mainWindow->initWorkersProcessArgv())
			retval = app.exec();

#if defined(MTS_HAS_BREAKPAD)
	#if defined(__OSX__)
		__mts_destroy_breakpad_osx(breakpad);
	#elif defined(__WINDOWS__)
		delete breakpad;
	#endif
#endif
		delete mainWindow;
	} catch (const std::exception &e) {
		SLog(EWarn, "Critical exception during startup: %s", e.what());
		QMessageBox::critical(NULL, QString("Critical exception"),
			e.what(), QMessageBox::Ok);
	}
	Statistics::getInstance()->printStats();


#if defined(__WINDOWS__)
	/* Shut down WINSOCK2 */
	WSACleanup();
#endif

	/* Shutdown the core framework */
	SceneHandler::staticShutdown();
	SHVector::staticShutdown();
	Scheduler::staticShutdown();
	Bitmap::staticShutdown();
	Spectrum::staticShutdown();
	FileStream::staticShutdown();
	Logger::staticShutdown();
	Thread::staticShutdown();
	Statistics::staticShutdown();
	PluginManager::staticShutdown();
	Object::staticShutdown();
	Class::staticShutdown();

	return retval;
}
