//
// File: VLPListDocument.h - Convert document from and to Cocoa plist
//
// Author(s):
//
//      (MN)    Matthias Neeracher
//
// Copyright © 2007-2011 Matthias Neeracher
//

#import "VLPListDocument.h"
#import "VLModel.h"
#import "VLPitchGrid.h"

//
// To convert from and to complex file formats, we use ruby scripts operating
// on the XML representation of a Cocoa property list. The property list 
// representation is strictly intended as an intermediate representation, 
// subject to change as necessary.
//

@implementation VLDocument (Plist) 

class VLPlistVisitor : public VLSongVisitor {
public:
	VLPlistVisitor(NSMutableDictionary * plist, bool performanceOrder)
		: fPlist(plist), fPerfOrder(performanceOrder) {}

	virtual void Visit(VLSong & song);
protected:
	virtual void VisitMeasure(size_t m, VLProperties & p, VLMeasure & meas);
	virtual void VisitNote(VLLyricsNote & n);
	virtual void VisitChord(VLChord & c);
	
	NSArray *		EncodeProperties(const std::vector<VLProperties> & properties);
	NSDictionary *	EncodeProperties(const VLProperties & properties);

	NSMutableDictionary *	fPlist;
	NSMutableArray *		fMeasures;
	NSMutableArray *		fNotes;
	NSMutableArray *		fChords;
    NSMutableArray *        fNotesInTuplet;
	bool					fPerfOrder;
	const VLSong *			fSong;
    VLVisualFilter          fVisFilter;
    int                     fInTuplet;
    uint16_t                fTuplet;
    uint16_t                fTupletNote;
    VLFraction              fTupletDur;
};

NSArray * VLPlistVisitor::EncodeProperties(const std::vector<VLProperties> & properties)
{
	NSMutableArray * pa = [NSMutableArray arrayWithCapacity:properties.size()];

	for (std::vector<VLProperties>::const_iterator i = properties.begin();
		 i != properties.end(); ++i)
		[pa addObject:EncodeProperties(*i)];

	return pa;
}

NSDictionary * VLPlistVisitor::EncodeProperties(const VLProperties & properties)
{
	return [NSDictionary dictionaryWithObjectsAndKeys:
			[NSNumber numberWithInt: properties.fTime.fNum], @"timeNum",	 
			[NSNumber numberWithInt: properties.fTime.fDenom], @"timeDenom",
			[NSNumber numberWithInt: properties.fKey], @"key",
			[NSNumber numberWithInt: properties.fMode], @"mode",
			[NSNumber numberWithInt: properties.fDivisions], @"divisions",
			[NSString stringWithUTF8String:properties.fGroove.c_str()], @"groove",			 
			nil];
}

void VLPlistVisitor::Visit(VLSong & song)
{
	fSong = &song;
	fMeasures = [NSMutableArray arrayWithCapacity:32];
	VisitMeasures(song, fPerfOrder);

	[fPlist setObject:EncodeProperties(song.fProperties) forKey:@"properties"];
	[fPlist setObject:fMeasures forKey:@"measures"];
}

