/**
 * @file llappviewerlinux.cpp
 * @brief The LLAppViewerLinux class definitions
 *
 * $LicenseInfo:firstyear=2007&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */ 

#include "llviewerprecompiledheaders.h"

#include "llappviewerlinux.h"

#include "llcommandlineparser.h"

#include "lldiriterator.h"
#include "llurldispatcher.h"		// SLURL from other app instance
#include "llviewernetwork.h"
#include "llviewercontrol.h"
#include "llwindowsdl.h"
#include "llmd5.h"
#include "llfindlocale.h"

#include <exception>

#include <gio/gio.h>
#include <resolv.h>

#if (__GLIBC__*1000 + __GLIBC_MINOR__) >= 2034
#pragma message "Compiling with libresolv stubs"
extern "C"
{
  int __res_nquery(res_state statep,
                   const char *dname, int qclass, int type,
                   unsigned char *answer, int anslen)
  {
    return res_nquery( statep, dname, qclass, type, answer, anslen );
  }

  int __dn_expand(const unsigned char *msg,
                  const unsigned char *eomorig,
                  const unsigned char *comp_dn, char *exp_dn,
                  int length)
  {
    return dn_expand( msg,eomorig,comp_dn,exp_dn,length);
  }
}
#endif

#if LL_SEND_CRASH_REPORTS
#include "breakpad/client/linux/handler/exception_handler.h"
#include "breakpad/common/linux/http_upload.h"
#include "lldir.h"
#include "../llcrashlogger/llcrashlogger.h"
// <FS:LO> Fix linux compile
#if LL_USESYSTEMLIBS
#include "jsoncpp/reader.h" // JSON
#else
#include "json/reader.h" // JSON
#endif
// </FS:LO>
#endif

#include "fsversionvalues.h"

#define VIEWERAPI_SERVICE "com.secondlife.ViewerAppAPIService"
#define VIEWERAPI_PATH "/com/secondlife/ViewerAppAPI"
#define VIEWERAPI_INTERFACE "com.secondlife.ViewerAppAPI"

static const char * DBUS_SERVER = "<node name=\"/com/secondlife/ViewerAppAPI\">\n"
                                  "  <interface name=\"com.secondlife.ViewerAppAPI\">\n"
                                  "    <annotation name=\"org.freedesktop.DBus.GLib.CSymbol\" value=\"viewer_app_api\"/>\n"
                                  "    <method name=\"GoSLURL\">\n"
                                  "      <annotation name=\"org.freedesktop.DBus.GLib.CSymbol\" value=\"dispatchSLURL\"/>\n"
                                  "      <arg type=\"s\" name=\"slurl\" direction=\"in\" />\n"
                                  "    </method>\n"
                                  "  </interface>\n"
                                  "</node>";

typedef struct
{
    GObject parent;
} ViewerAppAPI;

namespace
{
	int gArgC = 0;
	char **gArgV = NULL;
	void (*gOldTerminateHandler)() = NULL;
}


static void exceptionTerminateHandler()
{
	// reinstall default terminate() handler in case we re-terminate.
	if (gOldTerminateHandler) std::set_terminate(gOldTerminateHandler);
	// treat this like a regular viewer crash, with nice stacktrace etc.
    long *null_ptr;
    null_ptr = 0;
    *null_ptr = 0xDEADBEEF; //Force an exception that will trigger breakpad.
	// we've probably been killed-off before now, but...
	gOldTerminateHandler(); // call old terminate() handler
}

int main( int argc, char **argv ) 
{
	gArgC = argc;
	gArgV = argv;

	LLAppViewer* viewer_app_ptr = new LLAppViewerLinux();

	// install unexpected exception handler
	gOldTerminateHandler = std::set_terminate(exceptionTerminateHandler);
	// install crash handlers
	viewer_app_ptr->setErrorHandler(LLAppViewer::handleViewerCrash);

	unsetenv( "LD_PRELOAD" ); // <FS:ND/> Get rid of any preloading, we do not want this to happen during startup of plugins.
	
	bool ok = viewer_app_ptr->init();
	if(!ok)
	{
		LL_WARNS() << "Application init failed." << LL_ENDL;
		return -1;
	}

		// Run the application main loop
	while (! viewer_app_ptr->frame()) 
	{}

	if (!LLApp::isError())
	{
		//
		// We don't want to do cleanup here if the error handler got called -
		// the assumption is that the error handler is responsible for doing
		// app cleanup if there was a problem.
		//
		viewer_app_ptr->cleanup();
	}
	delete viewer_app_ptr;
	viewer_app_ptr = NULL;
	return 0;
}

