/*
 * tclMacOSXBundle.c --
 *
 *	This file implements functions that inspect CFBundle structures on
 *	MacOS X.
 *
 * Copyright 2001-2009, Apple Inc.
 * Copyright (c) 2003-2009 Daniel A. Steffen <das@users.sourceforge.net>
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "tclPort.h"

#ifdef HAVE_COREFOUNDATION
#include <CoreFoundation/CoreFoundation.h>

#ifndef TCL_DYLD_USE_DLFCN
/*
 * Use preferred dlfcn API on 10.4 and later
 */
#   if !defined(NO_DLFCN_H) && MAC_OS_X_VERSION_MAX_ALLOWED >= 1040
#	define TCL_DYLD_USE_DLFCN 1
#   else
#	define TCL_DYLD_USE_DLFCN 0
#   endif
#endif

#ifndef TCL_DYLD_USE_NSMODULE
/*
 * Use deprecated NSModule API only to support 10.3 and earlier:
 */
#   if MAC_OS_X_VERSION_MIN_REQUIRED < 1040
#	define TCL_DYLD_USE_NSMODULE 1
#   else
#	define TCL_DYLD_USE_NSMODULE 0
#   endif
#endif

#if TCL_DYLD_USE_DLFCN
#include <dlfcn.h>
#if defined(HAVE_WEAK_IMPORT) && MAC_OS_X_VERSION_MIN_REQUIRED < 1040
/*
 * Support for weakly importing dlfcn API.
 */
extern void *dlsym(void *handle, const char *symbol) WEAK_IMPORT_ATTRIBUTE;
extern char *dlerror(void) WEAK_IMPORT_ATTRIBUTE;
#endif
#endif

#if TCL_DYLD_USE_NSMODULE
#include <mach-o/dyld.h>
#endif

#if (TCL_DYLD_USE_DLFCN && MAC_OS_X_VERSION_MIN_REQUIRED < 1040) || \
	(MAC_OS_X_VERSION_MIN_REQUIRED < 1050)
MODULE_SCOPE long tclMacOSXDarwinRelease;
#endif

#ifdef TCL_DEBUG_LOAD
#define TclLoadDbgMsg(m, ...) do { \
	    fprintf(stderr, "%s:%d: %s(): " m ".\n", \
	    strrchr(__FILE__, '/')+1, __LINE__, __func__, ##__VA_ARGS__); \
	} while (0)
#else
#define TclLoadDbgMsg(m, ...)
#endif

#endif /* HAVE_COREFOUNDATION */

/*
 *----------------------------------------------------------------------
 *
 * Tcl_MacOSXOpenBundleResources --
 *
 *	Given the bundle name for a shared library, this routine sets
 *	libraryPath to the Resources/Scripts directory in the framework
 *	package. If hasResourceFile is true, it will also open the main
 *	resource file for the bundle.
 *
 * Results:
 *	TCL_OK if the bundle could be opened, and the Scripts folder found.
 *	TCL_ERROR otherwise.
 *
 * Side effects:
 *	libraryVariableName may be set, and the resource file opened.
 *
 *----------------------------------------------------------------------
 */

