/* FunHouseDocument.m 
 
 Version: 1.1
 
 © Copyright 2004-2009 Apple, Inc. All rights reserved.
 
 IMPORTANT:  This Apple software is supplied to 
 you by Apple Computer, Inc. ("Apple") in 
 consideration of your agreement to the following 
 terms, and your use, installation, modification 
 or redistribution of this Apple software 
 constitutes acceptance of these terms.  If you do 
 not agree with these terms, please do not use, 
 install, modify or redistribute this Apple 
 software.
 
 In consideration of your agreement to abide by 
 the following terms, and subject to these terms, 
 Apple grants you a personal, non-exclusive 
 license, under Apple's copyrights in this 
 original Apple software (the "Apple Software"), 
 to use, reproduce, modify and redistribute the 
 Apple Software, with or without modifications, in 
 source and/or binary forms; provided that if you 
 redistribute the Apple Software in its entirety 
 and without modifications, you must retain this 
 notice and the following text and disclaimers in 
 all such redistributions of the Apple Software. 
 Neither the name, trademarks, service marks or 
 logos of Apple Computer, Inc. may be used to 
 endorse or promote products derived from the 
 Apple Software without specific prior written 
 permission from Apple.  Except as expressly 
 stated in this notice, no other rights or 
 licenses, express or implied, are granted by 
 Apple herein, including but not limited to any 
 patent rights that may be infringed by your 
 derivative works or by other works in which the 
 Apple Software may be incorporated.
 
 The Apple Software is provided by Apple on an "AS 
 IS" basis.  APPLE MAKES NO WARRANTIES, EXPRESS OR 
 IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED 
 WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY 
 AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING 
 THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE 
 OR IN COMBINATION WITH YOUR PRODUCTS.
 
 IN NO EVENT SHALL APPLE BE LIABLE FOR ANY 
 SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL 
 DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 
 OF USE, DATA, OR PROFITS; OR BUSINESS 
 INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, 
 REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF 
 THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER 
 UNDER THEORY OF CONTRACT, TORT (INCLUDING 
 NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN 
 IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF 
 SUCH DAMAGE.
 
 */

#import "FunHouseDocument.h"
#import "EffectStack.h"
#import "FunHouseWindowController.h"
#import "ParameterView.h"
#import "CoreImageView.h"
#import "EffectStackController.h"
#import <ApplicationServices/ApplicationServices.h>
#import <Carbon/Carbon.h>
#import "FunHouseApplication.h"

@implementation FunHouseDocument

//
// fun house document code - we're a subclass of NSDocument
//
// this code uses NSFileWrapper to encode the fun house preset
// check out the NSFileWrapper class - it allows you to support packages or regular files
// to do so, you need to encode your data files as NSData first
// this NSDocument subclass also uses ImageIO to encode and decode the JPEG and TIFF files to and from NSData.
//

// set this to 1 to disable save
// to complete this, you must disconnext the save and save as menu items in MainMenu.nib
#define SAVEDISABLED 0

- (id)init
{
    self = [super init];
    if (self)
    {
        // allocate the effect stack for a document here
        effectStack = [[EffectStack alloc] init];
        fullScreen = NO;
        hasWindowDimensions = NO;
    }
    return self;
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    // release the effect stack for the document here
    [effectStack release];
    if (colorspace)
        CFRelease(colorspace);
    [super dealloc];
}

// this sets up our (non-full-screen) window controller
- (void)makeWindowControllers
{
    // create the window controller
    windowController = [[FunHouseWindowController allocWithZone:[self zone]] init];
    [self addWindowController:windowController];
    [windowController release];
}

+ (NSArray *)writableTypes
{
	return [NSArray arrayWithObjects:@"Fun House Preset", @"JPEG File", @"TIFF File", nil];
}

+ (BOOL)isNativeType:(NSString *)aType
{
	return [[[self class] writableTypes] containsObject:aType];
}

- (BOOL)prepareSavePanel:(NSSavePanel *)sp
{
    // assign defaults for the save panel
    [sp setTitle:@"Save image"];
    [sp setExtensionHidden:NO];
    return YES;
}


// add '-1' to filename before extension
- (NSString *)disambiguateFilename:(NSString *)filename
{
    NSString *base, *extension;
    
    base = [filename stringByDeletingPathExtension];
    extension = [filename pathExtension];
    return [[base stringByAppendingString:@"-1"] stringByAppendingPathExtension:extension];
}

#if SAVEDISABLED
// we implement this method in the NSDocument subclass specifically so we can disable the save and save as commands
- (BOOL)isDocumentEdited
{
    // this is all that's required to make a document think it doesn't have to be saved
    return NO;
}
#endif

