#import "NSPropertyListSerialization+MullePlistWriter.h"


NSString *MullePlistWriterException = @"MullePlistWriterException";


@interface NSObject ( MullePlistWriter)

- (void) mullePlistWriteIntoData:(NSMutableData *) data
                          indent:(NSData *) indent;

@end


@implementation NSObject ( MullePlistWriter)

- (void) mullePlistWriteIntoData:(NSMutableData *) data
                          indent:(NSData *) indent
{
   [NSException raise:MullePlistWriterException
               format:@"Objects of class %@ can't be saved in a plist", isa];
}

@end


/*
 *
 *
 */

@interface NSData ( MullePlistWriter)

- (void) mullePlistWriteIntoData:(NSMutableData *) data
                          indent:(NSData *) indent;
@end


static inline char   hexit( unsigned char c)
{
   return( c > 10 ? 'a' - 10 + c : '0' + c);  // znek wants lowercase
}


@implementation NSData ( MullePlistWriter)

- (void) mullePlistWriteIntoData:(NSMutableData *) data
                          indent:(NSData *) indent
{
   NSMutableData   *buffer;
   char            *hex;
   unsigned char   *s;
   unsigned char   *sentinel;
   unsigned char   *spacer;
   NSUInteger      len;
   
   len    = [self length];
   buffer = [NSMutableData dataWithLength:len * 3 + 3];

   hex    = (char *) [buffer bytes];
   *hex++ = '<';
   
   s        = (unsigned char *) [self bytes];
   sentinel = &s[ len];
   spacer   = &s[ 4];

   while( s < sentinel)
   {
      *hex++ = hexit( *s >> 4);
      *hex++ = hexit( *s & 0xF);
      s++;
      
      if( s == spacer && s != sentinel)
      {
         *hex++ = ' ';
         spacer = &s[ 4];
      }
   }
   *hex++ = '>';
   
   len = (char *) hex - (char *) [buffer bytes];
   [buffer setLength:len];
   [data appendBytes:buffer
              length:len];
}

@end

/*
 *
 *
 */

@interface NSNumber ( MullePlistWriter)

- (void) mullePlistWriteIntoData:(NSMutableData *) data
                          indent:(NSData *) indent;
@end


@implementation NSNumber ( MullePlistWriter)

- (void) mullePlistWriteIntoData:(NSMutableData *) data
                          indent:(NSData *) indent
{
   char  *s;

   s = (char *) [[self descriptionWithLocale:nil] UTF8String];
   [data appendBytes:s
              length:strlen( s)];
}

@end

/*
 *
 *
 */


@interface NSString ( MullePlistWriter)

- (void) mullePlistWriteIntoData:(NSMutableData *) data
                          indent:(NSData *) indent;

@end


@implementation NSString ( MullePlistWriter)

- (void) mullePlistWriteIntoData:(NSMutableData *) data
                          indent:(NSData *) indent
{
   char      *s;
   BOOL      quoted;
   NSArray   *components;
   NSString  *quotedSelf;
   
   quoted  = [self rangeOfCharacterFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].length;
   quoted |= [self rangeOfCharacterFromSet:[NSCharacterSet punctuationCharacterSet]].length;
   quoted |= [self length] == 0;
      
   components = [self componentsSeparatedByString:@"\""];
   quotedSelf = [components componentsJoinedByString:@"\\\""];
   quoted |= [components count] > 1;
      
   if( quoted)
      [data appendBytes:"\""
                 length:1];


   s = (char *) [quotedSelf UTF8String];
   [data appendBytes:s
              length:strlen( s)];
   if( quoted)
      [data appendBytes:"\""
                 length:1];
}

@end

/*
 *
 *
 */


@interface NSArray ( MullePlistWriter)

- (void) mullePlistWriteIntoData:(NSMutableData *) data
                          indent:(NSData *) indent;

@end


@implementation NSArray ( MullePlistWriter)

- (void) mullePlistWriteIntoData:(NSMutableData *) data
                          indent:(NSData *) indent
{
   NSMutableData   *indent2;
   NSUInteger       i, n;

   [data appendBytes:"("
              length:1];
   n = [self count];
   if( n)
   {
      [data appendBytes:"\n"
                 length:1];
      indent2 = [[indent mutableCopy] autorelease];
      [indent2 appendBytes:"   "
                    length:3];
      for( i = 0; i < n; i++)
      {
         if( i > 0)
            [data appendBytes:",\n"
                       length:2];
         [data appendData:indent2];
         [[self objectAtIndex:i] mullePlistWriteIntoData:data
                                                  indent:indent2];
      }
      [data appendBytes:"\n"
                 length:1];
      [data appendData:indent];
   }
   [data appendBytes:")"
              length:1];
}


@end

/*
 *
 *
 */


@interface NSDictionary ( MullePlistWriter)

- (void) mullePlistWriteIntoData:(NSMutableData *) data
                          indent:(NSData *) indent;

@end


@implementation NSDictionary ( MullePlistWriter)

- (void) mullePlistWriteIntoData:(NSMutableData *) data
                          indent:(NSData *) indent
{
   NSArray         *keys;
   id              key;
   id              value;
   NSMutableData   *indent2;
   NSUInteger      i, n;

   [data appendBytes:"{"
              length:1];
   keys = [self allKeys];  // sort'em ?
   n    = [keys count];

   if( n)
   {
      [data appendBytes:"\n"
                 length:1];

//      keys    = [keys sortedArrayUsingSelector:@selector( compare:)];
      indent2 = [indent mutableCopy];
      [indent2 appendBytes:"   "
                    length:3];

      for( i = 0; i < n; i++)
      {
         key   = [keys objectAtIndex:i];
         value = [self objectForKey:key];

         [data appendData:indent2];
         [key mullePlistWriteIntoData:data
                               indent:nil];
         [data appendBytes:" = "
                    length:3];
         [value mullePlistWriteIntoData:data
                                 indent:indent2];
         [data appendBytes:";\n"
                    length:2];

      }
      [data appendData:indent];
   }
   [data appendBytes:"}"
              length:1];
}

@end



@implementation NSPropertyListSerialization ( MullePlistWriter)

+ (NSData *) plistDataFromObject:(id) obj
                errorDescription:(NSString **) error
{
   NSMutableData   *data;
   
   if( ! obj)
   {
      *error = @"plist is nil";
      return( nil);
   }
   
   data = [NSMutableData dataWithCapacity:0x1000];
   [obj mullePlistWriteIntoData:data
                         indent:[NSData data]];
   if( ! [data length])
   {
      *error = @"Some objects were not plist compatible";
      return( nil);
   }

#if 0   
   [data appendBytes:""  // trailin zero
              length:1];
#endif   
   return( data);
}


+ (id) mulleDataFromPropertyList:(id) plist
                          format:(NSPropertyListFormat) format
                errorDescription:(NSString **) error
{
   if( format != NSPropertyListOpenStepFormat)
      return( [self dataFromPropertyList:plist
                                  format:format
                         errorDescription:error]);
   
   return( [self plistDataFromObject:plist
                    errorDescription:error]);
}


@end