void VLPlistVisitor::VisitMeasure(size_t m, VLProperties & p, VLMeasure & meas)
{
	fNotes = [NSMutableArray arrayWithCapacity:1];
	fChords= [NSMutableArray arrayWithCapacity:1];
    
    fVisFilter.ResetWithKey(p.fKey);
    fInTuplet      = 0;
	VisitNotes(meas, p, true);
	VisitChords(meas);
    if (fInTuplet) 
        [[fNotesInTuplet lastObject] setObject:[NSNumber numberWithInt:-1] forKey:@"tuplet"];

	NSMutableDictionary * md = 
		[NSMutableDictionary dictionaryWithObjectsAndKeys:
		 [NSNumber numberWithUnsignedInt:m], @"measure",
		 [NSNumber numberWithInt:meas.fPropIdx], @"properties",
		 fNotes, @"melody", fChords, @"chords",
		 nil];
	int		times;
	bool 	last;
	size_t	volta;
	if (fSong->DoesBeginRepeat(m, &times)) 
		[md setObject:
		       [NSDictionary dictionaryWithObjectsAndKeys:
				[NSNumber numberWithInt:times], @"times", nil]
			forKey: @"begin-repeat"];
	if (fSong->DoesBeginEnding(m, &last, &volta)) 
		[md setObject:
		       [NSDictionary dictionaryWithObjectsAndKeys:
				[NSNumber numberWithBool:!last], @"last",
				[NSNumber numberWithUnsignedInt:volta], @"volta",
				nil]
			forKey: @"begin-ending"];
	if (fSong->DoesEndRepeat(m+1, &times)) 
		[md setObject:
		       [NSDictionary dictionaryWithObjectsAndKeys:
				[NSNumber numberWithInt:times], @"times", nil]
			forKey: @"end-repeat"];
	if (fSong->DoesEndEnding(m+1, &last, &volta)) 
		[md setObject:
		       [NSDictionary dictionaryWithObjectsAndKeys:
				[NSNumber numberWithBool:!last], @"last",
				[NSNumber numberWithUnsignedInt:volta], @"volta",
				nil]
			forKey: @"end-ending"];
	if (fSong->fGoToCoda == m+1)
		[md setObject:[NSNumber numberWithBool:YES] forKey:@"tocoda"];
	if (fSong->fCoda == m)
		[md setObject:[NSNumber numberWithBool:YES] forKey:@"coda"];
	if (meas.fBreak & VLMeasure::kNewSystem)
		[md setObject:[NSNumber numberWithBool:YES] forKey:@"new-system"];
	if (meas.fBreak & VLMeasure::kNewPage)
		[md setObject:[NSNumber numberWithBool:YES] forKey:@"new-page"];
	[fMeasures addObject:md];
}

void VLPlistVisitor::VisitNote(VLLyricsNote & n)
{
	NSMutableArray * ly = [NSMutableArray arrayWithCapacity:0];
	for (size_t i = 0; i<n.fLyrics.size(); ++i)
		[ly addObject:n.fLyrics[i].fText.size() 
		  ? [NSDictionary dictionaryWithObjectsAndKeys:
			 [NSString stringWithUTF8String:n.fLyrics[i].fText.c_str()], @"text",
			 [NSNumber numberWithInt:n.fLyrics[i].fKind], @"kind",
			 nil]
		  : [NSDictionary dictionary]];
    
    int grid = n.fPitch==VLNote::kNoPitch ? 0 : VLPitchToGrid(n.fPitch, n.fVisual, 0);
	NSMutableDictionary * nd =
		[NSMutableDictionary dictionaryWithObjectsAndKeys:
		 [NSNumber numberWithInt:n.fDuration.fNum], @"durNum",
		 [NSNumber numberWithInt:n.fDuration.fDenom], @"durDenom",
		 [NSNumber numberWithInt:n.fPitch], @"pitch",
		 [NSNumber numberWithInt:n.fTied], @"tied",
		 [NSNumber numberWithInt:fVisFilter(grid, n.fVisual)], @"visual",
		 ly, @"lyrics",
		 nil];
    if (uint16_t newTuplet = n.fVisual & VLNote::kTupletMask) {
        if (!fInTuplet || newTuplet != fTuplet) {
            if (fInTuplet) {
                [[fNotesInTuplet lastObject] setObject:[NSNumber numberWithInt:-1] forKey:@"tuplet"];
                fInTuplet = 0;
            }
            fNotesInTuplet  = [NSMutableArray array];
            fTuplet         = newTuplet;
            fTupletNote     = n.fVisual & VLNote::kNoteHeadMask;
            fTupletDur      = 0;
            [nd setObject:[NSNumber numberWithInt:1] forKey:@"tuplet"];
        }
        int tupletNum   = VLNote::TupletNum(fTuplet);
        int tupletDenom = VLNote::TupletDenom(fTuplet);
        [nd setObject:[NSNumber numberWithInt:tupletNum] forKey:@"actualNotes"];
        [nd setObject:[NSNumber numberWithInt:tupletDenom] forKey:@"normalNotes"];
        ++fInTuplet;
        fTupletDur  += n.fDuration;
        if (fTuplet == VLNote::kTriplet) {
            uint16_t newNote = n.fVisual & VLNote::kNoteHeadMask;
            if (newNote == fTupletNote) {
                if (fInTuplet == 3)
                    fInTuplet = 0;
            } else if (fInTuplet == 2 && newNote == fTupletNote-1) {
                //
                // 8th, 4th triplet
                //
                [nd setObject:[NSNumber numberWithInt:fTupletNote] forKey:@"normalType"];
                fInTuplet = 0;
            } else if (fInTuplet == 3 && newNote == fTupletNote-1) {
                //
                // 8th 8th 4th
                //
                for (NSMutableDictionary * prevNote in fNotesInTuplet) {
                    [prevNote setObject:[NSNumber numberWithInt:newNote] forKey:@"normalType"];
                }
                fInTuplet   = 2;
                fTupletNote = newNote;
            } else {
                //
                // 4th 4th 8th 
                //
                [nd setObject:[NSNumber numberWithInt:fTupletNote] forKey:@"normalType"];
                if (fTupletDur.fNum == 1 && !(fTupletDur.fDenom&(fTupletDur.fDenom-1)))
                    fInTuplet = 0;
            }
        } else if (fInTuplet == tupletNum) 
            fInTuplet   = 0;
        if (!fInTuplet)
            [nd setObject:[NSNumber numberWithInt:-1] forKey:@"tuplet"];
        else    
             [fNotesInTuplet addObject:nd];        
    } else if (fInTuplet) {
        [[fNotesInTuplet lastObject] setObject:[NSNumber numberWithInt:-1] forKey:@"tuplet"];
        fInTuplet = 0;
    }
	[fNotes addObject:nd];
}

void VLPlistVisitor::VisitChord(VLChord & c)
{
    int             pitchGrid = 
        c.fPitch==VLNote::kNoPitch ? 0 : VLPitchToGrid(c.fPitch, c.fVisual, 0);
    VLVisualFilter  pitchFilter(0);
    int             rootGrid  = 
        c.fRootPitch==VLNote::kNoPitch ? 0 : VLPitchToGrid(c.fRootPitch, c.fRootAccidental, 0);
    VLVisualFilter  rootFilter(0);
	NSDictionary * cd = 
		[NSDictionary dictionaryWithObjectsAndKeys:
		 [NSNumber numberWithInt:c.fDuration.fNum], @"durNum",
		 [NSNumber numberWithInt:c.fDuration.fDenom], @"durDenom",
		 [NSNumber numberWithInt:c.fPitch], @"pitch",
		 [NSNumber numberWithInt:pitchFilter(pitchGrid, c.fVisual)], @"visual",
		 [NSNumber numberWithInt:c.fSteps], @"steps",
		 [NSNumber numberWithInt:c.fRootPitch], @"root",
         [NSNumber numberWithInt:rootFilter(rootGrid, c.fRootAccidental)], @"rootvisual",
		 nil];
	[fChords addObject: cd];
}

- (id)plistInPerformanceOrder:(BOOL)performanceOrder
{
	NSMutableDictionary *	plist = 
		[NSMutableDictionary dictionaryWithObjectsAndKeys:
		 songTitle, @"title", [NSNumber numberWithLong:lround(songTempo)], @"tempo",
		 [NSString stringWithUTF8String:song->PrimaryGroove().c_str()], @"groove", 
		 songComposer, @"composer", songLyricist, @"lyricist",
		 [NSDate date], @"saved",
		 [NSString stringWithFormat:@"VocalEasel %@",
				   [[NSBundle mainBundle] 
					   objectForInfoDictionaryKey:@"CFBundleVersion"]],
		 @"software",
		 nil];

	VLPlistVisitor	songWriter(plist, performanceOrder);
	songWriter.Visit(*song);

	return plist;
}