// create JPEG data for a document, using ImageIO
- (NSData *)jpegData:(NSError **)outError
{
    NSData *d;
    NSRect r;
    CIContext *context;

    // save the image
    // create a mutable data to store the JPEG data into
    CFMutableDataRef data = CFDataCreateMutable(kCFAllocatorDefault, 0);
    // create an image destination (ImageIO's way of saying we want to save to a file format)
    // note: public.jpeg denotes that we are saving to JPEG
    CGImageDestinationRef ref = CGImageDestinationCreateWithData(data, (CFStringRef)@"public.jpeg", 1, NULL);
    if (ref == NULL)
    {
        printf("problems creating image destination\n");
        if(data)
			CFRelease(data);
        if(ref)
			CFRelease(ref);
        *outError = [NSError errorWithDomain:@"fun house errors" code:-10101
          userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"problems creating image destination for file save", NSLocalizedDescriptionKey, nil]];
        return nil;
    }
    // get the window controller for this document
    FunHouseWindowController *con = [[self windowControllers] objectAtIndex:0];
    // compute the rectangle that the file fits into - we use the bounds of the view in many cases
    if ([[con coreImageView] isScaled])
    {
        if ([effectStack layerCount] < 1 || ![[effectStack typeAtIndex:0] isEqualToString:@"image"])
            // if there's no image (generator used instead or nothing is in the effect stack) we use the view bounds
            r = [[con coreImageView] bounds];
        else
        {
            // for the scaled image case, we use the actual image extents (too big to fit on screen)
            CGRect extent = [[effectStack imageAtIndex:0] extent];
            r = NSMakeRect(extent.origin.x, extent.origin.y, extent.size.width, extent.size.height);
        }
    }
    else
        // image is not scaled by a view transform, use the view bounds
        r = [[con coreImageView] bounds];
    // we make a CGImageRef for the view, based on tthe core image graph. Note: it gets rendered in createCGImage
    if (colorspace == nil)
        context = [CIContext contextWithCGContext:[[[[con coreImageView] window] graphicsContext] graphicsPort]
          options:[NSDictionary dictionaryWithObjectsAndKeys:(id)CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB), kCIContextOutputColorSpace, nil]];
    else
        context = [CIContext contextWithCGContext:[[[[con coreImageView] window] graphicsContext] graphicsPort]
          options:[NSDictionary dictionaryWithObjectsAndKeys:(id)colorspace, kCIContextOutputColorSpace, nil]];
    CGImageRef iref = [context createCGImage:[effectStack coreImageResultForRect:r] fromRect:CGRectMake(r.origin.x, r.origin.y, r.size.width, r.size.height)];
    // add image to the ImageIO destination (specify the image we want to save)
    CGImageDestinationAddImage(ref, iref, NULL);
    // finalize: this saves the image to the JPEG format as data
    if (!CGImageDestinationFinalize(ref))
    {
        printf("problems writing JPEG file\n");
        CFRelease(data);
        CFRelease(ref);
        *outError = [NSError errorWithDomain:@"fun house errors" code:-10102
          userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"problems writing JPEG file for file save", NSLocalizedDescriptionKey, nil]];
        return nil;
    }
    CFRelease(ref);
    *outError = nil;
    // return the data
    d = (NSData *)data;
    return [d autorelease];
}

- (NSData *)tiffData:(NSError **)outError
{
    NSData *d;
    NSRect r;
    CIContext *context;
    
    // save the image
    // create a mutable data to store the TIFF data into
    CFMutableDataRef data = CFDataCreateMutable(kCFAllocatorDefault, 0);
    // create an image destination (ImageIO's way of saying we want to save to a file format)
    // note: public.tiff denotes that we are saving to TIFF
    CGImageDestinationRef ref = CGImageDestinationCreateWithData(data, (CFStringRef)@"public.tiff", 1, NULL);
    if (ref == NULL)
        {
        printf("problems creating image destination\n");
        CFRelease(data);
        CFRelease(ref);
        *outError = [NSError errorWithDomain:@"fun house errors" code:-10101
          userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"problems creating image destination for file save", NSLocalizedDescriptionKey, nil]];
        return nil;
        }
    // get the window controller for this document
    FunHouseWindowController *con = [[self windowControllers] objectAtIndex:0];
    // compute the rectangle that the file fits into - we use the bounds of the view in many cases
    if ([[con coreImageView] isScaled])
    {
        if ([effectStack layerCount] < 1 || ![[effectStack typeAtIndex:0] isEqualToString:@"image"])
            // if there's no image (generator used instead or nothing is in the effect stack) we use the view bounds
            r = [[con coreImageView] bounds];
        else
        {
            // for the scaled image case, we use the actual image extents (too big to fit on screen)
            CGRect extent = [[effectStack imageAtIndex:0] extent];
            r = NSMakeRect(extent.origin.x, extent.origin.y, extent.size.width, extent.size.height);
        }
    }
    else
        // image is not scaled by a view transform, use the view bounds
        r = [[con coreImageView] bounds];
    // we make a CGImageRef for the view, based on tthe core image graph. Note: it gets rendered in createCGImage
    if (colorspace == nil)
        context = [CIContext contextWithCGContext:[[[[con coreImageView] window] graphicsContext] graphicsPort]
          options:[NSDictionary dictionaryWithObjectsAndKeys:(id)CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB), kCIContextOutputColorSpace, nil]];
    else
        context = [CIContext contextWithCGContext:[[[[con coreImageView] window] graphicsContext] graphicsPort]
          options:[NSDictionary dictionaryWithObjectsAndKeys:(id)colorspace, kCIContextOutputColorSpace, nil]];
    CGImageRef iref = [context createCGImage:[effectStack coreImageResultForRect:r] fromRect:CGRectMake(r.origin.x, r.origin.y, r.size.width, r.size.height)];
    // add image to the ImageIO destination (specify the image we want to save)
    CGImageDestinationAddImage(ref, iref, NULL);
    // finalize: this saves the image to the TIFF format as data
    if (!CGImageDestinationFinalize(ref))
        {
        printf("problems writing TIFF file\n");
        if(data)
			CFRelease(data);
        CFRelease(ref);
        *outError = [NSError errorWithDomain:@"fun house errors" code:-10102
          userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"problems writing TIFF file for file save", NSLocalizedDescriptionKey, nil]];
        return nil;
        }
    if(ref)
		CFRelease(ref);
    *outError = nil;
    // return the data
    d = (NSData *)data;
    return [d autorelease];
}

