/* 
   NSData.m

   Copyright (C) 1995, 1996 Ovidiu Predescu and Mircea Oancea.
   All rights reserved.

   Author: Ovidiu Predescu <ovidiu@bx.logicnet.ro>

   This file is part of libFoundation.

   Permission to use, copy, modify, and distribute this software and its
   documentation for any purpose and without fee is hereby granted, provided
   that the above copyright notice appear in all copies and that both that
   copyright notice and this permission notice appear in supporting
   documentation.

   We disclaim all warranties with regard to this software, including all
   implied warranties of merchantability and fitness, in no event shall
   we be liable for any special, indirect or consequential damages or any
   damages whatsoever resulting from loss of use, data or profits, whether in
   an action of contract, negligence or other tortious action, arising out of
   or in connection with the use or performance of this software.
*/

#include <Foundation/common.h>
#include <Foundation/NSData.h>
#include <Foundation/NSString.h>
#include <Foundation/NSPosixFileDescriptor.h>
#include <Foundation/NSCoder.h>
#include <Foundation/NSException.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/exceptions/GeneralExceptions.h>

#include <extensions/objc-runtime.h>

#include "byte_order.h"
#include "NSConcreteData.h"

@implementation NSData

+ (id)allocWithZone:(NSZone*)zone
{
    return NSAllocateObject(((self == [NSData class]) ? 
	    [NSConcreteData class] : self), 0, zone);
}

+ (id)data
{
    return [[[self allocWithZone:NSDefaultMallocZone()]
		initWithBytes:NULL length:0]
		autorelease];
}

+ (id)dataWithBytes:(const void*)bytes
    length:(unsigned int)length
{
    return [[[self allocWithZone:NSDefaultMallocZone()]
		initWithBytes:bytes length:length]
		autorelease];
}

+ (id)dataWithBytesNoCopy:(void*)bytes
    length:(unsigned int)length
{
    return [[[self allocWithZone:NSDefaultMallocZone()]
		initWithBytesNoCopy:bytes 
		length:length] autorelease];
}

+ (id)dataWithContentsOfFile:(NSString*)path
{
    return [[[self allocWithZone:NSDefaultMallocZone()]
		initWithContentsOfFile:path]
		autorelease];
}

+ (id)dataWithContentsOfMappedFile:(NSString*)path
{
    NSPosixFileDescriptor* descriptor;
    NSRange range = {0, 0};
    
    descriptor = [[[NSPosixFileDescriptor alloc]
	initWithPath:path] autorelease];
    range.length = [descriptor fileLength];    

    return [descriptor mapFileRange:range];
}

+ (id)dataWithData:(NSData*)aData
{
    return [[[self alloc] initWithData:aData] autorelease]; 
}

- (id)initWithBytes:(const void*)bytes
    length:(unsigned int)length
{
    [self subclassResponsibility:_cmd];
    return nil;
}

- (id)initWithBytesNoCopy:(void*)bytes
    length:(unsigned int)length
{
    [self subclassResponsibility:_cmd];
    return nil;
}

- (id)initWithContentsOfFile:(NSString*)path
{
    NSRange range = {0, 0};
    char* content = NULL;
    NSPosixFileDescriptor* descriptor = [[NSPosixFileDescriptor alloc] 
					initWithPath:path];
    
    /* Take the address of range to force the compiler to do not allocate it
       in registers */
    (&range)->length = 0;
    
    TRY {
	range.length = [descriptor fileLength];    
	content = descriptor ? NSZoneMalloc([self zone], range.length) : NULL;
	[descriptor readBytes:content range:range];
    } END_TRY
    OTHERWISE {
	Free(content);
	content = NULL;
	descriptor = [descriptor self];
    } END_CATCH
    
    [descriptor release];
    
    if (!content) {
	[self autorelease];
	return nil;
    }

    return [self initWithBytesNoCopy:content length:range.length];
}

- (id)initWithContentsOfMappedFile:(NSString*)path
{
    NSPosixFileDescriptor* descriptor;
    NSRange range = {0, 0};
    
    descriptor = [[[NSPosixFileDescriptor alloc]
	initWithPath:path] autorelease];
    range.length = [descriptor fileLength];    

    [self release];
    return [descriptor mapFileRange:range];
}