int
Tcl_MacOSXOpenBundleResources(
    Tcl_Interp *interp,
    CONST char *bundleName,
    int hasResourceFile,
    int maxPathLen,
    char *libraryPath)
{
    return Tcl_MacOSXOpenVersionedBundleResources(interp, bundleName,
	    NULL, hasResourceFile, maxPathLen, libraryPath);
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_MacOSXOpenVersionedBundleResources --
 *
 *	Given the bundle and version name for a shared library (version name
 *	can be NULL to indicate latest version), this routine sets libraryPath
 *	to the Resources/Scripts directory in the framework package. If
 *	hasResourceFile is true, it will also open the main resource file for
 *	the bundle.
 *
 * Results:
 *	TCL_OK if the bundle could be opened, and the Scripts folder found.
 *	TCL_ERROR otherwise.
 *
 * Side effects:
 *	libraryVariableName may be set, and the resource file opened.
 *
 *----------------------------------------------------------------------
 */

int
Tcl_MacOSXOpenVersionedBundleResources(
    Tcl_Interp *interp,
    CONST char *bundleName,
    CONST char *bundleVersion,
    int hasResourceFile,
    int maxPathLen,
    char *libraryPath)
{
#ifdef HAVE_COREFOUNDATION
    CFBundleRef bundleRef, versionedBundleRef = NULL;
    CFStringRef bundleNameRef;
    CFURLRef libURL;

    libraryPath[0] = '\0';

    bundleNameRef = CFStringCreateWithCString(NULL, bundleName,
	    kCFStringEncodingUTF8);

    bundleRef = CFBundleGetBundleWithIdentifier(bundleNameRef);
    CFRelease(bundleNameRef);

    if (bundleVersion && bundleRef) {
	/*
	 * Create bundle from bundleVersion subdirectory of 'Versions'.
	 */

	CFURLRef bundleURL = CFBundleCopyBundleURL(bundleRef);

	if (bundleURL) {
	    CFStringRef bundleVersionRef = CFStringCreateWithCString(NULL,
		    bundleVersion, kCFStringEncodingUTF8);

	    if (bundleVersionRef) {
		CFComparisonResult versionComparison = kCFCompareLessThan;
		CFStringRef bundleTailRef = CFURLCopyLastPathComponent(
			bundleURL);

		if (bundleTailRef) {
		    versionComparison = CFStringCompare(bundleTailRef,
			    bundleVersionRef, 0);
		    CFRelease(bundleTailRef);
		}
		if (versionComparison != kCFCompareEqualTo) {
		    CFURLRef versURL = CFURLCreateCopyAppendingPathComponent(
			    NULL, bundleURL, CFSTR("Versions"), TRUE);

		    if (versURL) {
			CFURLRef versionedBundleURL =
				CFURLCreateCopyAppendingPathComponent(
				NULL, versURL, bundleVersionRef, TRUE);

			if (versionedBundleURL) {
			    versionedBundleRef = CFBundleCreate(NULL,
				    versionedBundleURL);
			    if (versionedBundleRef) {
				bundleRef = versionedBundleRef;
			    }
			    CFRelease(versionedBundleURL);
			}
			CFRelease(versURL);
		    }
		}
		CFRelease(bundleVersionRef);
	    }
	    CFRelease(bundleURL);
	}
    }

    if (bundleRef) {
	if (hasResourceFile) {
	    /*
	     * Dynamically acquire address for CFBundleOpenBundleResourceMap
	     * symbol, since it is only present in full CoreFoundation on Mac
	     * OS X and not in CFLite on pure Darwin.
	     */

	    static int initialized = FALSE;
	    static short (*openresourcemap)(CFBundleRef) = NULL;

	    if (!initialized) {
#if TCL_DYLD_USE_DLFCN
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1040
		if (tclMacOSXDarwinRelease >= 8)
#endif
		{
		    const char *errMsg = nil;
		    openresourcemap = dlsym(RTLD_NEXT,
			    "CFBundleOpenBundleResourceMap");
		    if (!openresourcemap) {
			errMsg = dlerror();
			TclLoadDbgMsg("dlsym() failed: %s", errMsg);
		    }
		}
		if (!openresourcemap)
#endif
		{
#if TCL_DYLD_USE_NSMODULE
		    NSSymbol nsSymbol = NULL;
		    if (NSIsSymbolNameDefinedWithHint(
			    "_CFBundleOpenBundleResourceMap",
			    "CoreFoundation")) {
			nsSymbol = NSLookupAndBindSymbolWithHint(
				"_CFBundleOpenBundleResourceMap",
				"CoreFoundation");
			if (nsSymbol) {
			    openresourcemap = NSAddressOfSymbol(nsSymbol);
			}
		    }
#endif
		}
		initialized = TRUE;
	    }

	    if (openresourcemap) {
		openresourcemap(bundleRef);
	    }
	}

	libURL = CFBundleCopyResourceURL(bundleRef, CFSTR("Scripts"),
		NULL, NULL);

	if (libURL) {
	    /*
	     * FIXME: This is a quick fix, it is probably not right for
	     * internationalization.
	     */

	    CFURLGetFileSystemRepresentation(libURL, TRUE,
		    (unsigned char*) libraryPath, maxPathLen);
	    CFRelease(libURL);
	}
	if (versionedBundleRef) {
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050
	    /* Workaround CFBundle bug in Tiger and earlier. [Bug 2569449] */
	    if (tclMacOSXDarwinRelease >= 9)
#endif
	    {
		CFRelease(versionedBundleRef);
	    }
	}
    }

    if (libraryPath[0]) {
	return TCL_OK;
    } else {
	return TCL_ERROR;
    }
#else  /* HAVE_COREFOUNDATION */
    return TCL_ERROR;
#endif /* HAVE_COREFOUNDATION */
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */
