//
//  CJSONSerializer.m
//  TouchCode
//
//  Created by Jonathan Wight on 12/07/2005.
//  Copyright 2005 toxicsoftware.com. All rights reserved.
//
//  Permission is hereby granted, free of charge, to any person
//  obtaining a copy of this software and associated documentation
//  files (the "Software"), to deal in the Software without
//  restriction, including without limitation the rights to use,
//  copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following
//  conditions:
//
//  The above copyright notice and this permission notice shall be
//  included in all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
//  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
//  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
//  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//  OTHER DEALINGS IN THE SOFTWARE.
//

#import "CJSONSerializer.h"

#import "JSONRepresentation.h"

static NSData *kNULL = NULL;
static NSData *kFalse = NULL;
static NSData *kTrue = NULL;

@implementation CJSONSerializer

@synthesize options;

+ (void)initialize
    {
    if (self == [CJSONSerializer class])
        {
        NSAutoreleasePool *thePool = [[NSAutoreleasePool alloc] init];

        if (kNULL == NULL)
            kNULL = [[NSData alloc] initWithBytesNoCopy:(void *)"null" length:4 freeWhenDone:NO];
        if (kFalse == NULL)
            kFalse = [[NSData alloc] initWithBytesNoCopy:(void *)"false" length:5 freeWhenDone:NO];
        if (kTrue == NULL)
            kTrue = [[NSData alloc] initWithBytesNoCopy:(void *)"true" length:4 freeWhenDone:NO];

        [thePool release];
        }
    }

+ (CJSONSerializer *)serializer
    {
    return([[[self alloc] init] autorelease]);
    }
    
- (BOOL)isValidJSONObject:(id)inObject
    {
    if ([inObject isKindOfClass:[NSNull class]])
        {
        return(YES);
        }
    else if ([inObject isKindOfClass:[NSNumber class]])
        {
        return(YES);
        }
    else if ([inObject isKindOfClass:[NSString class]])
        {
        return(YES);
        }
    else if ([inObject isKindOfClass:[NSArray class]])
        {
        return(YES);
        }
    else if ([inObject isKindOfClass:[NSDictionary class]])
        {
        return(YES);
        }
    else if ([inObject isKindOfClass:[NSData class]])
        {
        return(YES);
        }
    else if ([inObject respondsToSelector:@selector(JSONDataRepresentation)])
        {
        return(YES);
        }
    else
        {
        return(NO);
        }
    }

- (NSData *)serializeObject:(id)inObject error:(NSError **)outError
    {
    NSData *theResult = NULL;

    if ([inObject isKindOfClass:[NSNull class]])
        {
        theResult = [self serializeNull:inObject error:outError];
        }
    else if ([inObject isKindOfClass:[NSNumber class]])
        {
        theResult = [self serializeNumber:inObject error:outError];
        }
    else if ([inObject isKindOfClass:[NSString class]])
        {
        theResult = [self serializeString:inObject error:outError];
        }
    else if ([inObject isKindOfClass:[NSArray class]])
        {
        theResult = [self serializeArray:inObject error:outError];
        }
    else if ([inObject isKindOfClass:[NSDictionary class]])
        {
        theResult = [self serializeDictionary:inObject error:outError];
        }
    else if ([inObject isKindOfClass:[NSData class]])
        {
        NSString *theString = [[[NSString alloc] initWithData:inObject encoding:NSUTF8StringEncoding] autorelease];
        theResult = [self serializeString:theString error:outError];
        }
    else if ([inObject respondsToSelector:@selector(JSONDataRepresentation)])
        {
        theResult = [inObject JSONDataRepresentation];
        }
    else
        {
        if (outError)
            {
            NSDictionary *theUserInfo = [NSDictionary dictionaryWithObjectsAndKeys:
                [NSString stringWithFormat:@"Cannot serialize data of type '%@'", NSStringFromClass([inObject class])], NSLocalizedDescriptionKey,
                NULL];
            *outError = [NSError errorWithDomain:@"TODO_DOMAIN" code:CJSONSerializerErrorCouldNotSerializeDataType userInfo:theUserInfo];
            }
        return(NULL);
        }
    if (theResult == NULL)
        {
        if (outError)
            {
            NSDictionary *theUserInfo = [NSDictionary dictionaryWithObjectsAndKeys:
                [NSString stringWithFormat:@"Could not serialize object '%@'", inObject], NSLocalizedDescriptionKey,
                NULL];
            *outError = [NSError errorWithDomain:@"TODO_DOMAIN" code:CJSONSerializerErrorCouldNotSerializeObject userInfo:theUserInfo];
            }
        return(NULL);
        }
    return(theResult);
    }

- (NSData *)serializeNull:(NSNull *)inNull error:(NSError **)outError
    {
    #pragma unused (inNull, outError)
    return(kNULL);
    }