// convert a CIImage to a CGImageRef - uses the context of the document's view
- (CGImageRef)CIImageToCGImage:(CIImage *)im usingRect:(CGRect)r
    {
    FunHouseWindowController *con = [[self windowControllers] objectAtIndex:0];
    return [[[con coreImageView] context] createCGImage:im fromRect:r];
    }
    
- (NSPrintOperation *)printOperationWithSettings:(NSDictionary *)printSettings error:(NSError **)outError
{
    FunHouseWindowController *con = [[self windowControllers] objectAtIndex:0];
    
    return [NSPrintOperation printOperationWithView: [con coreImageView]];
}

// save the document's effect stack as a preset bundle (package)
// images are saved as actual files within the package with names ident1.tiff, ident2.tiff, etc.
// the effect stack is saved as a text XML file, with the name file.xml
// this means that core image data must be encoded as NSDictionary, NSArray, NSString and NSNumber items
// anything that's not of that form (like CIVector, CIColor, etc.) must be re-encoded
// this also has good code for converting CIImage to tiff file data
- (NSFileWrapper *)fileWrapperForPreset:(NSError **)outError
{
    BOOL hasBackground;
    NSInteger i, count, lastImageNumber;
    NSString *type, *file, *key, *error, *name, *filename, *path;
    NSMutableDictionary *layerdict, *filedict, *values, *pathdict;
    NSMutableArray *layerarray;
    NSPoint offset;
    NSArray *inputKeys;
    NSDictionary *attr, *parameter;
    NSEnumerator *enumerator, *e;
    id obj;
    CIFilter *filter;
    NSData *xmlData, *d;
    NSFileWrapper *fw;
    NSMutableDictionary *dict, *localdict;
    
    // create the preset bundle
    fw = [[NSFileWrapper alloc] initDirectoryWithFileWrappers:[NSDictionary dictionary]];
    count = [effectStack layerCount];
    // first sort out all the image files used by this effect stack
    pathdict = [NSMutableDictionary dictionary];
    // enumerate the document's effect stack layers
    for (i = 0; i < count; i++)
    {
        type = [effectStack typeAtIndex:i];
        if ([type isEqualToString:@"image"])
        {
            // image layer
            // get original image file path
            path = [effectStack imageFilePathAtIndex:i];
            // make sure the file isn't being stored twice - we can reference the same file as many times as we want!
            if ([pathdict valueForKey:path] == nil)
            {		
		filename = [path lastPathComponent];
                // make sure the last component is unique (there could be two files withg the same name from two directories)
                // we must assume they are different at this point
                e = [pathdict objectEnumerator];
                while ((localdict = [e nextObject]) != nil)
                {
                    name = [localdict valueForKey:@"filename"];
                    if ([name isEqualToString:filename])
                    {
                        // handle collision
                        filename = [self disambiguateFilename:filename];
                        // start over - check again!
                        e = [pathdict objectEnumerator];
                    }
                }
		// set up the path to have a representation, by default its last component (e. g. tiger.jpg)
                [pathdict setValue:
                  [NSMutableDictionary dictionaryWithObjectsAndKeys:filename, @"filename",
                    [NSNumber numberWithBool:NO], @"storedToDisk", [effectStack imageFileDataAtIndex:i], @"data", nil]
                  forKey:path];
            }
        }
        else if ([type isEqualToString:@"filter"])
        {
            // filter layer
            filter = [effectStack filterAtIndex:i];
            attr = [filter attributes];
            inputKeys = [filter inputKeys];
            // decide if the filter has a background (it's a blend mode)
            enumerator = [inputKeys objectEnumerator];
            hasBackground = NO;
            while ((key = [enumerator nextObject]) != nil) 
            {
                if ([key isEqualToString:@"inputBackgroundImage"])
                {
                    hasBackground = YES;
                    break;
                }
            }
            // now inspect all the input keys for the filter
            enumerator = [inputKeys objectEnumerator];
            while ((key = [enumerator nextObject]) != nil)
            {
                parameter = [attr objectForKey:key];
                obj = [filter valueForKey:key];
                if (obj != nil)
                {
                    if ([obj isKindOfClass:[CIImage class]])
                    {
                        // decide whether to skip this key (it gets filled in by something above it in the effect stack)
                        if (hasBackground && [key isEqualToString:@"inputBackgroundImage"])
                            continue; // a blend mode; we chain on inputBackgroundImage
                        if (!hasBackground && [key isEqualToString:@"inputImage"])
                            continue; // not a blend mode; we chain on inputImage
                        // we have an image that fills in a filter image parameter
                        // get its path
                        path = [effectStack filterLayer:i imageFilePathValueForKey:key];
                        // make sure the file isn't being stored twice - we can reference the same file as many times as we want!
                        if ([pathdict valueForKey:path] == nil)
                        {
                            filename = [path lastPathComponent];
                            // make sure the last component is unique (there could be two files withg the same name from two directories)
                            // we must assume they are different at this point
                            e = [pathdict objectEnumerator];
                            while ((localdict = [e nextObject]) != nil)
                            {
                                name = [localdict valueForKey:@"filename"];
                                if ([name isEqualToString:filename])
                                {
                                    // handle collision
                                    filename = [self disambiguateFilename:filename];
                                    // start over - check again!
                                    e = [pathdict objectEnumerator];
                                }
                            }
                            // set up the path to have a representation, by default its last component (e. g. tiger.jpg)
                            [pathdict setValue:
                              [NSMutableDictionary dictionaryWithObjectsAndKeys:filename, @"filename",
                                [NSNumber numberWithBool:NO], @"storedToDisk",
                                [effectStack filterLayer:i imageFileDataValueForKey:key], @"data", nil]
                              forKey:path];
                        }
                    }
                }
            }
        }
    }
    // make two structures: image file dictionary and layer array
    filedict = [NSMutableDictionary dictionaryWithCapacity:10];
    layerarray = [NSMutableArray arrayWithCapacity:count];
    [filedict setValue:layerarray forKey:@"layers"];
    NSRect fr = [[windowController window] frame];
    [filedict setValue:[NSNumber numberWithDouble:fr.size.width] forKey:@"windowWidth"];
    [filedict setValue:[NSNumber numberWithDouble:fr.size.height] forKey:@"windowHeight"];
    lastImageNumber = 0;
    // enumerate the document's effect stack layers
    for (i = 0; i < count; i++)
    {
        // create a dictionary for this layer and add it to the layer array
        layerdict = [NSMutableDictionary dictionaryWithCapacity:10];
        [layerarray addObject:layerdict];
        // dispatch on layer type
        type = [effectStack typeAtIndex:i];
        if ([type isEqualToString:@"image"])
        {
            // image layer
            // fill dictionary for image layer
            [layerdict setValue:@"image" forKey:@"type"];
            offset = [effectStack offsetAtIndex:i];
            [layerdict setValue:[NSNumber numberWithDouble:offset.x] forKey:@"offsetX"];
            [layerdict setValue:[NSNumber numberWithDouble:offset.y] forKey:@"offsetY"];
            // create identifier for image
            path = [effectStack imageFilePathAtIndex:i];
            // decide if this file has yet been output
            localdict = [pathdict valueForKey:path];
            // get the filename within the bundle
            file = [localdict valueForKey:@"filename"];
            // we only store it if it hasn't yet been stored
            if (![[localdict valueForKey:@"storedToDisk"] boolValue])
            {
                // mark it stored
                [localdict setValue:[NSNumber numberWithBool:YES] forKey:@"storedToDisk"];
                // get original file as data
                //d = [NSData dataWithContentsOfFile:path];
                d = [localdict valueForKey:@"data"];
                // add the file to the file wrapper, given the NSData for the file
                [fw addRegularFileWithContents:d preferredFilename:file];
            }
            // bind it to the layer dictionary
            [layerdict setValue:file forKey:@"file"];
        }
        else if ([type isEqualToString:@"text"])
        {
            // text layer
            // fill dictionary for image layer
            [layerdict setValue:@"text" forKey:@"type"];
            dict = [effectStack mutableDictionaryAtIndex:i];
            [layerdict setValue:[dict valueForKey:@"string"] forKey:@"string"];
            [layerdict setValue:[dict valueForKey:@"scale"] forKey:@"scale"];
            [layerdict setValue:[dict valueForKey:@"offsetX"] forKey:@"offsetX"];
            [layerdict setValue:[dict valueForKey:@"offsetY"] forKey:@"offsetY"];
            [layerdict setValue:[dict valueForKey:@"font"] forKey:@"font"];
            [layerdict setValue:[dict valueForKey:@"pointSize"] forKey:@"pointSize"];
            [layerdict setValue:[dict valueForKey:@"colorRed"] forKey:@"colorRed"];
            [layerdict setValue:[dict valueForKey:@"colorGreen"] forKey:@"colorGreen"];
            [layerdict setValue:[dict valueForKey:@"colorBlue"] forKey:@"colorBlue"];
            [layerdict setValue:[dict valueForKey:@"colorAlpha"] forKey:@"colorAlpha"];
            [layerdict setValue:[dict valueForKey:@"shadowColorRed"] forKey:@"shadowColorRed"];
            [layerdict setValue:[dict valueForKey:@"shadowColorGreen"] forKey:@"shadowColorGreen"];
            [layerdict setValue:[dict valueForKey:@"shadowColorBlue"] forKey:@"shadowColorBlue"];
            [layerdict setValue:[dict valueForKey:@"shadowColorAlpha"] forKey:@"shadowColorAlpha"];
            [layerdict setValue:[dict valueForKey:@"shadowBlurRadius"] forKey:@"shadowBlurRadius"];
            [layerdict setValue:[dict valueForKey:@"shadowBlurOffsetX"] forKey:@"shadowBlurOffsetX"];
            [layerdict setValue:[dict valueForKey:@"shadowBlurOffsetY"] forKey:@"shadowBlurOffsetY"];
            [layerdict setValue:[dict valueForKey:@"strikeThroughStyle"] forKey:@"strikeThroughStyle"];
            [layerdict setValue:[dict valueForKey:@"underlineStyle"] forKey:@"underlineStyle"];
        }
        else if ([type isEqualToString:@"filter"])
        {
            // filter layer
            // fill dictionary for filter layer
            filter = [effectStack filterAtIndex:i];
            [layerdict setValue:@"filter" forKey:@"type"];
            attr = [filter attributes];
            inputKeys = [filter inputKeys];
            // decide if the filter has a background (it's a blend mode)
            enumerator = [inputKeys objectEnumerator];
            hasBackground = NO;
            while ((key = [enumerator nextObject]) != nil) 
            {
                if ([key isEqualToString:@"inputBackgroundImage"])
                {
                    hasBackground = YES;
                    break;
                }
            }
            [layerdict setValue:[attr valueForKey:kCIAttributeFilterName] forKey:@"classname"];
            values = [NSMutableDictionary dictionaryWithCapacity:10];
            [layerdict setValue:values forKey:@"values"];
            // now inspect all the input keys for the filter
            enumerator = [inputKeys objectEnumerator];
            while ((key = [enumerator nextObject]) != nil) 
            {
                parameter = [attr objectForKey:key];
                obj = [filter valueForKey:key];
                if (obj != nil)
                {
                    if ([obj isKindOfClass:[CIImage class]])
                    {
                        // decide whether to skip this key (it gets filled in by something above it in the effect stack)
                        if (hasBackground && [key isEqualToString:@"inputBackgroundImage"])
                            continue; // a blend mode; we chain on inputBackgroundImage
                        if (!hasBackground && [key isEqualToString:@"inputImage"])
                            continue; // not a blend mode; we chain on inputImage
                        // we have an image that fills in a filter image parameter
                        // create identifier for image
                        path = [effectStack filterLayer:i imageFilePathValueForKey:key];
                        localdict = [pathdict valueForKey:path];
                        // get the filename within the bundle
                        file = [localdict valueForKey:@"filename"];
                        // we only store it if it hasn't yet been stored
                        if (![[localdict valueForKey:@"storedToDisk"] boolValue])
                        {
                            // mark it stored
                            [localdict setValue:[NSNumber numberWithBool:YES] forKey:@"storedToDisk"];
                            // get original file as data
                            // d = [NSData dataWithContentsOfFile:path];
                            d = [localdict valueForKey:@"data"];
                            // add the file to the file wrapper, given the NSData for the file
                            [fw addRegularFileWithContents:d preferredFilename:file];
                        }
                        // bind the filename within the bundle to the filter's key
                        [values setValue:file forKey:key];
                    }
                    else
                        // if not an image, do a standard encoding into XML-compatible items
                        [effectStack encodeValue:obj forKey:key intoDictionary:values];
                }
            }
        }
    }
    // now create the top level XML file (as data, of course)
    xmlData = [NSPropertyListSerialization dataFromPropertyList:filedict format:NSPropertyListXMLFormat_v1_0
      errorDescription:&error];
    if (xmlData == nil)
    {
        NSLog(@"%@", error);
        [error release];
        *outError = [NSError errorWithDomain:@"fun house errors" code:-10106
          userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"problems writing xml file for preset save", NSLocalizedDescriptionKey, nil]];
        return nil;
    }
    // add the XML file (in NSData form) to the file wrapper
    [fw addRegularFileWithContents:xmlData preferredFilename:@"file.xml"];
    *outError = nil;
    // and the file wrapper's ready to store onto disk
    return fw;
}

