/* Implementation of NXBundle class
 *
 * Copyright (C)  1993  The Board of Trustees of  
 * The Leland Stanford Junior University.  All Rights Reserved.
 *
 * Authors: Adam Fedor, Scott Francis, Fred Harris, Paul Kunz, Tom Pavel, 
 *	    Imran Qureshi, and Libing Wang
 *
 * This file is part of an Objective-C class library a window system
 *
 * NXBundle.m,v 1.8 1993/10/20 00:44:53 pfkeb Exp
 */

#include "NXBundle.h"

#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <objc/List.h>
#include <objc/Storage.h>
#include <objc/objc-api.h>
#include <Foundation/objc-load.h>
#include "objc/hashtable.h"
#include "appkit/stdmacros.h"

#ifndef index
#define index strchr
#define rindex strrchr
#endif

/* This is the extension that NXBundle expect on all bundle names */
#define BUNDLE_EXT	"bundle"

/* By default, we transmorgrify extensions of type "nib" to type "xmib"
   which is the common extension for IB files for the GnuStep project
*/
#define IB_EXT		"xmib"

/* Class variables - We keep track of all the bundles and all the classes
   that are in  each bundle
*/
static NXBundle *_mainBundle = nil;
static List	*_bundles = nil;
static List	*_bundleClasses = nil;

/* List of language preferences */
static char	**_languages = NULL;

/* When we are linking in an object file, objc_load_modules calls our
   callBack routine for every Class and Category loaded.  The following
   variable stores the bundle that is currently doing the loading so we know
   where to store the class names.  This is way non-thread-safe, but
   apparently this is how NeXT does it (maybe?).
*/
static int _loadingBundlePos = -1;

/* Get the extension from a name */
static const char *
extension(const char *name)
{
    char *s;
    
    s = rindex(name, '.');
    if (s > rindex(name, '/'))
        return s+1;
    else
        return NULL;
} 

/* Get the object file that should be located in the bundle of the same name */
static char *
object_name(const char *dir)
{
    char name[MAXPATHLEN+1];
    char *object;
    char *s, *f;

    strcpy(name, dir);
    if ((s = rindex(name, '/')))
	s++;
    else
	s = name;
    f = (char *)extension(name);
    if (f && strcmp(f, BUNDLE_EXT) == 0)
	*(f-1) = '\0';

    NX_MALLOC(object, char, strlen(dir) + strlen(s) + 1);
    strcpy(object, dir);
    strcat(object, "/");
    strcat(object, s);

    return object;
} 

/* Construct a path from the directory, language, name and extension.  Used by 
    getPath:...
*/
static void 
construct_path(char *buf, const char *dir, const char *lang, 
	const char *name, const char *ext )
{
    strcpy( buf, dir);
    strcat( buf, "/" );
    if (lang) {
        strcat( buf, lang );
    	strcat( buf, ".lproj/" );
    }
    strcat( buf, name );
    /* Check to see if extension already exists on the end of
       name before we concatenate it. 
    */
    if (ext) {
	char *names_ext;
	names_ext = (char *)extension(buf);
	if (names_ext && (strcmp(ext, names_ext) == 0)) {
            if ( strcmp( ext, "nib") == 0 ) {
		*names_ext = '\0';
                strcat( buf, IB_EXT );
	    }
	} else {
            strcat( buf, "." );
            if ( strcmp( ext, "nib") == 0)
                strcat( buf, IB_EXT );
            else
                strcat( buf, ext );
	}
    }
#ifdef DEBUG
    fprintf(stderr, "Debug (NXBundle): path is %s\n", buf);
#endif
    return;
}

void
_bundle_load_callback(Class theClass, Category *theCategory)
{
    /* Don't store categories */
    if ((_loadingBundlePos >= 0) && !theCategory)
        [[_bundleClasses objectAt:_loadingBundlePos] addElement:&theClass];
}


@implementation NXBundle