- (id)initWithData:(NSData*)data
{
    return [self initWithBytes:[data bytes] length:[data length]];
}

- copy
{
    return [self copyWithZone:NSDefaultMallocZone()];
}

- (id)copyWithZone:(NSZone*)zone
{
    return [[NSData allocWithZone:zone] initWithData:self];
}

- mutableCopy
{
    return [self mutableCopyWithZone:NSDefaultMallocZone()];
}

- (id)mutableCopyWithZone:(NSZone*)zone
{
    return [[NSMutableData allocWithZone:zone] initWithData:self];
}

- (const void*)bytes
{
    [self subclassResponsibility:_cmd];
    return NULL;
}

- (NSString*)description
{
    int i;
    unsigned int length = [self length];
    const char* bytes = [self bytes];
    unsigned int final_length = 4 + 2 * length + 1 + length / 4;
    char* description = Malloc(final_length);
    char* temp = description + 1;

    description[0] = 0;
    Strcat(description, "<");
    for(i = 0; i < length; i++, temp += 2) {
	if (i % 4 == 0)
	    *temp++ = ' ';
	sprintf (temp, "%02X", (unsigned char)((char*)bytes)[i]);
    }
    strcat(temp, " >");
    *(temp += 2) = 0;
    return [NSString stringWithCStringNoCopy:description freeWhenDone:YES];
}

- (void)getBytes:(void*)buffer
{
    memcpy(buffer, [self bytes], [self length]);
}

- (void)getBytes:(void*)buffer
  length:(unsigned int)_length
{
    if(_length > [self length])
	THROW([RangeException new]);
    else memcpy(buffer, [self bytes], _length);
}

- (void)getBytes:(void*)buffer
    range:(NSRange)aRange
{
    unsigned int length = [self length];

    if(aRange.location > length
	    || aRange.length > length
	    || aRange.location + aRange.length > length)
	THROW([RangeException new]);
    else memcpy(buffer, [self bytes] + aRange.location, aRange.length);
}

- (NSData*)subdataWithRange:(NSRange)aRange
{
    return [[[NSConcreteDataRange allocWithZone:NSDefaultMallocZone()]
	    initWithData:self range:aRange] autorelease];
}

- (BOOL)isEqualToData:(NSData*)other
{
    if([self length] == [other length])
	return memcmp([self bytes], [other bytes], [self length]) == 0;
    else return NO;
}

- (BOOL)isEqual:(id)anObject
{
    if([anObject isKindOfClass:[NSData class]])
	return [self isEqualToData:anObject];
    else return NO;
}

- (unsigned int)length
{
    [self subclassResponsibility:_cmd];
    return 0;
}

- (BOOL)writeToFile:(NSString*)path
  atomically:(BOOL)useAuxiliaryFile
{
    return writeToFile(path, self, useAuxiliaryFile);
}

- (unsigned int)deserializeAlignedBytesLengthAtCursor:(unsigned int*)cursor
{
    return *cursor;
}

- (void)deserializeBytes:(void*)buffer
  length:(unsigned int)bytes
  atCursor:(unsigned int*)cursor
{
    NSRange range = { *cursor, bytes };
    [self getBytes:buffer range:range];
    *cursor += bytes;
}

- (void)deserializeDataAt:(void*)data
  ofObjCType:(const char*)type
  atCursor:(unsigned int*)cursor
  context:(id <NSObjCTypeSerializationCallBack>)callback
{
    if(!type || !data)
	return;

    switch(*type) {
	case _C_ID: {
	    [callback deserializeObjectAt:data ofObjCType:type
		    fromData:self atCursor:cursor];
	    break;
	}
	case _C_CHARPTR: {
	    volatile int len = [self deserializeIntAtCursor:cursor];
	    id adr = nil;

	    /* This statement, by taking the address of `type', forces the
		compiler to not allocate `type' into a register */
	    *(void**)data = &type;

	    if(len == -1) {
		*(const char**)data = NULL;
		return;
	    }
	    else {
		*(char**)data = Malloc(len + 1);
		/* Autorelease *data in case of exceptions */
		adr = [NSAutoreleasedPointer autoreleasePointer:*(char**)data];
	    }
    
	    [self deserializeBytes:*(char**)data length:len atCursor:cursor];

	    /* Save the *data from destruction */
	    [adr retain];
    
	    break;
	}
	case _C_ARY_B: {
	    int i, count, offset, itemSize;
	    const char* itemType;
    
	    count = Atoi(type + 1);
	    itemType = type;
	    while(isdigit(*++itemType));
		itemSize = objc_sizeof_type(itemType);

		for(i = offset = 0; i < count; i++, offset += itemSize)
		    [self deserializeDataAt:(char*)data + offset
				    ofObjCType:itemType
				    atCursor:cursor
				    context:callback];
	    break;
	}
	case _C_STRUCT_B: {
	    int offset = 0;
	    int align, rem;

	    while(*type != _C_STRUCT_E && *type++ != '='); /* skip "<name>=" */
	    while(1) {
		[self deserializeDataAt:((char*)data) + offset
			ofObjCType:type
			atCursor:cursor
			context:callback];
		offset += objc_sizeof_type(type);
		type = objc_skip_typespec(type);
		if(*type != _C_STRUCT_E) {
		    align = objc_alignof_type(type);
		    if((rem = offset % align))
			offset += align - rem;
		}
		else break;
	    }
	    break;
        }
        case _C_PTR: {
	    id addr;

            *(char**)data = Malloc(objc_sizeof_type(++type));

	    /* Autorelease *data in case of exceptions */
	    addr = [NSAutoreleasedPointer autoreleasePointer:*(char**)data];

	    [self deserializeDataAt:*(char**)data
		    ofObjCType:type
		    atCursor:cursor
		    context:callback];

	    /* Save the *data from destruction */
	    [addr retain];

	    break;
        }
	case _C_CHR:
	case _C_UCHR: {
	    [self deserializeBytes:data
		  length:sizeof(unsigned char)
		  atCursor:cursor];
	    break;
	}
        case _C_SHT:
	case _C_USHT: {
	    unsigned short ns;

	    [self deserializeBytes:&ns
		  length:sizeof(unsigned short)
		  atCursor:cursor];
	    *(unsigned short*)data = network_short_to_host (ns);
	    break;
	}
        case _C_INT:
	case _C_UINT: {
	    unsigned int ni;

	    [self deserializeBytes:&ni
		  length:sizeof(unsigned int)
		  atCursor:cursor];
	    *(unsigned int*)data = network_int_to_host (ni);
	    break;
	}
        case _C_LNG:
	case _C_ULNG: {
	    unsigned int nl;

	    [self deserializeBytes:&nl
		  length:sizeof(unsigned long)
		  atCursor:cursor];
	    *(unsigned long*)data = network_long_to_host (nl);
	    break;
	}
        case _C_FLT: {
	    network_float nf;

	    [self deserializeBytes:&nf
		  length:sizeof(float)
		  atCursor:cursor];
	    *(float*)data = network_float_to_host (nf);
	    break;
	}
        case _C_DBL: {
	    network_double nd;

	    [self deserializeBytes:&nd
		  length:sizeof(double)
		  atCursor:cursor];
	    *(double*)data = network_double_to_host (nd);
	    break;
	}
        default:
	    THROW([[UnknownTypeException alloc] initForType:type]);
    }
}

- (int)deserializeIntAtCursor:(unsigned int*)cursor
{
    unsigned int ni, result;

    [self deserializeBytes:&ni length:sizeof(unsigned int) atCursor:cursor];
    result = network_int_to_host (ni);
    return result;
}

- (int)deserializeIntAtIndex:(unsigned int)index
{
    unsigned int ni, result;

    [self deserializeBytes:&ni length:sizeof(unsigned int) atCursor:&index];
    result = network_int_to_host (ni);
    return result;
}