// this is the high-level method that produces the NSFileWrapper for JPEG, TIFF, or fun house preset files
- (NSFileWrapper *)fileWrapperOfType:(NSString *)typeName error:(NSError **)outError
{
    if ([typeName isEqualToString:@"JPEG File"])
        return [[NSFileWrapper alloc] initRegularFileWithContents:[self jpegData:outError]];
    if ([typeName isEqualToString:@"TIFF File"])
        return [[NSFileWrapper alloc] initRegularFileWithContents:[self tiffData:outError]];
    if ([typeName isEqualToString:@"Fun House Preset"])
        return [self fileWrapperForPreset:outError];
    *outError = [NSError errorWithDomain:@"fun house errors" code:-10103
      userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"unrecognized file type for file save", NSLocalizedDescriptionKey, nil]];
    return nil;
}

// this handles the read of a plain image file - it sets up the base image of the effect stack
- (BOOL)readFromData:(NSData *)data ofType:(NSString *)aType error:(NSError **)outError
{
    CIImage *im;
    CGImageSourceRef ref;
    CGImageRef image;
    
    // use Core Image to convert the data - it uses the ImageIO library
    // get color space of image data
    if ([aType isEqualToString:@"OpenEXR File"])
        ref = CGImageSourceCreateWithURL((CFURLRef)[self fileURL], nil);
    else
        ref = CGImageSourceCreateWithData((CFDataRef)data, nil);
    image = CGImageSourceCreateImageAtIndex(ref, 0, (CFDictionaryRef)[NSDictionary dictionaryWithObjectsAndKeys:(id)kCFBooleanTrue,
      (NSString *)kCGImageSourceShouldAllowFloat, nil]);
    colorspace = CGImageGetColorSpace(image);
    if(colorspace)
		CFRetain(colorspace);
    CFRelease(ref);
    if ([aType isEqualToString:@"OpenEXR File"])
        im = [CIImage imageWithCGImage:image];
    else
        im = [CIImage imageWithData:data];
    if (image)
		CFRelease(image);
    [effectStack setBaseImage:im withFilename:[[[self fileURL] path] lastPathComponent] andImageFilePath:[[self fileURL] path]];
    return YES;
}

