//
// main.c
// mulle-plist-compress
//
// Created by Nat! on 09.02.13.
// Copyright (c) 2013 Mulle kybernetiK. All rights reserved.
//
// http://github.com/mulle-nat/mulle-plist-from-files
// http://www.mulle-kybernetik.com
// http://www.mulle-kybernetik.com/weblog
//
#import <Foundation/Foundation.h>
#import "NSData+ZLib.h"
#import "NSObject+UnformattedDescription.h"


static char  version[] = "1.0";


static void   hexwrite( void *buf, size_t len, FILE *fp)
{
   unsigned char   *p;
   unsigned char   *sentinel;
   unsigned int    i;
   
   if( !  len)
      return;
   
   p        = buf;
   sentinel = &p[ len];
   
   i = 15;
   for(;;)
   {
      fprintf( fp, "0x%02x", *p++);
      if( p == sentinel)
         break;
      
      fprintf( fp, i ? "," : ",\n");
      i = (i - 1) & 0xF;
   }
}


static NSData  *plist_data_with_style( id plist, int style)
{
   NSData   *data;
   NSError   *error;

   switch( style)
   {
      case 0 :
         data = [[plist unformattedDescription] dataUsingEncoding:NSUTF8StringEncoding];
         break;
         
      case 666 :
         data = [[plist description] dataUsingEncoding:NSUTF8StringEncoding];
         break;
         
      case NSPropertyListOpenStepFormat :
      case NSPropertyListXMLFormat_v1_0 :
      case NSPropertyListBinaryFormat_v1_0 :
         data = [NSPropertyListSerialization dataWithPropertyList:plist
                                                           format:style
                                                          options:NSPropertyListImmutable
                                                            error:&error];
         if( ! data)
         {
            fprintf( stderr, "Conversion to plist failed: %s\n", [[error description] cString]);
            return( nil);
         }
         break;
         
      default :
         fprintf( stderr, "Unknown plist style %d", style);
         return( nil);
   }
   return( data);
}


static NSData  *compressed_data_with_compression_type( NSData *data, int compression)
{
   switch( compression)
   {
      case 1 :  // remove extraneous characters
         data = [data compressedDataUsingZLib];
         if( ! data)
         {
            fprintf( stderr, "Compression of data failed");
            return( nil);
         }
         break;
         
      case 0 :  // pass thru
         break;
         
      default :
         fprintf( stderr, "Unknown compression type %d", compression);
         return( nil);
   }
   return( data);
}


static int   write_data( NSData *data, int output, FILE *stream)
{
   NSString   *s;

   switch( output)
   {
   case 2 :  // create hexes
      hexwrite( (void *) [data bytes], [data length], stream);
      break;
      
   case 0 : // escape everything
      s = [[[NSString alloc] initWithBytes:[data bytes]
                                    length:[data length]
                                  encoding:NSUTF8StringEncoding] autorelease]; // guess !
      s = [s stringByReplacingOccurrencesOfString:@"\""
                                       withString:@"\\\""];
      data = [s dataUsingEncoding:NSUTF8StringEncoding];
      
   case 1 :
      fwrite( [data bytes], [data length], 1, stream);
      break;
         
   default :
      fprintf( stderr, "Unknown plist output format %d", output);
      return( -1);
   }
   
   return( 0);
}


static int   write_plist( id plist, int style, int compression, int output, FILE *stream)
{
   NSData   *data;
   
   data = plist_data_with_style( plist, style);
   if( ! data)
      return( -1);
   
   data = compressed_data_with_compression_type( data, compression);
   if( ! data)
      return( -1);
   
   return( write_data( data, output, stream));
}


static struct
{
   char   *name;
   int    value;
} table[] =
{
   { "default", 0 },

   // style
   { "condensed", 0 },
   { "pretty", 666 },
   { "openstep", NSPropertyListOpenStepFormat },   // don't work
   { "xml", NSPropertyListXMLFormat_v1_0 },
   { "binary", NSPropertyListBinaryFormat_v1_0 },
   
   // compression
   { "none",  0 },
   { "zlib", 1 },

   // output
   { "escaped", 0 },
   { "plain",  1 },
   { "hex", 2 },
   
   { NULL, 0 }
};