- (IBAction)dump:(id)sender
{	
	id plist = [self plistInPerformanceOrder:NO];
	switch ([sender tag]) {
	case 0:
		//
		// Dump as plist
		//
		NSLog(@"\n%@\n", plist);
		break;
	case 1:
		//
		// Dump as XML
		//
		plist = [[[NSString alloc] initWithData:
					 [NSPropertyListSerialization dataFromPropertyList:plist 
												  format:NSPropertyListXMLFormat_v1_0 errorDescription:nil]
								   encoding:NSUTF8StringEncoding] autorelease];
		NSLog(@"\n%@\n", plist);
		break;	
	case 2:
		//
		// Dump after roundtrip
		//
		[self readFromPlist:plist error:nil];
		plist = [self plistInPerformanceOrder:NO];
		NSLog(@"\n%@\n", plist);
		break;
	}
}

//
// We try to keep the number of divisions as small as possible, so we keep track
// of all note onsets per quarter note. In addition, we keep track of potential
// swing 8ths [0, 1/8]->[0,1/6] and 
// swing 16ths [0,1/16]->[0,1/12] [1/8,3/16]->[1/8,1/6]
// so we can recognize swing songs containing triplets and note them with 3 (6)
// divisions instead of 6 (12)
//
enum {
	kPotentialSwing8th = 12,
	kPotentialSwing16th
};

- (void)readMelody:(NSArray *)melody inMeasure:(size_t)measNo onsets:(int *)onsets lyrics:(uint8_t *)prevKind
{
	VLLocation		at          = {measNo, VLFraction(0)};
	int				lastOnset   = 0;
	VLLocation		tiedStart   = {measNo, VLFraction(0)};
	VLLyricsNote	tiedNote;

	for (NSEnumerator * ne 	  = [melody objectEnumerator];
		 NSDictionary * ndict = [ne nextObject];
	) {	
		VLLyricsNote note;
		note.fDuration = 
			VLFraction([[ndict objectForKey:@"durNum"] intValue],
					   [[ndict objectForKey:@"durDenom"] intValue],
					   true);
		note.fPitch	   = [[ndict objectForKey:@"pitch"] intValue];	
		note.fVisual   = [[ndict objectForKey:@"visual"] intValue]
			& VLNote::kAccidentalsMask;
		note.fTied	   = 0;
        
        if ([ndict objectForKey:@"actualNotes"])
            note.fVisual |= VLNote::Tuplet([[ndict objectForKey:@"actualNotes"] intValue],
                                           [[ndict objectForKey:@"normalNotes"] intValue]);
		
		if ([[ndict objectForKey:@"tied"] intValue] & VLNote::kTiedWithPrev) {
			if (at.fAt != VLFraction(0)) {
				//
				// Extend preceding note
				//
				tiedNote.fDuration	+= note.fDuration;
				song->DelNote(tiedStart);
				song->AddNote(tiedNote, tiedStart);
				
				goto advanceAt;
			} else {
				//
				// Extend previous measure
				//
				note.fTied |= VLNote::kTiedWithPrev;
			}
		} else {
			for (NSEnumerator * le 	  = [[ndict objectForKey:@"lyrics"] objectEnumerator];
				 NSDictionary * ldict = [le nextObject];
			) {	
				VLSyllable syll;

				if (NSString * t = [ldict objectForKey:@"text"])
					syll.fText = [t UTF8String];

				syll.fKind = [[ldict objectForKey:@"kind"] intValue];

				note.fLyrics.push_back(syll);
			}
		}

		//
		// Sanitize syllabic information which was corrupt in early
		// versions.
		//
		for (size_t i = 0; i<note.fLyrics.size(); ++i)
			if (note.fLyrics[i].fText.size()) {
				if (prevKind[i] & VLSyllable::kHasNext)
					note.fLyrics[i].fKind |= VLSyllable::kHasPrev;
                else
                    note.fLyrics[i].fKind &= ~VLSyllable::kHasPrev;
				prevKind[i] = note.fLyrics[i].fKind;
			} else {
                prevKind[i] = 0;
            }

		tiedStart	= at;
		tiedNote	= note;
		
		song->AddNote(note, at);

		if (!(note.fTied & VLNote::kTiedWithPrev)) {
			VLFraction 	inQuarter	= at.fAt % VLFraction(1,4);
			int 		onset 		= inQuarter.fNum * 48 / inQuarter.fDenom;
			++onsets[onset];
			switch (onset) {
			case 3:
				if (lastOnset == 0)
					++onsets[kPotentialSwing16th];
				break;					
			case 6:
				if (lastOnset == 0)
					++onsets[kPotentialSwing8th];
				break;
			case 9:
				if (lastOnset == 6 || lastOnset == 3 || lastOnset == 0)
					++onsets[kPotentialSwing16th];
				break;
			}
		}
advanceAt:
		at.fAt = at.fAt + note.fDuration;		
	}
}