- (void)deserializeInts:(int*)intBuffer
  count:(unsigned int)numInts
  atCursor:(unsigned int*)cursor
{
    int i;

    [self deserializeBytes:&intBuffer
	  length:numInts * sizeof(unsigned int)
	  atCursor:cursor];
    for (i = 0; i < numInts; i++)
	intBuffer[i] = network_int_to_host (intBuffer[i]);
}

- (void)deserializeInts:(int*)intBuffer
  count:(unsigned int)numInts
  atIndex:(unsigned int)index
{
    int i;

    [self deserializeBytes:&intBuffer
		    length:numInts * sizeof(int)
		    atCursor:&index];
    for (i = 0; i < numInts; i++)
	intBuffer[i] = network_int_to_host (intBuffer[i]);
}

- (Class)classForCoder
{
    return [NSData class];
}

- (void)encodeWithCoder:(NSCoder*)aCoder
{
    const char* bytes = [self bytes];
    unsigned int length = [self length];

    [aCoder encodeValueOfObjCType:@encode(unsigned int) at:&length];
    [aCoder encodeArrayOfObjCType:@encode(char) count:length at:&bytes];
}

- (id)initWithCoder:(NSCoder*)aDecoder
{
    char* bytes;
    unsigned int length;

    [aDecoder decodeValueOfObjCType:@encode(unsigned int) at:&length];
    bytes = Malloc (length);
    [aDecoder decodeArrayOfObjCType:@encode(char) count:length at:bytes];
    [self initWithBytesNoCopy:bytes length:length];

    return self;
}

@end /* NSData */


@implementation NSMutableData

+ (id)allocWithZone:(NSZone*)zone
{
    return NSAllocateObject(((self == [NSMutableData class]) ? 
	    [NSConcreteMutableData class] : self), 0, zone);
}

+ (id)dataWithCapacity:(unsigned int)numBytes
{
    return [[[self allocWithZone:NSDefaultMallocZone()]
		initWithCapacity:numBytes]
		autorelease];
}

+ (id)dataWithLength:(unsigned int)length
{
    return [[[self allocWithZone:NSDefaultMallocZone()]
		initWithLength:length]
		autorelease];
}

- (id)initWithCapacity:(unsigned int)capacity
{
    [self subclassResponsibility:_cmd];
    return nil;
}

- (id)initWithLength:(unsigned int)length
{
    [self subclassResponsibility:_cmd];
    return nil;
}

- (NSData*)subdataWithRange:(NSRange)aRange
{
    char* buffer = Malloc(aRange.length);

    [self getBytes:buffer range:aRange];
    return [[NSData alloc] initWithBytesNoCopy:buffer 
	    length:aRange.length];
}

- (void)increaseLengthBy:(unsigned int)extraLength
{
    [self subclassResponsibility:_cmd];
}

- (void*)mutableBytes
{
    [self subclassResponsibility:_cmd];
    return NULL;
}

- (void)setLength:(unsigned int)length
{
    [self subclassResponsibility:_cmd];
}

- (void)appendBytes:(const void*)_bytes
    length:(unsigned int)_length
{
    [self subclassResponsibility:_cmd];
}

- (void)appendData:(NSData*)other
{
    [self appendBytes:[other bytes] length:[other length]];
}

- (void)replaceBytesInRange:(NSRange)aRange
    withBytes:(const void*)bytes
{
    unsigned int length = [self length];

    if(aRange.location > length
		|| aRange.length > length
		|| aRange.location + aRange.length > length)
	THROW([RangeException new]);
    else {
	char* mBytes = [self mutableBytes];
	memcpy(mBytes + aRange.location, bytes, aRange.length);
    }
}

- (void)setData:(NSData*)aData
{
    [self setLength:[aData length]];
    [self replaceBytesInRange:NSMakeRange(0, [self length])
	withBytes:[aData bytes]];
}

- (void)resetBytesInRange:(NSRange)aRange
{
    unsigned int length = [self length];

    if(aRange.location > length
		|| aRange.length > length
		|| aRange.location + aRange.length > length)
	THROW([RangeException new]);
    else {
	char* mBytes = [self mutableBytes];
	memset(mBytes + aRange.location, 0, aRange.length); 
    }
}

- (void)serializeAlignedBytesLength:(unsigned int)length
{
}