+ mainBundle
{
    if ( !_mainBundle ) {
	char	*path;
	char 	*s;

	path = objc_executable_location();
	if (!path) {
	    fprintf(stderr, "Error (NXBundle): Cannot find main bundle.\n");
	    return nil;
	}
	if (!(s = strrchr(path, '/'))) {
	    /* Bad path - do a temporary hack */
	    fprintf(stderr, "Error (NXBundle): Improper main bundle path.\n");
	    path = getcwd(NULL, MAXPATHLEN+1);
	    s = path+strlen(path);
	    //return nil;
	}
	*s = '\0';
#ifdef DEBUG
	fprintf(stderr, "Debug (NXBundle): Found main in %s\n", path);
#endif
	/* We do alloc and init separately so initForDirectory: does not
	   add us to the _bundles list
	*/
	_mainBundle = [NXBundle alloc];
	_mainBundle = [_mainBundle initForDirectory:path];
	free(path);
    }
    return _mainBundle;
}

/* Due to lazy evaluation, we will not find a class if a either classNamed: or
   principalClass has not been called on the particular bundle that contains
   the class.
*/
+ bundleForClass:aClass
{
    int		i, count;
    NXBundle	*bundle = nil;

    if (!aClass)
	return nil;

    count = [_bundleClasses count];
    for (i=0; i < count; i++) {
        int 	j, class_count;
    	Storage *classList = [_bundleClasses objectAt:i];
	class_count = [classList count];
	for (j = 0; j < class_count; j++) 
	    if ([aClass isEqual:(*(Class*)[classList elementAt:j])]) {
	        bundle = [_bundles objectAt:i];
		break;
	}
	if (bundle)
	    break;
    }
    if (!bundle) {
	/* Is it in the main bundle? */
	if (class_is_class(aClass))
	    bundle = [NXBundle mainBundle];
    }

    return bundle;
}

- init
{
    char    directory[MAXPATHLEN+1];

    getcwd( directory, MAXPATHLEN+1 );
    [self initForDirectory:directory];
    return self;
}

- initForDirectory:(const char *)directory
{
    struct stat statbuf;
    [super init];

    if (!_languages)
	[[self class] setSystemLanguages:NULL];

    if (!directory) {
	fprintf(stderr, "Error (NXBundle): NULL directory.\n");
	[self free];
	return nil;
    }
    if (stat(directory, &statbuf) != 0) {
	fprintf(stderr, "Error (NXBundle): Bundle does not exist.\n");
	[self free];
	return nil;
    }
    _directory = NXCopyStringBuffer(directory);

    if (self == _mainBundle)
	return self;

    if (!_bundles) {
        _bundles = [[List alloc] init];
	_bundleClasses = [[List alloc] init];
    }
    [_bundles addObject:self];
    [_bundleClasses addObject:[[Storage alloc] initCount:0
    					elementSize:sizeof(Class)
					description:"#"] ];

    return self;
}

/* We can't really unload the module, since objc_unload_module has
   no idea where we were loaded from, so we just free everything and
   don't worry about it.
*/
- free
{
    int pos = [_bundles indexOf:self];

    if (pos >= 0) {
    	[[_bundleClasses removeObjectAt:pos] free]; 
    	[[_bundles removeObjectAt:pos] free];
    }
    NX_FREE(_directory);
    return [super free];
}

- (const char *)directory
{
    return _directory;
}

- classNamed:(const char *)className
{
    int     j, class_count;
    Storage *classList;
    Class   theClass = Nil;
    if (!_codeLoaded) {
	if (self != _mainBundle && ![self principalClass])
	    return nil;
    }

    if (self == _mainBundle) {
	theClass = objc_lookup_class(className);
	if (theClass && [[self class] bundleForClass:theClass] != _mainBundle)
	    theClass = Nil;
    } else {
    	classList = [_bundleClasses objectAt:[_bundles indexOf:self]];
    	class_count = [classList count];
    	for (j = 0; j < class_count; j++) {
	    theClass = *(Class*)[classList elementAt:j];
	    if (strcmp(className, 
		    class_get_class_name(*(Class*)[classList elementAt:j])) 
		    == 0) {
	        theClass = *(Class*)[classList elementAt:j];
	        break;
	    }
    	}
    }

    return theClass;
}