- (void)readChords:(NSArray *)chords inMeasure:(size_t)measNo
{
	VLLocation	at  = {measNo, VLFraction(0)};

	for (NSEnumerator * ce 	  = [chords objectEnumerator];
		 NSDictionary * cdict = [ce nextObject];
	) {	
		VLChord chord;
		chord.fDuration = 
			VLFraction([[cdict objectForKey:@"durNum"] intValue],
					   [[cdict objectForKey:@"durDenom"] intValue],
					   true);
		chord.fPitch			= [[cdict objectForKey:@"pitch"] intValue];	
        chord.fVisual           = [[cdict objectForKey:@"visual"] intValue];
		chord.fRootPitch		= [[cdict objectForKey:@"root"] intValue];	
		chord.fRootAccidental	= [[cdict objectForKey:@"rootvisual"] intValue];	
		chord.fSteps			= [[cdict objectForKey:@"steps"] intValue];	

		song->AddChord(chord, at);
		
		at.fAt = at.fAt + chord.fDuration;
	}
}

- (void)readMeasuresFromPlist:(NSArray *)measures
{
	std::vector<size_t>	repeatStack;

	size_t measNo = 0;
	int onsets[14] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
	uint8_t  		lyricsKind[20];
	memset(lyricsKind, 0, 20*sizeof(lyricsKind[0]));
	for (NSEnumerator * me 	  = [measures objectEnumerator];
		 NSDictionary * mdict = [me nextObject];
		 ++measNo
	) {
		if (NSNumber * mNo = [mdict objectForKey:@"measure"])
			measNo = static_cast<size_t>([mNo intValue]);
		if (NSNumber * mPx = [mdict objectForKey:@"properties"]) 
			song->SetProperties(measNo, [mPx intValue]);

		[self readMelody:[mdict objectForKey:@"melody"] inMeasure:measNo onsets:onsets lyrics:&lyricsKind[0]];
		[self readChords:[mdict objectForKey:@"chords"] inMeasure:measNo];

		if ([[mdict objectForKey:@"tocoda"] boolValue])
			song->fGoToCoda = measNo+1;
		if ([[mdict objectForKey:@"coda"] boolValue])
			song->fCoda = measNo;			
		if ([[mdict objectForKey:@"new-system"] boolValue])
			song->fMeasures[measNo].fBreak |= VLMeasure::kNewSystem;
		if ([[mdict objectForKey:@"new-page"] boolValue])
			song->fMeasures[measNo].fBreak |= VLMeasure::kNewPage;
		
		if (NSDictionary * beginRep = [mdict objectForKey:@"begin-repeat"]) {
			VLRepeat 			rep;
			VLRepeat::Ending	ending(measNo, measNo, 0);

			rep.fTimes	= [[beginRep objectForKey:@"times"] intValue];
			rep.fEndings.push_back(ending);

			repeatStack.push_back(song->fRepeats.size());
			song->fRepeats.push_back(rep);
		}
		if (NSDictionary * beginEnd = [mdict objectForKey:@"begin-ending"]) {
			VLRepeat & 			rep = song->fRepeats[repeatStack.back()];
			VLRepeat::Ending	ending(measNo, measNo, 0);

			ending.fVolta	= [[beginEnd objectForKey:@"volta"] intValue];
			rep.fEndings.push_back(ending);
		}
		if (NSDictionary * endEnd = [mdict objectForKey:@"end-ending"]) {
			VLRepeat & 			rep 	= song->fRepeats[repeatStack.back()];
			VLRepeat::Ending &	ending 	= rep.fEndings.back();

			ending.fEnd = measNo+1;
			if (NSNumber * volta = [endEnd objectForKey:@"volta"])
				ending.fVolta = [volta intValue];
			while ((((1<<rep.fTimes) - 1) & ending.fVolta) < ending.fVolta)
				++rep.fTimes;
			if ([[endEnd objectForKey:@"last"] boolValue]) {
				rep.fEndings[0].fEnd = measNo+1;
				repeatStack.pop_back();
			}
		}
		if (NSDictionary * endRep = [mdict objectForKey:@"end-repeat"]) {
			VLRepeat & 			rep = song->fRepeats[repeatStack.back()];

			if (NSNumber * times = [endRep objectForKey:@"times"])
				rep.fTimes = [times intValue];

			rep.fEndings[0].fEnd 	= measNo+1;
			rep.fEndings[0].fVolta	= (1<<rep.fTimes)-1;

			repeatStack.pop_back();
		}
	}
	size_t empty = song->EmptyEnding();
	while (empty-- > 1)
		song->fMeasures.pop_back();
	if (!song->fProperties.back().fDivisions) {
		if (!(onsets[1]+onsets[5]+onsets[7]+onsets[11]))
			if (!(onsets[3]+onsets[9]-onsets[kPotentialSwing16th]))
				if (onsets[kPotentialSwing16th]) {
					song->fProperties.back().fDivisions = 12;
					song->ChangeDivisions(song->fProperties.size()-1, 6);
				} else if (!(onsets[2]+onsets[4]+onsets[8]+onsets[10])) {
					song->fProperties.back().fDivisions = 2;
				} else if (!(onsets[2]+onsets[10]
						   + onsets[6]-onsets[kPotentialSwing8th])) {
					if (onsets[kPotentialSwing8th]) {
						song->fProperties.back().fDivisions = 6;
						song->ChangeDivisions(song->fProperties.size()-1, 3);
					} else {
						song->fProperties.back().fDivisions = 3;
					}
				} else {
					song->fProperties.back().fDivisions = 6;
		        }
			else if (!(onsets[2]+onsets[4]+onsets[8]+onsets[10]))
				song->fProperties.back().fDivisions = 4;
			else	
				song->fProperties.back().fDivisions = 12;
		else	
			song->fProperties.back().fDivisions = 12;
	}
}

