/*
    MaskedException - Allow masking of exceptions.

    Copyright (C) 1995, Adam Fedor.

*/
#ifdef NEXT_FOUNDATION
#import <foundation/NSDictionary.h>
#import <foundation/NSString.h>
#import <foundation/NSValue.h>
#import <foundation/NSUtilities.h>
#else
#import <Foundation/NSDictionary.h>
#import <Foundation/NSString.h>
#import <Foundation/NSValue.h>
#import <Foundation/NSUtilities.h>
#endif
#include "MathArray/MaskedException.h"
#include <stdio.h>

typedef struct {
    ExceptionMask 		 e_mask;
    id <MaskedExceptionHandling> handler;
} mask_info_t;

static NSMutableDictionary *exceptions = nil;
static NSMutableDictionary *prefixes = nil;
static mask_info_t	    global_mask = {RaiseException, nil};

@implementation MaskedException
/*
This is a simple class that allows one to control the effects of raised
exceptions. Perhaps one of the best uses of this class is for debugging. If an 
exception occurs during debugging, the program aborts (unless the exception is 
caught), but in any case, the original context of the error is completely lost, 
making it difficult to trace the cause of the error. With MaskedException, you 
can cause the exception to abort the program in place, without jumping out of 
the context, so you can go back and look for at the frame that caused the 
error. You can set a mask for one exception or a whole class of excetions using 
MaskedExcetion.  The set of choices (masks) includes:

.LI .ital RaiseException	.plain Normal operation. Raise the exception as usual.
		
.LI .ital AbortException	.plain Abort, rather than raise the exception. Prints a message to stderr.
		
.LI .ital HandleException	.plain Sends a message to  a specific exception handler object.
		
.LI .ital LogException		.plain Log the exception (NSLog) and continue excecution. Dangerous!
		
.LI .ital IgnoreException	.plain Completely ignore the exception and continue excecution. Dangerous!

Note the last two choices, which allow one to completely ignore the exception 
and continue as if nothing happened. Normally, this is not very useful and 
would probably cause some kind of program crash, but it could be used in cases 
where an exception is meant to be a warning rather than an error. The third 
choice allows one to set a specific handler object to handle one or more 
exceptions. When the exception is raised, the message .bold 
exceptionWithName:reason:userInfo .plain is sent to the object (like the 
NSException method of the same name).
*/

/* Easy way to get MaskedException to work with all exceptions 
   (same as [self poseAsClass:[NSException class]]).
*/
+ (void)bodysnatchNSException
{
    [self poseAsClass:[NSException class]];
}

+ (void)_updateLazyVariables
{
    if (!exceptions)
    	exceptions = [[NSMutableDictionary dictionaryWithCapacity:2] retain];
    if (!prefixes)
    	prefixes = [[NSMutableDictionary dictionaryWithCapacity:2] retain];
}

/* Set the handler object for exceptions with the name exceptionName. */
+ (void)setHandler: (id <MaskedExceptionHandling>)handler 
	forException:(NSString *)exceptionName 
{
    NSValue *info;
    mask_info_t  mask_info;
    
    if (!exceptionName)
    	return;
    [self _updateLazyVariables];
    mask_info.e_mask  = HandleException;
    mask_info.handler = handler;
    info = [NSValue value:&mask_info withObjCType:@encode(mask_info_t)];
    [exceptions setObject:info forKey:exceptionName];
}

/* Set the handler object for any exception with a name that has the 
   prefix prefix. */
+ (void)setHandler:(id <MaskedExceptionHandling>)handler 
	forExceptionsWithPrefix:(NSString *)prefix 
{
    NSValue *info;
    mask_info_t  mask_info;
    
    if (!prefix)
    	return;
    [self _updateLazyVariables];
    mask_info.e_mask  = HandleException;
    mask_info.handler = handler;
    info = [NSValue value:&mask_info withObjCType:@encode(mask_info_t)];
    [prefixes setObject:info forKey:prefix];
}

/* Set the default handler object for all exceptions. */
+ (void)setHandlerForAllExceptions:(id <MaskedExceptionHandling>)handler
{
    global_mask.e_mask  = HandleException;
    global_mask.handler = handler;
}