- (void)serializeDataAt:(const void*)data
  ofObjCType:(const char*)type
  context:(id <NSObjCTypeSerializationCallBack>)callback
{
    if(!data || !type)
	    return;

    switch(*type) {
        case _C_ID: {
	    [callback serializeObjectAt:(id*)data
			ofObjCType:type
			intoData:self];
	    break;
	}
        case _C_CHARPTR: {
	    int len;

	    if(!*(void**)data) {
		[self serializeInt:-1];
		return;
	    }

	    len = Strlen(*(void**)data);
	    [self serializeInt:len];
	    [self appendBytes:*(void**)data length:len];

	    break;
	}
        case _C_ARY_B: {
            int i, offset, itemSize, count = Atoi(type + 1);
            const char* itemType = type;

            while(isdigit(*++itemType));
		itemSize = objc_sizeof_type(itemType);

		for(i = offset = 0; i < count; i++, offset += itemSize)
		    [self serializeDataAt:(char*)data + offset
			    ofObjCType:itemType
			    context:callback];

		break;
        }
        case _C_STRUCT_B: {
            int offset = 0;
            int align, rem;

            while(*type != _C_STRUCT_E && *type++ != '='); /* skip "<name>=" */
            while(1) {
                [self serializeDataAt:((char*)data) + offset
			ofObjCType:type
			context:callback];
                offset += objc_sizeof_type(type);
                type = objc_skip_typespec(type);
                if(*type != _C_STRUCT_E) {
                    align = objc_alignof_type(type);
                    if((rem = offset % align))
                        offset += align - rem;
                }
                else break;
            }
            break;
        }
	case _C_PTR:
	    [self serializeDataAt:*(char**)data
		    ofObjCType:++type context:callback];
	    break;
        case _C_CHR:
	case _C_UCHR:
	    [self appendBytes:data length:sizeof(unsigned char)];
	    break;
	case _C_SHT:
	case _C_USHT: {
	    unsigned short ns = host_short_to_network (*(unsigned short*)data);
	    [self appendBytes:&ns length:sizeof(unsigned short)];
	    break;
	}
	case _C_INT:
	case _C_UINT: {
	    unsigned int ni = host_int_to_network (*(unsigned int*)data);
	    [self appendBytes:&ni length:sizeof(unsigned int)];
	    break;
	}
	case _C_LNG:
	case _C_ULNG: {
	    unsigned long nl = host_long_to_network (*(unsigned long*)data);
	    [self appendBytes:&nl length:sizeof(unsigned long)];
	    break;
	}
	case _C_FLT: {
	    network_float nf = host_float_to_network (*(float*)data);
	    [self appendBytes:&nf length:sizeof(float)];
	    break;
	}
	case _C_DBL: {
	    network_double nd = host_double_to_network (*(double*)data);
	    [self appendBytes:&nd length:sizeof(double)];
	    break;
	}
	default:
	    THROW([[UnknownTypeException alloc] initForType:type]);
    }
}

- (void)serializeInt:(int)value
{
    unsigned int ni = host_int_to_network (value);
    [self appendBytes:&ni length:sizeof(unsigned int)];
}

- (void)serializeInt:(int)value atIndex:(unsigned int)index
{
    unsigned int ni = host_int_to_network (value);
    NSRange range = { index, sizeof(int) };
    [self replaceBytesInRange:range withBytes:&ni];
}

- (void)serializeInts:(int*)intBuffer count:(unsigned int)numInts
{
    int i;
    SEL selector = @selector (serializeInt:);
    IMP imp = [self methodForSelector:selector];

    for (i = 0; i < numInts; i++)
	(*imp)(self, selector, intBuffer[i]);
}

- (void)serializeInts:(int*)intBuffer
  count:(unsigned int)numInts
  atIndex:(unsigned int)index
{
    int i;
    SEL selector = @selector (serializeInt:atIndex:);
    IMP imp = [self methodForSelector:selector];

    for (i = 0; i < numInts; i++)
	(*imp)(self, selector, intBuffer[i], index++);
}

- (Class)classForCoder
{
    return [NSMutableData class];
}

@end /* NSMutableData */