- (void)readPropertiesFromPlist:(NSArray *)properties
{
	song->fProperties.clear();
	for (NSEnumerator * pe = [properties objectEnumerator];
		 NSDictionary * pdict = [pe nextObject];
	) {
		VLProperties prop;

		prop.fTime = 
			VLFraction([[pdict objectForKey:@"timeNum"] intValue],
					   [[pdict objectForKey:@"timeDenom"] intValue],
					   false);
		prop.fKey			= [[pdict objectForKey:@"key"] intValue];
		prop.fMode			= [[pdict objectForKey:@"mode"] intValue];
		prop.fDivisions		= [[pdict objectForKey:@"divisions"] intValue];

		if (NSString * groove = [pdict objectForKey:@"groove"])
			prop.fGroove	= [groove UTF8String];
		else
			prop.fGroove	= [songGroove UTF8String];

		song->fProperties.push_back(prop);
	}
}

- (void)setValueFromPlist:(id)plist plistKey:(NSString *)plistKey forKey:(NSString *)key
{
	id value = [plist objectForKey:plistKey];
	if (value)
		[self setValue:value forKey:key];
}

- (BOOL)readFromPlist:(id)plist error:(NSError **)outError
{
	NSUndoManager * undoMgr = [self undoManager];
	[undoMgr disableUndoRegistration];
	song->clear();

	[self setValueFromPlist:plist plistKey:@"title" forKey:@"songTitle"];
	[self setValueFromPlist:plist plistKey:@"composer" forKey:@"songComposer"];
	[self setValueFromPlist:plist plistKey:@"lyricist" forKey:@"songLyricist"];
	[self setValueFromPlist:plist plistKey:@"groove" forKey:@"songGroove"];
	[self setValueFromPlist:plist plistKey:@"tempo" forKey:@"songTempo"];
	[self readPropertiesFromPlist:[plist objectForKey:@"properties"]];
	[self readMeasuresFromPlist:[plist objectForKey:@"measures"]];
	[undoMgr enableUndoRegistration];
    
    if (song->fMeasures.empty()) {
        delete song;
        song = new VLSong(true);
    }

	return YES;
}

