/*
   This file is part of the KDE libraries
   Copyright (C) 2008 Benjamin Reed <rangerrick@befunk.com>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License version 2 as published by the Free Software Foundation.

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

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

#include "kkernel_mac.h"

#include <config.h>

#ifdef Q_OS_MACX

#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/param.h>
#include <crt_externs.h>
#include <mach-o/dyld.h>

#include <CoreFoundation/CFBundle.h>
#include <CoreFoundation/CFString.h>
#include <CoreFoundation/CFURL.h>
#include <QtCore/QFile>
#include <QtCore/QProcess>
#include <QtCore/QStringList>
#include <QtCore/qvarlengtharray.h>
#include <kstandarddirs.h>
#include <ksharedconfig.h>
#include <kconfig.h>
#include <kdebug.h>

int timeout = 3000; // msec

bool dbus_initialized = false;

/**
  qAppFileName() is not public in qt4/mac, so we need to redo it here
*/

QString convert_CFString_to_QString(CFStringRef str) {
	CFIndex length = CFStringGetLength(str);
	const UniChar *chars = CFStringGetCharactersPtr(str);
	if (chars)
		return QString(reinterpret_cast<const QChar *>(chars), length);

	QVarLengthArray<UniChar> buffer(length);
	CFStringGetCharacters(str, CFRangeMake(0, length), buffer.data());
	return QString(reinterpret_cast<const QChar *>(buffer.constData()), length);
}

/**
 Calling CoreFoundation APIs (which is unavoidable in Qt/Mac) has always had issues
 on Mac OS X, but as of 10.5 is explicitly disallowed with an exception.  As a
 result, in the case where we would normally fork and then dlopen code, or continue
 to run other code, we must now fork-and-exec.
 
 See "CoreFoundation and fork()" at http://developer.apple.com/releasenotes/CoreFoundation/CoreFoundation.html
*/

void
mac_fork_and_reexec_self()
{
	int argc = *_NSGetArgc();
	char ** argv = *_NSGetArgv();
	char * newargv[argc+2];
	char progname[PATH_MAX];
	uint32_t buflen = PATH_MAX;
	_NSGetExecutablePath(progname, &buflen);
	bool found_psn = false;

	for (int i = 0; i < argc; i++) {
		newargv[i] = argv[i];
	}

	newargv[argc] = "--nofork";
	newargv[argc+1] = NULL;

	int x_fork_result = fork();
	switch(x_fork_result) {

		case -1:
#ifndef NDEBUG
			fprintf(stderr, "Mac OS X workaround fork() failed!\n");
#endif
			::_exit(255);
			break;

		case 0:
			// Child
			execvp(progname, newargv);
			break;

		default:
			// Parent
			_exit(0);
			break;

	}
}

/**
 Set the D-Bus environment based on session bus socket
*/

bool mac_set_dbus_address(QString value)
{
	if (!value.isEmpty() && QFile::exists(value) && (QFile::permissions(value) & QFile::WriteUser)) {
		value = QLatin1String("unix:path=") + value;
		::setenv("DBUS_SESSION_BUS_ADDRESS", value.toLocal8Bit(), 1);
		kDebug() << "set session bus address to" << value;
		return true;
	}
	return false;
}

/**
 Make sure D-Bus is initialized, by any means necessary.
*/

void mac_initialize_dbus()
{
	if (dbus_initialized)
		return;

	QString dbusVar = QString::fromLocal8Bit(qgetenv("DBUS_SESSION_BUS_ADDRESS"));
	if (!dbusVar.isEmpty()) {
		dbus_initialized = true;
		return;
	}

	dbusVar = QFile::decodeName(qgetenv("DBUS_LAUNCHD_SESSION_BUS_SOCKET"));
	if (mac_set_dbus_address(dbusVar)) {
		dbus_initialized = true;
		return;
	}

	QString externalProc;
	QStringList path = QFile::decodeName(qgetenv("KDEDIRS")).split(QLatin1Char(':')).replaceInStrings(QRegExp(QLatin1String("$")), QLatin1String("/bin"));
	path << QFile::decodeName(qgetenv("PATH")).split(QLatin1Char(':')) << QLatin1String("/usr/local/bin");

	for (int i = 0; i < path.size(); ++i) {
		QString testLaunchctl = QString(path.at(i)).append(QLatin1String("/launchctl"));
		if (QFile(testLaunchctl).exists()) {
			externalProc = testLaunchctl;
			break;
		}
	}

	if (!externalProc.isEmpty()) {
                QProcess qp;
                qp.setTextModeEnabled(true);

		qp.start(externalProc, QStringList() << QLatin1String("getenv") << QLatin1String("DBUS_LAUNCHD_SESSION_BUS_SOCKET"));
                if (!qp.waitForFinished(timeout)) {
                    kDebug() << "error running" << externalProc << qp.errorString();
                    return;
                }
                if (qp.exitCode() != 0) {
                    kDebug() << externalProc << "unsuccessful:" << qp.readAllStandardError();
                    return;
                }

                QString line = QString::fromLatin1(qp.readLine()).trimmed(); // read the first line
                if (mac_set_dbus_address(line))
                    dbus_initialized = true; // hooray
	}

	if (dbus_initialized == false) {
		kDebug() << "warning: unable to initialize D-Bus environment!";
	}

}

QString mac_app_filename() {
	static QString appFileName;
	if (appFileName.isEmpty()) {
		CFURLRef bundleURL = NULL;
		CFBundleRef bundle = NULL;
		CFStringRef bundlePath = NULL;

		bundle = CFBundleGetMainBundle();
		if (bundle) {
			bundleURL = CFBundleCopyBundleURL(bundle);
			bundlePath = CFURLCopyFileSystemPath(bundleURL, kCFURLPOSIXPathStyle);

			if (bundleURL) {
				CFRelease(bundleURL);
			}

			if (bundlePath) {
				appFileName = convert_CFString_to_QString(bundlePath);
				CFRelease(bundlePath);
			}
		}
	}
	return appFileName;
}

#endif