LLAppViewerLinux::LLAppViewerLinux()
{
}

LLAppViewerLinux::~LLAppViewerLinux()
{
}

#if LL_SEND_CRASH_REPORTS
std::string gCrashLogger;
std::string gVersion;
std::string gBugsplatDB;
std::string gCrashBehavior;

static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded)
{
	if( fork() == 0 )
		execl( gCrashLogger.c_str(), gCrashLogger.c_str(), descriptor.path(), gVersion.c_str(), gBugsplatDB.c_str(),  gCrashBehavior.c_str(), nullptr );
    return succeeded;
}

void setupBreadpad()
{
    std::string build_data_fname(gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, "build_data.json"));
    gCrashLogger =  gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, "linux-crash-logger.bin");

    llifstream inf(build_data_fname.c_str());
    if(!inf.is_open())
    {
        LL_WARNS("BUGSPLAT") << "Can't initialize BugSplat, can't read '" << build_data_fname << "'" << LL_ENDL;
        return;
    }

    Json::Reader reader;
    Json::Value build_data;
    if(!reader.parse(inf, build_data, false))
    {
        LL_WARNS("BUGSPLAT") << "Can't initialize BugSplat, can't parse '" << build_data_fname << "': "
                             << reader.getFormatedErrorMessages() << LL_ENDL;
        return;
    }

    Json::Value BugSplat_DB = build_data["BugSplat DB"];
    if(!BugSplat_DB)
    {
        LL_WARNS("BUGSPLAT") << "Can't initialize BugSplat, no 'BugSplat DB' entry in '" << build_data_fname
                             << "'" << LL_ENDL;
        return;
    }
    gVersion = STRINGIZE(
            LL_VIEWER_VERSION_MAJOR << '.' << LL_VIEWER_VERSION_MINOR << '.' << LL_VIEWER_VERSION_PATCH
                                    << '.' << LL_VIEWER_VERSION_BUILD);
    gBugsplatDB = BugSplat_DB.asString();

	LL_INFOS("BUGSPLAT") << "Initializing with crash logger: " << gCrashLogger << " database: " << gBugsplatDB << " version: " << gVersion << LL_ENDL;
	
    google_breakpad::MinidumpDescriptor *descriptor = new google_breakpad::MinidumpDescriptor(gDirUtilp->getExpandedFilename(LL_PATH_DUMP, ""));
    google_breakpad::ExceptionHandler *eh = new google_breakpad::ExceptionHandler(*descriptor, NULL, dumpCallback, NULL, true, -1);
}
#endif

bool LLAppViewerLinux::init()
{
	// g_thread_init() must be called before *any* use of glib, *and*
	// before any mutexes are held, *and* some of our third-party
	// libraries likes to use glib functions; in short, do this here
	// really early in app startup!

	bool success = LLAppViewer::init();

#if LL_SEND_CRASH_REPORTS
    S32 nCrashSubmitBehavior = gCrashSettings.getS32("CrashSubmitBehavior");

	// For the first version we just consider always send and create a nice dialog for CRASH_BEHAVIOR_ASK later.
    if (success && nCrashSubmitBehavior != CRASH_BEHAVIOR_NEVER_SEND )
	{
		if( nCrashSubmitBehavior == CRASH_BEHAVIOR_ASK )
			gCrashBehavior = "ask";
		else
			gCrashBehavior = "send";
        setupBreadpad();
	}
#endif

	return success;
}

bool LLAppViewerLinux::restoreErrorTrap()
{
	// *NOTE:Mani there is a case for implementing this on the mac.
	// Linux doesn't need it to my knowledge.
	return true;
}

/////////////////////////////////////////
#if LL_GLIB

typedef struct
{
        GObjectClass parent_class;
} ViewerAppAPIClass;

///

static void viewerappapi_init(ViewerAppAPI *server);
static void viewerappapi_class_init(ViewerAppAPIClass *klass);

G_DEFINE_TYPE(ViewerAppAPI, viewerappapi, G_TYPE_OBJECT);

void viewerappapi_class_init(ViewerAppAPIClass *klass)
{
}

static void dispatchSLURL(gchar const *slurl)
{
    LL_INFOS() << "Was asked to go to slurl: " << slurl << LL_ENDL;

    std::string url = slurl;
    LLMediaCtrl* web = NULL;
    const bool trusted_browser = false;
    LLURLDispatcher::dispatch(url, "", web, trusted_browser);
}