- (NSData *)serializeNumber:(NSNumber *)inNumber error:(NSError **)outError
    {
    #pragma unused (outError)
    NSData *theResult = NULL;
    switch (CFNumberGetType((CFNumberRef)inNumber))
        {
        case kCFNumberCharType:
            {
            int theValue = [inNumber intValue];
            if (theValue == 0)
                theResult = kFalse;
            else if (theValue == 1)
                theResult = kTrue;
            else
                theResult = [[inNumber stringValue] dataUsingEncoding:NSASCIIStringEncoding];
            }
            break;
        case kCFNumberFloat32Type:
        case kCFNumberFloat64Type:
        case kCFNumberFloatType:
        case kCFNumberDoubleType:
        case kCFNumberSInt8Type:
        case kCFNumberSInt16Type:
        case kCFNumberSInt32Type:
        case kCFNumberSInt64Type:
        case kCFNumberShortType:
        case kCFNumberIntType:
        case kCFNumberLongType:
        case kCFNumberLongLongType:
        case kCFNumberCFIndexType:
        default:
            theResult = [[inNumber stringValue] dataUsingEncoding:NSASCIIStringEncoding];
            break;
        }
    return(theResult);
    }

- (NSData *)serializeString:(NSString *)inString error:(NSError **)outError
    {
    #pragma unused (outError)

    const char *theUTF8String = [inString UTF8String];

    NSMutableData *theData = [NSMutableData dataWithLength:strlen(theUTF8String) * 2 + 2];

    char *theOutputStart = [theData mutableBytes];
    char *OUT = theOutputStart;

    *OUT++ = '"';

    for (const char *IN = theUTF8String; IN && *IN != '\0'; ++IN)
        {
        switch (*IN)
            {
            case '\\':
                {
                *OUT++ = '\\';
                *OUT++ = '\\';
                }
                break;
            case '\"':
                {
                *OUT++ = '\\';
                *OUT++ = '\"';
                }
                break;
            case '/':
                {
                if (self.options & kJSONSerializationOptions_EncodeSlashes)
                    {
                    *OUT++ = '\\';
                    *OUT++ = '/';
                    }
                else
                    {
                    *OUT++ = *IN;
                    }
                }
                break;
            case '\b':
                {
                *OUT++ = '\\';
                *OUT++ = 'b';
                }
                break;
            case '\f':
                {
                *OUT++ = '\\';
                *OUT++ = 'f';
                }
                break;
            case '\n':
                {
                *OUT++ = '\\';
                *OUT++ = 'n';
                }
                break;
            case '\r':
                {
                *OUT++ = '\\';
                *OUT++ = 'r';
                }
                break;
            case '\t':
                {
                *OUT++ = '\\';
                *OUT++ = 't';
                }
                break;
            default:
                {
                *OUT++ = *IN;
                }
                break;
            }
        }

    *OUT++ = '"';

    theData.length = OUT - theOutputStart;
    return(theData);
    }

- (NSData *)serializeArray:(NSArray *)inArray error:(NSError **)outError
    {
    NSMutableData *theData = [NSMutableData data];

    [theData appendBytes:"[" length:1];

    NSEnumerator *theEnumerator = [inArray objectEnumerator];
    id theValue = NULL;
    NSUInteger i = 0;
    while ((theValue = [theEnumerator nextObject]) != NULL)
        {
        NSData *theValueData = [self serializeObject:theValue error:outError];
        if (theValueData == NULL)
            {
            return(NULL);
            }
        [theData appendData:theValueData];
        if (++i < [inArray count])
            [theData appendBytes:"," length:1];
        }

    [theData appendBytes:"]" length:1];

    return(theData);
    }

- (NSData *)serializeDictionary:(NSDictionary *)inDictionary error:(NSError **)outError
    {
    NSMutableData *theData = [NSMutableData data];

    [theData appendBytes:"{" length:1];

    NSArray *theKeys = [inDictionary allKeys];
    NSEnumerator *theEnumerator = [theKeys objectEnumerator];
    NSString *theKey = NULL;
    while ((theKey = [theEnumerator nextObject]) != NULL)
        {
        id theValue = [inDictionary objectForKey:theKey];
        
        NSData *theKeyData = [self serializeString:theKey error:outError];
        if (theKeyData == NULL)
            {
            return(NULL);
            }
        NSData *theValueData = [self serializeObject:theValue error:outError];
        if (theValueData == NULL)
            {
            return(NULL);
            }
        
        
        [theData appendData:theKeyData];
        [theData appendBytes:":" length:1];
        [theData appendData:theValueData];
        
        if (theKey != [theKeys lastObject])
            [theData appendData:[@"," dataUsingEncoding:NSASCIIStringEncoding]];
        }

    [theData appendBytes:"}" length:1];

    return(theData);
    }

@end
