#import "NSPropertyListSerialization+MullePlistReader.h"
#include <ctype.h>


#define TRY_VALIANTLY_TO_CONVERT  0


@implementation NSPropertyListSerialization ( MullePlistReader)


/* this is really very, very old code, that has been resurrected */

#define TRY_VALIANTLY_TO_CONVERT  0

struct parse_context
{
   char         *s;
   NSUInteger   len;
};


static inline int   iswhite( int c)
{
   return( c == ' ' || c == '\n' || c == '\r' || c == '\t');
}


static inline int   hexify( int c)
{
   if( c >= '0' && c <= '9')
      return( c - '0');
   if( c >= 'a' && c <= 'f')
      return( c - 'A' + 10);
   if( c >= 'A' && c <= 'F')
      return( c - 'A' + 10);
   return( -1);
}



static id  _parse_object( struct parse_context *p);

static int   skip_white( struct parse_context *p)
{
   while( p->len)
   {
      if( ! iswhite( *p->s))
         return( 0);
      
      ++p->s;
      --p->len;
   }
   return( -1);
}

/*
 static NSString  p->slurp_identifier( char *p->s, unsigned int p->len)
 {
 int    c;
 char   *memo;
 
 memo = p->s;
 while( p->len)
 {
 c = *p->s;
 if( ! (isalnum( c) || c == '_'))
 break;
 ++p->s;
 --p->len;
 }
 return( [NSString stringWithCString:memo
 length:p->s - memo]);
 }
 */

static id   _parse_value( struct parse_context *p)
{
   int        c;
   int        d;
   char       *memo;
   int        quoted;
   NSString   *s;
   
   quoted = *p->s == '"';
   if( quoted)
   {
      ++p->s;
      --p->len;
   }
   
   c    = 0;
   memo = p->s;
   while( p->len)
   {
      d = c;
      c = *p->s;

      if( quoted)
      {
         if( c == '"' && d != '\\')
            break;
      }
      else
      {
         if( iswhite( c) || c == ',' || c == ';' || c == ')' || c == '}')
            break;
      }
      ++p->s;
      --p->len;
   }
   
   s = [NSString stringWithCString:memo
                            length:p->s - memo];
   if( quoted)
   {
      ++p->s;
      --p->len;
   }
   
   // brute force, try NSCalendarDate first
   // then NSDecimalNumber Number
   // finally stay as NSString
#if TRY_VALIANTLY_TO_CONVERT
   if( isdigit( *memo))
   {
      if( value = [NSDecimalNumber decimalNumberWithString:string])
         return( value);
   }
   
   if( value = [NSCalendarDate dateWithString:string])
      return( value);
#endif
   
   return( s);
}


static int   _parse_key_value_into_dictionary( struct parse_context *p, NSMutableDictionary *dictionary)
{
   id   key;
   id   value;
   
   key = _parse_value( p);
   if( ! key)
      return( -1);
   
   if( skip_white( p))
   {
      NSLog( @"Malformed key value pair, only key %@ present", key);
      return( -2);
   }
   
   if( *p->s != '=')
   {
      NSLog( @"Malformed key value pair, = missing after key %@", key);
      return( -3);
   }
   
   ++p->s;
   --p->len;
   
   if( skip_white( p))
   {
      NSLog( @"Malformed key value pair, missing value after key %@", key);
      return( -4);
   }
   
   value = _parse_object( p);
   if( ! value)
   {
      NSLog( @"Malformed key value pair, missing value after key %@", key);
      return( -4);
   }

   [dictionary setObject:value
                  forKey:key];
   
   if( ! skip_white( p) && p->len)
   {
      if( *p->s == ';')
      {
         ++p->s;
         --p->len;
      }
   }
   
   return( 0);
}


static id   _parse_array( struct parse_context *p)
{
   NSMutableArray   *array;
   id               parsed;
   
   array = [NSMutableArray array];
   while( p->len)
   {
      if( skip_white( p))
         return( nil);
      
      if( *p->s == ',')
      {
         ++p->s;
         --p->len;
         continue;
      }
      
      if( *p->s == ')')
      {
         ++p->s;
         --p->len;
         return( array);
      }
      
      parsed = _parse_object( p);
      if( ! parsed)
         break;

      [array addObject:parsed];
   }
   NSLog( @"Array truncated");
   return( nil);
}


static id   _parse_data( struct parse_context *p)
{
   NSMutableData   *data;
   unsigned char   byte;
   int             c, d;
   
   data = [NSMutableData data];
   while( p->len)
   {
      if( skip_white( p))
         return( nil);
      
      if( *p->s == '>')
      {
         ++p->s;
         --p->len;
         return( data);
      }
      
      if( p->len < 2)
         break;
      
      c = hexify( p->s[ 0]);
      d = hexify( p->s[ 1]);
      if( c < 0 || d < 0)
         break;
      
      byte = ((unsigned char) c << 4) | (unsigned char) d;

      p->len -= 2;
      p->s   += 2;
      
      // terribly inefficient
      [data appendBytes:&byte
                 length:byte];
   }
   NSLog( @"NSData truncated");
   return( nil);
}

static id   _parse_dictionary( struct parse_context *p)
{
   NSMutableDictionary   *dictionary;
   
   dictionary = [NSMutableDictionary dictionary];
   while( p->len)
   {
      if( skip_white( p))
         break;
      
      if( *p->s == '}')
      {
         ++p->s;
         --p->len;
         return( dictionary);
      }
      
      if( _parse_key_value_into_dictionary( p, dictionary) < 0)
         return( nil);
   }
   NSLog( @"Dictionary truncated");
   return( nil);
}


static id  _parse_object( struct parse_context *p)
{
   if( skip_white( p))
      return( nil);
   
   switch( *p->s)
   {
   case '(' :
      ++p->s;
      --p->len;
      return( _parse_array( p));

   case '<' :
      ++p->s;
      --p->len;
      return( _parse_data( p));

   case '{' :
      ++p->s;
      --p->len;
      return( _parse_dictionary( p));
      
   }
   return( _parse_value( p));
}


static id    parse_old_plist( NSData *data)
{
   struct parse_context   ctxt;

   ctxt.s   = (char *) [data bytes];
   ctxt.len = [data length];

   return( _parse_object( &ctxt));
}


+ (id) mullePropertyListFromData:(NSData *) data
                mutabilityOption:(NSPropertyListMutabilityOptions) option 
                          format:(NSPropertyListFormat *) format
                errorDescription:(NSString **) error
{
   id   list;
   
   list = [NSPropertyListSerialization propertyListFromData:data
                                           mutabilityOption:option 
                                                     format:format 
                                           errorDescription:error];
   if( ! list)
   {
      // can only do immutable containers
      if( option == NSPropertyListImmutable)
      {
         list = parse_old_plist( data);
         if( list)
            *format = NSPropertyListOpenStepFormat;
      }
      // fall through, if this is nil then Foundation
      // will provide the error 
   }
   return( list);
}

@end