static void DoMethodeCall (GDBusConnection       *connection,
                                const gchar           *sender,
                                const gchar           *object_path,
                                const gchar           *interface_name,
                                const gchar           *method_name,
                                GVariant              *parameters,
                                GDBusMethodInvocation *invocation,
                                gpointer               user_data)
{
    LL_INFOS() << "DBUS message " << method_name << "  from: " << sender << " interface: " << interface_name << LL_ENDL;
    const gchar *slurl;

    g_variant_get (parameters, "(&s)", &slurl);
    dispatchSLURL(slurl);
}

GDBusNodeInfo *gBusNodeInfo = nullptr;
static const GDBusInterfaceVTable interface_vtable =
        {
                DoMethodeCall
        };
static void busAcquired(GDBusConnection *connection, const gchar *name, gpointer user_data)
{
    auto id = g_dbus_connection_register_object(connection,
                                                VIEWERAPI_PATH,
                                                gBusNodeInfo->interfaces[0],
                                                &interface_vtable,
                                                NULL,  /* user_data */
                                                             NULL,  /* user_data_free_func */
                                                             NULL); /* GError** */
    g_assert (id > 0);
}

static void nameAcquired(GDBusConnection *connection, const gchar *name, gpointer user_data)
{
}

static void nameLost(GDBusConnection *connection, const gchar *name, gpointer user_data)
{

}
void viewerappapi_init(ViewerAppAPI *server)
{
    gBusNodeInfo = g_dbus_node_info_new_for_xml (DBUS_SERVER, NULL);
    g_assert (gBusNodeInfo != NULL);

    g_bus_own_name(G_BUS_TYPE_SESSION,
                   VIEWERAPI_SERVICE,
                   G_BUS_NAME_OWNER_FLAGS_NONE,
                   busAcquired,
                   nameAcquired,
                   nameLost,
                   NULL,
                   NULL);

}

///

//virtual
bool LLAppViewerLinux::initSLURLHandler()
{
	//ViewerAppAPI *api_server = (ViewerAppAPI*)
	g_object_new(viewerappapi_get_type(), NULL);

	return true;
}

//virtual
bool LLAppViewerLinux::sendURLToOtherInstance(const std::string& url)
{
	auto *pBus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, nullptr);

    if( !pBus )
    {
        LL_WARNS() << "Getting dbus failed." << LL_ENDL;
        return false;
    }

    auto pProxy = g_dbus_proxy_new_sync(pBus, G_DBUS_PROXY_FLAGS_NONE, nullptr,
                                        VIEWERAPI_SERVICE, VIEWERAPI_PATH,
                                        VIEWERAPI_INTERFACE, nullptr, nullptr);

    if( !pProxy )
    {
        LL_WARNS() << "Cannot create new dbus proxy." << LL_ENDL;
        g_object_unref( pBus );
        return false;
    }

    auto *pArgs = g_variant_new( "(s)", url.c_str() );
    if( !pArgs )
    {
        LL_WARNS() << "Cannot create new variant." << LL_ENDL;
        g_object_unref( pBus );
        return false;
    }

    auto pRes  = g_dbus_proxy_call_sync(pProxy,
                                        "GoSLURL",
                                        pArgs,
                                        G_DBUS_CALL_FLAGS_NONE,
                                        -1, nullptr, nullptr);



    if( pRes )
        g_variant_unref( pRes );
    g_object_unref( pProxy );
    g_object_unref( pBus );
    return true;
}

#else // LL_GLIB
bool LLAppViewerLinux::initSLURLHandler()
{
	return false; // not implemented without dbus
}
bool LLAppViewerLinux::sendURLToOtherInstance(const std::string& url)
{
	return false; // not implemented without dbus
}
#endif // LL_GLIB