/* Set the mask for exceptions with the name exceptionName. */
+ (void)setMask:(ExceptionMask)mask forException:(NSString *)exceptionName 
{
    NSValue *info;
    mask_info_t  mask_info;
    
    if (!exceptionName)
    	return;
    [self _updateLazyVariables];
    mask_info.e_mask = mask;
    info = [NSValue value:&mask withObjCType:@encode(mask_info_t)];
    [exceptions setObject:info forKey:exceptionName];
}

/* Set the mask for any exception with a name that has the prefix prefix. */
+ (void)setMask:(ExceptionMask)mask forExceptionsWithPrefix:(NSString *)prefix 
{
    NSValue *info;
    mask_info_t  mask_info;
    
    if (!prefix)
    	return;
    [self _updateLazyVariables];
    mask_info.e_mask = mask;
    info = [NSValue value:&mask_info withObjCType:@encode(mask_info_t)];
    [prefixes setObject:info forKey:prefix];
}

/* Set the default mask for all exceptions. */
+ (void)setMaskForAllExceptions:(ExceptionMask)mask
{
    global_mask.e_mask = mask;
}

+ (mask_info_t)maskInfoForException:(NSString *)exceptionName
{
    NSValue *info;
    mask_info_t  mask_info;
    
    if (!exceptionName)
    	return ((mask_info_t){0, 0});
    [self _updateLazyVariables];
    mask_info = global_mask;
    if ((info = [exceptions objectForKey:exceptionName]))
    	[info getValue:&mask_info];
    else {
    	NSEnumerator *enumerator = [prefixes keyEnumerator];
	NSString *prefix = nil;
	while ((prefix = [enumerator nextObject]))
	    if ([exceptionName hasPrefix:prefix])
	    	break;
	    else
	    	prefix = nil;
	if (prefix) {
	    info = [prefixes objectForKey:prefix];
    	    [info getValue:&mask_info];
	}
    }
    return mask_info;
}

/* Returns the mask for the exception with name exceptionName if is was
   explicitly set, or the mask for the set of exception names that have the
   same prefix as exceptionName.  Otherwise returns the global exception mask
   (RaiseException by default) set by setMaskForAllExceptions. 
*/
+ (ExceptionMask)maskForException:(NSString *)exceptionName
{
    return ([self maskInfoForException:exceptionName]).e_mask;
}

/* Override from NSException to do the right thing */
- (volatile void)raise
{
    NSString *raise_name;
    NSString *raise_reason;
    NSDictionary *raise_info;
    mask_info_t  mask_info;
    static BOOL  did_raise;

    /* Prevent us from doing something stupid like causing an exception in
       the middle of our own exception handling */
    if (did_raise) {
        did_raise = NO;
    	[super raise];
    }
    did_raise = YES;

#ifdef NEXT_FOUNDATION
    raise_name = [self exceptionName];
    raise_reason = [self exceptionReason];
    raise_info = [self exceptionUserInfo];
#else
    raise_name = [self name];
    raise_reason = [self reason];
    raise_info = [self userInfo];
#endif

    mask_info = [MaskedException maskInfoForException:raise_name];
    did_raise = NO;
    switch (mask_info.e_mask) {
    case RaiseException:
    	[super raise];
	break;
    case AbortException:
    	// FIXME: where should we print the information? stderr I assume.
    	fprintf(stderr, "Aborted Exception %s: %s\n", [raise_name cString],
		[raise_reason cString]);
	abort();
	break;
    case HandleException:
    	[mask_info.handler exceptionWithName:raise_name
		reason:raise_reason
		userInfo:raise_info];
	break;
    case LogException:
	// FIXME: should be NSLog
    	//NSLog(@"Ignored Exception %@: %@\n", raise_name, raise_reason);
    	fprintf(stderr, "Ignored Exception %s: %s\n", [raise_name cString],
		[raise_reason cString]);
	break;
    case IgnoreException:
	break;
    }
    
    // FIXME: The last two cases actually return when the method is assumed
    // not to return.  How useful are these cases?
}

@end