- (NSData *)runFilter:(NSString *)filterName withContents:(NSData *)contents
{
	NSString * filterPath = [[NSBundle bundleForClass:[VLDocument class]] pathForResource:filterName
												   ofType:nil
												   inDirectory:@"Filters"];
	NSPipe * filterInput  = [NSPipe pipe];
	NSPipe * filterOutput = [NSPipe pipe];
	NSPipe * filterError  = [NSPipe pipe];

	NSTask * filterTask	= [[NSTask alloc] init];
	[filterTask setLaunchPath:filterPath];
	[filterTask setStandardInput:filterInput];
	[filterTask setStandardOutput:filterOutput];
	[filterTask setStandardError:filterError];
	[filterTask launch];

	NSFileHandle * inputHandle = [filterInput fileHandleForWriting];
	[inputHandle writeData:contents];
	[inputHandle closeFile];

	NSFileHandle * outputHandle = [filterOutput fileHandleForReading];
	NSData * 	   output		= [outputHandle readDataToEndOfFile];
 	
	NSFileHandle * errorHandle  = [filterError fileHandleForReading];
	NSData * 	   error		= [errorHandle readDataToEndOfFile];
 	
	[filterTask waitUntilExit];
	[filterTask release];

	if ([error length]) {
        [contents writeToFile:@"/var/tmp/VocalEaselFilterInput" atomically:NO];
        [error writeToFile:@"/var/tmp/VocalEaselFilterError" atomically:NO];
		NSString * errStr = [[[NSString alloc] initWithData:error 
			encoding:NSUTF8StringEncoding] autorelease];
		[NSException raise:NSInvalidArgumentException 
					 format:@"Filter %@: %@", filterName, errStr];
	}
	
	return output;
}

- (NSFileWrapper *)fileWrapperWithFilter:(NSString *)filterName
								   error:(NSError **)outError
{
	NSBundle * 	mainBundle = [NSBundle mainBundle];
	BOOL 	 	perfOrder  = [mainBundle pathForResource:filterName	
							  ofType:@"pwriter" inDirectory:@"Filters"] != nil;
	filterName = [filterName stringByAppendingPathExtension:
				  perfOrder ? @"pwriter" : @"writer"];
	id 		 inPlist= [self plistInPerformanceOrder:perfOrder];
	NSData * inData = 
		[NSPropertyListSerialization dataFromPropertyList:inPlist
									 format:NSPropertyListXMLFormat_v1_0 
									 errorDescription:nil];
	NSData * outData= [self runFilter:filterName withContents:inData];
	
	return [[[NSFileWrapper alloc] initRegularFileWithContents:outData]
			autorelease];
}

- (BOOL)readFromFileWrapper:(NSFileWrapper *)wrapper
				 withFilter:(NSString *)filterName
					  error:(NSError **)outError
{
	filterName = [filterName stringByAppendingPathExtension:@"reader"];

	NSData * inData 	= [wrapper regularFileContents];
	NSData * outData	= [self runFilter:filterName withContents:inData];
	NSString*errString;
	id       outPlist	= 
		[NSPropertyListSerialization propertyListFromData:outData
									 mutabilityOption:NSPropertyListImmutable
									 format:NULL errorDescription:&errString];
	if (!outPlist) 
		[NSException raise:NSInvalidArgumentException 
					 format:@"Plist %@: %@", filterName, errString];
	return [self readFromPlist:outPlist error:outError];
}

@end