// this is the high-level method required by the NSDocument subclass that reads in the document from an NSFileWrapper
- (BOOL)readFromFileWrapper:(NSFileWrapper *)fileWrapper ofType:(NSString *)typeName error:(NSError **)outError
{
    BOOL b;
    
    if ([fileWrapper isRegularFile])
        b = [self readFromData:[fileWrapper regularFileContents] ofType:typeName error:outError];
    else
        b = [self openPreset:fileWrapper error:outError];
    [[self undoManager] removeAllActions];
    return b;
}

// we implement this method in the subclass so that we can call removeAllActions (for undo)
- (void)updateChangeCount:(NSDocumentChangeType)change
{
    // This clears the undo stack whenever we load or save.
    [super updateChangeCount:change];
    if (change == NSChangeCleared)
        [[self undoManager] removeAllActions];
}

// return this document's effect stack
- (EffectStack *)effectStack
{
    return effectStack;
}

// this reads in a fun house preset from an NSFileWrapper
- (BOOL)openPreset:(NSFileWrapper *)fileWrapper error:(NSError **)outError
{
    BOOL hasBackground;
    NSInteger i, count;
    NSDictionary *fw;
    NSData *xmldata;
    NSDictionary *filedict, *layerdict, *values, *attr, *parameter;
    NSArray *layers, *inputKeys;
    NSString *type, *file, *classname, *error, *key, *classstring, *path;
    NSPoint offset;
    NSNumber *num;
    CIImage *im;
    CIFilter *filter;
    NSPropertyListFormat format;
    NSEnumerator *enumerator;
    NSMutableDictionary *dict;
    
    // note: fileWrapper must be a dictionary by here
    fw = [fileWrapper fileWrappers];
    // unpack dictionary
    xmldata = [[fw valueForKey:@"file.xml"] regularFileContents];
    [effectStack removeAllLayers];
    // read into internal data structure
    filedict = [NSPropertyListSerialization propertyListFromData:xmldata
      mutabilityOption:kCFPropertyListImmutable format:&format errorDescription:&error];
    if (filedict == nil)
        {
        *outError = [NSError errorWithDomain:@"fun house errors" code:-10105
          userInfo:[NSDictionary dictionaryWithObjectsAndKeys:error, NSLocalizedDescriptionKey, nil]];
        [error release];
        return NO;
        }
    // now unpack file dictionary
    num = [filedict valueForKey:@"windowWidth"];
    if (num == nil)
        hasWindowDimensions = NO;
    else
    {
        hasWindowDimensions = YES;
        wdWidth = [num doubleValue];
        wdHeight = [[filedict valueForKey:@"windowHeight"] doubleValue];
    }
    layers = [filedict valueForKey:@"layers"];
    count = [layers count];
    for (i = 0; i < count; i++)
    {
        layerdict = [layers objectAtIndex:i];
        type = [layerdict valueForKey:@"type"];
        if ([type isEqualToString:@"image"])
        {
            // an image layer
            offset = NSMakePoint([[layerdict valueForKey:@"offsetX"] doubleValue], [[layerdict valueForKey:@"offsetY"] doubleValue]);
            file = [layerdict valueForKey:@"file"];
            path = [[[[self fileURL] path] stringByAppendingString:@"/"] stringByAppendingString:file];
            im = [[[CIImage alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path]] autorelease];
            // and insert the image layer into the effect stack
            [effectStack insertImageLayer:im withFilename:file atIndex:i];
            [effectStack setImageLayer:i offset:offset];
            [effectStack setImageLayer:i imageFilePath:path];
        }
        else if ([type isEqualToString:@"text"])
        {
            // add the text layer to the effect stack
            [effectStack insertTextLayer:[layerdict valueForKey:@"string"] withImage:nil atIndex:i];
            // rebuild the effect stack text layer's properties from the dictionary format saved to the XML file
            dict = [effectStack mutableDictionaryAtIndex:i];
            [dict setValue:[layerdict valueForKey:@"offsetX"] forKey:@"offsetX"];
            [dict setValue:[layerdict valueForKey:@"offsetY"] forKey:@"offsetY"];
            [dict setValue:[layerdict valueForKey:@"scale"] forKey:@"scale"];
            [dict setValue:[layerdict valueForKey:@"font"] forKey:@"font"];
            [dict setValue:[layerdict valueForKey:@"pointSize"] forKey:@"pointSize"];
            [dict setValue:[layerdict valueForKey:@"colorRed"] forKey:@"colorRed"];
            [dict setValue:[layerdict valueForKey:@"colorGreen"] forKey:@"colorGreen"];
            [dict setValue:[layerdict valueForKey:@"colorBlue"] forKey:@"colorBlue"];
            [dict setValue:[layerdict valueForKey:@"colorAlpha"] forKey:@"colorAlpha"];
            [dict setValue:[layerdict valueForKey:@"shadowColorRed"] forKey:@"shadowColorRed"];
            [dict setValue:[layerdict valueForKey:@"shadowColorGreen"] forKey:@"shadowColorGreen"];
            [dict setValue:[layerdict valueForKey:@"shadowColorBlue"] forKey:@"shadowColorBlue"];
            [dict setValue:[layerdict valueForKey:@"shadowColorAlpha"] forKey:@"shadowColorAlpha"];
            [dict setValue:[layerdict valueForKey:@"shadowBlurRadius"] forKey:@"shadowBlurRadius"];
            [dict setValue:[layerdict valueForKey:@"shadowBlurOffsetX"] forKey:@"shadowBlurOffsetX"];
            [dict setValue:[layerdict valueForKey:@"shadowBlurOffsetY"] forKey:@"shadowBlurOffsetY"];
            [dict setValue:[layerdict valueForKey:@"strikeThroughStyle"] forKey:@"strikeThroughStyle"];
            [dict setValue:[layerdict valueForKey:@"underlineStyle"] forKey:@"underlineStyle"];
        }
        else if ([type isEqualToString:@"filter"])
        {
            // filter layer
            classname = [layerdict valueForKey:@"classname"];
            values = [layerdict valueForKey:@"values"];
            filter = [CIFilter filterWithName:classname];
            // add the filter to the effect stack
            [effectStack insertFilterLayer:filter atIndex:i];
            // get filter keys
            attr = [filter attributes];
            inputKeys = [filter inputKeys];
            // decide if the filter has a background (it's a blend mode)
            enumerator = [inputKeys objectEnumerator];
            hasBackground = NO;
            while ((key = [enumerator nextObject]) != nil) 
            {
                if ([key isEqualToString:@"inputBackgroundImage"])
                    hasBackground = YES;
            }
            // now inspect all the input keys for the filter
            enumerator = [inputKeys objectEnumerator];
            while ((key = [enumerator nextObject]) != nil) 
            {
                parameter = [attr objectForKey:key];
                // decide whether to skip this key
                // we skip it if it's an image parameter that's provided by evaluation of the effect stack (a chained image from above)
                if (hasBackground && [key isEqualToString:@"inputBackgroundImage"])
                    continue; // a blend mode; we chain on inputBackgroundImage
                if (!hasBackground && [key isEqualToString:@"inputImage"])
                    continue; // not a blend mode; we chain on inputImage
                // otherwise try to unpack this key's value from values dictionary
                classstring = [parameter objectForKey:kCIAttributeClass];
                if ([classstring isEqualToString:@"CIImage"])
                {
                    // filter image parameter
                    file = [values valueForKey:key];
                    path = [[[[self fileURL] path] stringByAppendingString:@"/"] stringByAppendingString:file];
                    im = [[[CIImage alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path]] autorelease];
                    // and set up the filter's image value
                    [filter setValue:im forKey:key];
                    [effectStack setFilterLayer:i imageFilePathValue:[[[[self fileURL] path] stringByAppendingString:@"/"] stringByAppendingString:file]
                      forKey:key];
                }
                else
                    // set the filter's value (must decode complex parameters line CIVector, CIColor, etc.)
                    [filter setValue:[effectStack decodedValueForKey:key ofClass:classstring fromDictionary:values] forKey:key];
            }
        }
    }
return YES;
}