void LLAppViewerLinux::initCrashReporting(bool reportFreeze)
{
	std::string cmd =gDirUtilp->getExecutableDir();
	cmd += gDirUtilp->getDirDelimiter();
#if LL_LINUX
	cmd += "linux-crash-logger.bin";
#else
# error Unknown platform
#endif

    std::stringstream pid_str;
    pid_str <<  LLApp::getPid();
    std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_DUMP, "");
    std::string appname = gDirUtilp->getExecutableFilename();
	std::string grid{ LLGridManager::getInstance()->getGridId() };
	std::string title{ LLAppViewer::instance()->getSecondLifeTitle() };
	std::string pidstr{ pid_str.str() };
	// launch the actual crash logger
	const char * cmdargv[] =
		{cmd.c_str(),
		 "-user",
		 grid.c_str(),
		 "-name",
		 title.c_str(),
		 "-pid", 
		 pidstr.c_str(),
		 "-dumpdir",
		 logdir.c_str(),
		 "-procname",
		 appname.c_str(),
		 NULL};
	fflush(NULL);

	pid_t pid = fork();
	if (pid == 0)
	{ // child
		execv(cmd.c_str(), (char* const*) cmdargv);		/* Flawfinder: ignore */
		LL_WARNS() << "execv failure when trying to start " << cmd << LL_ENDL;
		_exit(1); // avoid atexit()
	} 
	else
	{
		if (pid > 0)
		{
			// DO NOT wait for child proc to die; we want
			// the logger to outlive us while we quit to
			// free up the screen/keyboard/etc.
			////int childExitStatus;
			////waitpid(pid, &childExitStatus, 0);
		} 
		else
		{
			LL_WARNS() << "fork failure." << LL_ENDL;
		}
	}
	// Sometimes signals don't seem to quit the viewer.  Also, we may
	// have been called explicitly instead of from a signal handler.
	// Make sure we exit so as to not totally confuse the user.
	//_exit(1); // avoid atexit(), else we may re-crash in dtors.
}

bool LLAppViewerLinux::beingDebugged()
{
	static enum {unknown, no, yes} debugged = unknown;

	if (debugged == unknown)
	{
		pid_t ppid = getppid();
		char *name;
		int ret;

		ret = asprintf(&name, "/proc/%d/exe", ppid);
		if (ret != -1)
		{
			char buf[1024];
			ssize_t n;
			
			n = readlink(name, buf, sizeof(buf) - 1);
			if (n != -1)
			{
				char *base = strrchr(buf, '/');
				buf[n + 1] = '\0';
				if (base == NULL)
				{
					base = buf;
				} else {
					base += 1;
				}
				
				if (strcmp(base, "gdb") == 0)
				{
					debugged = yes;
				}
			}
			free(name);
		}
	}

	return debugged == yes;
}

void LLAppViewerLinux::initLoggingAndGetLastDuration()
{
	// Remove the last stack trace, if any
	// This file is no longer created, since the move to Google Breakpad
	// The code is left here to clean out any old state in the log dir
	std::string old_stack_file =
		gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"stack_trace.log");
	LLFile::remove(old_stack_file);

	LLAppViewer::initLoggingAndGetLastDuration();
}

bool LLAppViewerLinux::initParseCommandLine(LLCommandLineParser& clp)
{
	if (!clp.parseCommandLine(gArgC, gArgV))
	{
		return false;
	}

	// Find the system language.
	FL_Locale *locale = NULL;
	FL_Success success = FL_FindLocale(&locale, FL_MESSAGES);
	if (success != 0)
	{
		if (success >= 2 && locale->lang) // confident!
		{
			LL_INFOS("AppInit") << "Language " << ll_safe_string(locale->lang) << LL_ENDL;
			LL_INFOS("AppInit") << "Location " << ll_safe_string(locale->country) << LL_ENDL;
			LL_INFOS("AppInit") << "Variant " << ll_safe_string(locale->variant) << LL_ENDL;

			LLControlVariable* c = gSavedSettings.getControl("SystemLanguage");
			if(c)
			{
				c->setValue(std::string(locale->lang), false);
			}
		}
	}
	FL_FreeLocale(&locale);

	return true;
}

std::string LLAppViewerLinux::generateSerialNumber()
{
	char serial_md5[MD5HEX_STR_SIZE];
	serial_md5[0] = 0;
	std::string best;
	std::string uuiddir("/dev/disk/by-uuid/");

	// trawl /dev/disk/by-uuid looking for a good-looking UUID to grab
	std::string this_name;

	LLDirIterator iter(uuiddir, "*");
	while (iter.next(this_name))
	{
		if (this_name.length() > best.length() ||
		    (this_name.length() == best.length() &&
		     this_name > best))
		{
			// longest (and secondarily alphabetically last) so far
			best = this_name;
		}
	}

	// we don't return the actual serial number, just a hash of it.
	LLMD5 md5( reinterpret_cast<const unsigned char*>(best.c_str()) );
	md5.hex_digest(serial_md5);

	return serial_md5;
}