- principalClass
{
    Storage *classList;
    if (!_codeLoaded && self != _mainBundle) {
	char *object = object_name(_directory);
	/* Link in the object file */
	_loadingBundlePos = [_bundles indexOf:self];
	if (objc_load_module(object, stderr, _bundle_load_callback,NULL,NULL)) {
	    NX_FREE(object);
	    return nil;
	} else
	    _codeLoaded = YES;
	_loadingBundlePos = -1;
	NX_FREE(object);
    }

    if (self == _mainBundle) {
	_codeLoaded = YES;
	return nil;	// the mainBundle does not have a principal class
    }

    classList =  [_bundleClasses objectAt:[_bundles indexOf:self]];
    if ([classList count])
        return *(Class*)[classList elementAt:0];
    else
	return nil;
}

- (BOOL)getPath:(char *)path
    forResource:(const char *)name
	 ofType:(const char *)ext
{
    return [[self class] getPath:path forResource:name
           	  ofType:ext 
	     inDirectory: _directory 
	     withVersion: 0];
}

/* short cut method for Application not supported in NeXTSTEP */
- (BOOL)getPath:(char *)path forSection:(const char *)name
{
    return [self getPath:path forResource:name ofType:NULL];
}

+ (BOOL)getPath:(char *)path forResource:(const char *)name
         ofType:(const char *)ext inDirectory: (const char *)bundlePath
    withVersion: (int)version
{
    struct stat statbuf;
    int         len = 0;
    
    if (!name)
	return NO;

    len = strlen( bundlePath ) + strlen( name ) + strlen("English") + 10;
    if ( ext )
	len += strlen( ext );
    if ( len > MAXPATHLEN-1 ) 
	return NO;

    if (_languages) {
	int count;
	count = 0;
	while (_languages[count]) {
    	    construct_path( path, bundlePath, _languages[count], name, ext );
    	    if ( stat(path, &statbuf) == 0) 
		break;
	    count++;
	}
	if (_languages[count-1])
	    return YES;
    } else {
    	construct_path( path, bundlePath, "English", name, ext );
    	if ( stat(path, &statbuf) == 0) 
	    return YES;
    }

    construct_path( path, bundlePath, NULL, name, ext );
    if ( stat(path, &statbuf) != 0) {
	path[0] = '\0';
	return NO;
    }
    return YES;
}

+ setSystemLanguages:(const char * const *)languages
{
    int	count = 0;

    if (_languages) {
	count = 0;
	while (_languages[count])
	    NX_FREE(_languages[count++]);
	NX_FREE(_languages);
    }
	
    /* If called with a null pointer, look in the environment for the
       language list 
    */
    if (!languages) {
	char *s, *e;
	char *env_language;
	const char *env = getenv("LANGUAGE");
	if (!env)
	    return nil;

	env_language = NXCopyStringBuffer(env);
	s = env_language;
	while (s) {
	    s = index(s+1, ' ');
	    count++;
	}
	NX_MALLOC(_languages, char *, count+1);
	count = 0;
	s = env_language;
	e = s;
	while (e) {
	    e = index(s+1, ' ');
	    if (e)
	        *e = '\0';
	    _languages[count] = NXCopyStringBuffer(s);
	    s = e+1;
	    count++;
	}
	_languages[count] = NULL;
	NX_FREE(env_language);
	return self;
    }

    count = 0;
    while (languages[count++])
	;
    NX_MALLOC(_languages, char *, count+1);
    count = 0;
    while (languages[count]) {
	_languages[count] = NXCopyStringBuffer(languages[count]);
	count++;
    }
    _languages[count] = NULL;
	
    return self;
}

@end