static void  usage( char *name)
{
   fprintf( stderr, "%s [-s <style>][-c <compression>][-e <encoding>]\n"
           "\t-s <condensed|pretty|xml|binary>\n"
           "\t-c <none|zlib>\n"
           "\t-e <escaped|plain|hex>\n"
           , name);
   fprintf( stderr, "\tv%s\n", version);
}


static int   intValue( NSString *string)
{
   char  *s;
   int   i;
   
   string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];  // gratuitous
   string = [string lowercaseString];
   
   s = (char *) [string cString];
   for( i = 0;  table[i].name; i++)
      if( ! strcmp( table[ i].name, s))
         return( table[ i].value);

   if( [string rangeOfCharacterFromSet:[NSCharacterSet decimalDigitCharacterSet]].length == [string length])
      return( [string intValue]);

   fprintf( stderr, "Unknown option value %s", [string cString]);
   return( -1);
}


int main(int argc, const char * argv[])
{
   NSArray               *arguments;
   NSAutoreleasePool     *pool;
   NSData                *data;
   NSDictionary          *environment;
   NSEnumerator          *rover;
   NSError               *error;
   NSFileHandle          *handle;
   NSProcessInfo         *processInfo;
   NSPropertyListFormat  format;
   NSString              *footer;
   NSString              *header;
   NSString              *option;
   NSString              *inputFile;
   NSString              *outputFile;
   NSString              *processName;
   NSString              *s;
   id                    plist;
   int                   compression;
   int                   output;
   int                   rval;
   int                   style;
   int                   value;
   unichar               c;
   FILE                  *fout;
   
   pool = [NSAutoreleasePool new];
   
   processInfo = [NSProcessInfo processInfo];
   environment = [processInfo environment];
   arguments   = [processInfo arguments];
   rover       = [arguments objectEnumerator];
   s           = [rover nextObject];
   processName = [s lastPathComponent];

   style       = 0;
   compression = 0;
   output      = 0;
   inputFile   = nil;
   outputFile  = nil;
   
   while( option = [rover nextObject])
   {
      if( ! [option hasPrefix:@"-"])
      {
         if( ! inputFile)
         {
            inputFile = option;
            continue;
         }
         goto usage;
      }
      
      if( [option length] < 2)
         goto usage;
      
      c = [option characterAtIndex:1];
      s = [rover nextObject];
      if( ! [s length])
         goto usage;
      
      value = intValue( s);
      switch( c)
      {
      case 's' : style       = value; break;
      case 'c' : compression = value; break;
      case 'e' : output      = value; break;
      case 'o' : outputFile  = s; break;
      default  : fprintf( stderr, "Unknown argument %s\n", [option cString]);
                 goto usage;
      }
   }
   
   if( inputFile)
   {
      handle = [NSFileHandle fileHandleForReadingAtPath:inputFile];
      if( ! handle)
      {
         perror( "open:");
         goto usage;
      }
   }
   else
      handle = [NSFileHandle fileHandleWithStandardInput];

   fout = stdout;
   if( outputFile)
   {
      fout = fopen( [outputFile cString], "w");
      if( ! fout)
      {
         perror( "open:");
         goto usage;
      }
   }
   data   = [handle readDataToEndOfFile];
   
   error  = nil;
   plist  = [NSPropertyListSerialization propertyListWithData:data
                                                      options:NSPropertyListImmutable
                                                       format:&format
                                                        error:&error];
   if( ! plist)
   {
      fprintf( stderr, "Not a property list: %s\n", [[error description] cString]);
      return( -1);
   }
   
   header = [environment objectForKey:@"MULLE_PLIST_HEADER"];
   if( header)
      fprintf( fout, "%s", [header cString]);
   
   rval = write_plist( plist, style, compression, output, fout);
   if( rval)
      return( rval);
   
   footer = [environment objectForKey:@"MULLE_PLIST_FOOTER"];
   if( footer)
      fprintf( fout, "%s", [footer cString]);
   return( 0);
   
usage:
   usage( (char *) [processName cString]);
   return( -1);
}