// handle revert - basically do what NSDocument does, except that we have to update the effect stack to correspond
- (IBAction)revertDocumentToSaved:(id)sender
{
    [super revertDocumentToSaved:sender];
    // redo the effect stack to correspond
    [[EffectStackController sharedEffectStackController] updateLayout];
}

// this handles the zoom to full screen menu item
- (IBAction)zoomToFullScreenAction:(id)sender
{
    CoreImageView *v;
    
    if (fullScreen)
    {
        // zoom away from full screen mode
        // put the menu bar back
        SetSystemUIMode(kUIModeNormal, 0);
        // close the full screen window and its controller
        [fullScreenController close];
        fullScreen = NO;
        // reset the core image view for the effect stack controller
        v = [windowController coreImageView];
        [[EffectStackController sharedEffectStackController] setCoreImageView:v];
        // and update the effect stack controller
        [[EffectStackController sharedEffectStackController] updateLayout];
        // finally redisplay the window
        [v setNeedsDisplay:YES];
    }
    else
    {
        // zoom into full screen mode
        // add a new window controller, and thus a new window
        fullScreenController = [[FunHouseWindowController allocWithZone:[self zone]] initFullScreen];
        [self addWindowController:fullScreenController];
        // set the window up properly to be a full screen window (check out FunHouseWindowController.m)
        [fullScreenController prepFullScreenWindow];
        v = [fullScreenController coreImageView];
        // release it now that it's owned by the document
        [fullScreenController release];
        fullScreen = YES;
        // point the effect stack controller to the right view in the right window
        [[EffectStackController sharedEffectStackController] setCoreImageView:v];
        // update the effect stack controller
        [[EffectStackController sharedEffectStackController] updateLayout];
        // recompute the view transform
        // note: the base image is zoomed to fit the full screen window
        if ([[effectStack typeAtIndex:0] isEqualToString:@"image"])
        {
            CGFloat scale, xscale, yscale, offsetX, offsetY;
            CGSize imagesize;
            NSSize screensize;
            
            // compute size of image
            imagesize = [[effectStack imageAtIndex:0] extent].size;
            // compute size of full screen window
            screensize = [[NSScreen mainScreen] frame].size;
            // decide scale factor now
            xscale = screensize.width / imagesize.width;
            yscale = screensize.height / imagesize.height;
            if (yscale < xscale)
            {
                scale = yscale;
                offsetX = (screensize.width - imagesize.width * scale) * 0.5;
                offsetY = 0.0;
                
            }
            else
            {
                scale = xscale;
                offsetX = 0.0;
                offsetY = (screensize.height - imagesize.height * scale) * 0.5;
            }
            
            // set the view transform for the core image view
            [v setViewTransformScale:scale];
            [v setViewTransformOffsetX:offsetX andY:offsetY];
        }
        // and call for a redisplay
        [v setNeedsDisplay:YES];
    }
    // finally, set up the menu item to reflect the current state (actually, to reflect what the menu item will do)
    [((FunHouseApplication *)NSApp) setFullScreenMenuTitle:(BOOL)fullScreen];
}

// handle the undo command
- (void)undo
{
    CoreImageView *v;
    
    // undo (resets the object state)
    [[self undoManager] undo];
    // get the right core image view pointer
    if (!fullScreen)
        v = [windowController coreImageView];
    else
        v = [fullScreenController coreImageView];
    // update the effect stack controller
    [[EffectStackController sharedEffectStackController] updateLayout];
    // redisplay the core image view
    [v setNeedsDisplay:YES];
}

// handle the redo command
- (void)redo
{
    CoreImageView *v;
    
    // redo (sets the object state)
    [[self undoManager] redo];
    // get the right core image view pointer
    if (!fullScreen)
        v = [windowController coreImageView];
    else
        v = [fullScreenController coreImageView];
    // update the effect stack controller
    [[EffectStackController sharedEffectStackController] updateLayout];
    // redisplay the core image view
    [v setNeedsDisplay:YES];
}

- (void)reconfigureWindowToSize:(NSSize)size andPath:(NSString *)path
{
    [self setFileURL:[NSURL fileURLWithPath:path]];
    [windowController configureToSize:size andFilename:[path lastPathComponent]];
}

- (BOOL)hasWindowDimensions
{
    return hasWindowDimensions;
}

- (CGFloat)windowWidth
{
    return wdWidth;
}

- (CGFloat)windowHeight
{
    return wdHeight;
}

@end
