/*
    MAValueData - NSData that knows what type of data it stores.
 
    Copyright (C) 1995, Adam Fedor.
*/
#ifdef NeXT_Foundation_LIBRARY
#include <Foundation/Foundation.h>
#else
#include <Foundation/NSArray.h>
#include <Foundation/NSString.h>
#include <Foundation/NSCoder.h>
#endif
#include <string.h>
#include "MathArray/MAValueData.h"
#include "MathArrayPrivate.h"
#include "MathArray/array_encoding.h"

@implementation MAValueData
/* This is a class for storing arrays of arbitrary data.  It is much like
   NSData, only it 'knows' what type of data it stores (like NSValue), and
   can access it appropriately */

+ allocWithZone:(NSZone *)zone
{
    return NSAllocateObject(self, 0, zone);
}

/* Create a new object using the data given in values, which is
   assumed to be of Objective-C type and contain count values. */
+ dataWithValues:(const void *)values 
    count:(unsigned)count 
    objCType:(const char *)type 
{
    return [[[self alloc] initWithValues:values count:count objCType:type]
    	autorelease];
}

/* Create a new object using the data given in values, which is
   assumed to be of Objective-C type and contain count values. The
   array becomes the owner of values, as in the NSData method of a similar
   name. */
+ dataWithValuesNoCopy:(void *)values 
	count:(unsigned)count 
	objCType:(const char *)type 
{
    return [[[self alloc] initWithValuesNoCopy:values 
    	count:count objCType:type] autorelease];
}

/* Create a new object using the data given in values, which is
   assumed to contain NSPoint values. */
+ dataWithPoints:(const void *)pointArray count:(unsigned)count 
{
    return [[[self alloc] initWithValues:pointArray 
    	count:count objCType:@encode(NSPoint)] autorelease];
}

/* Create a new object using the data given in values, which is
   assumed to contain NSRect values. */
+ dataWithRects:(const void *)rectArray count:(unsigned)count 
{
    return [[[self alloc] initWithValues:rectArray 
    	count:count objCType:@encode(NSRect)] autorelease];
}

/* Create a new object using the NSValues contained in the NSArray list. */
+ dataWithValueList:(NSArray *)valueList
{
    return [[[self alloc] initWithValueList:valueList] autorelease];
}

- (unsigned)_initType:(const char *)type
{
    unsigned size = 0;
    if (!type)
    	MA_RAISE_PARAMETER;
    
    size = math_aligned_size(type);
    c_type = [[NSString stringWithCString:type] retain];

    return size;
}

- _allocData
{
    // Allocate our storage, but don't initialize (for subclassing)
    data = [NSData alloc];
    return data;
}

/* The following method needs to be overriden from NSData */
- (id) initWithBytes: (const void*)aBuffer
                    length: (unsigned int)bufferSize
{
    data = [[self _allocData] initWithBytes: aBuffer 
		                           length: bufferSize];
    return self;
}

/* The following method needs to be overriden from NSData */
- (id) initWithBytesNoCopy: (void*)aBuffer
                    length: (unsigned int)bufferSize
              freeWhenDone: (BOOL)shouldFree
{
    data = [[self _allocData] initWithBytesNoCopy: aBuffer 
		                           length: bufferSize
				     freeWhenDone: shouldFree];
    return self;
}

/* Initializes a newly created MAValueData using the data given in values, 
   which is assumed to be of Objective-C type and contain count values. */
- initWithValues:(const void *)values 
	count:(unsigned)count 
	objCType:(const char *)type 
{
    unsigned type_size = [self _initType:type];
    return [self initWithBytes:values length:count*type_size];
}

/* Initializes a newly created MAValueData using the data given in 
   values, which is assumed to be of Objective-C type and contain count 
   values. The array becomes the owner of values, as in the NSData 
   method of a similar name. */
- initWithValuesNoCopy:(void *)values 
	count:(unsigned)count 
	objCType:(const char *)type 
{
    unsigned type_size = [self _initType:type];
    return [self initWithBytesNoCopy:values length:count*type_size freeWhenDone: YES];
}

/* Initializes a newly created MAValueData with data obtained from the
   NSData object vdata, which is assumed to contain values of Objective-C
   type type. */
- initWithData:(NSData *)vdata objCType:(const char *)type 
{
    //[super init];
    [self _initType:type];
    data = [[self _allocData] initWithData: vdata];
    return self;
}

/* Same as initWithData:objcType: but simple retains the data and does
   not copy it (if possible) */
- initWithDataNoCopy:(NSData *)vdata objCType:(const char *)type 
{
    //[super init];
    [self _initType:type];
    if ([[self _allocData] respondsToSelector:@selector(mutableBytes)]
	&& ![vdata respondsToSelector:@selector(mutableBytes)])
      data = [vdata mutableCopy];
    else
      data = [vdata retain];
    return self;
}

/* Initializes the receiver with values from the valueArray list which must
   contain values of the same objCType */
- initWithValueList:(NSArray *)valueList
{
    int i, count;
    NSMutableData *vdata;
    
    if (![[valueList objectAtIndex:0] respondsToSelector:@selector(objCType)])
	MA_RAISE_PARAMETER

    [self _initType:[[valueList objectAtIndex:0] objCType]];
    count = [valueList count];
    vdata = [NSMutableData 
    		dataWithLength:count*math_aligned_size([c_type cString])];
    for (i=0; i < count; i++) {
        NSValue *value = [valueList objectAtIndex:i];
	if (![value respondsToSelector:@selector(objCType)])
	    MA_RAISE_PARAMETER
	// FIXME: check for same types
	[value getValue:&vdata[i*math_aligned_size([c_type cString])]];
    }

    return [self initWithData:vdata];
}

- (void)dealloc
{
    [c_type release];
    [data release];
    [super dealloc];
}

- (const void *)bytes
{
    return [data bytes];
}

- (unsigned)length
{
    return [data length];
}

/* Returns an array containing NSPoint values */
- (const NSPoint *)pointArray
{
    return (const NSPoint *)[self bytes];
}

/* Returns an array containing NSRect values */
- (const NSRect *)rectArray
{
    return (const NSRect *)[self bytes];
}

/* Returns a NSArray containing NSValues representing the values in the
   receiver. */
- (NSArray *)valueList
{
    int i, count;
    NSMutableArray *array;
    
    count = [self count];
    array = [NSMutableArray arrayWithCapacity:count];
    for (i=0; i < count; i++)
    	[array addObject:[self valueAtIndex:i]];

    return [array copy];
}

/* Returns as an NSValue, the value stored at the specified index */
- (NSValue *)valueAtIndex:(unsigned)index
{
    const void *value;
    value = [data bytes] + index * math_aligned_size([c_type cString]);
    
    /* I call MANumber instead of NSValue because MANumber will automatically
       allocate the correct NSNumber or NSValue object depending on c_type.
       NSValue won't do this (even though it should) */
    return [MANumber value:value withObjCType:[c_type cString]];
}

- (void) getValues:(void *)buffer range: (NSRange)range
{
    range.location *= math_aligned_size([c_type cString]);
    range.length   *= math_aligned_size([c_type cString]);
    [data getBytes:buffer range:range];
}

/* Returns the number of values stored in the receiver. */
- (unsigned)count
{
    return [self length]/math_aligned_size([c_type cString]);
}

/* Returns the Objective-C type of data sotred in the receiver */
- (const char *)objCType
{
    return [c_type cString];
}

- deepen
{
    c_type = [c_type copyWithZone:[self zone]];
    data = [data copyWithZone:[self zone]];
    return self;
}

- copyWithZone:(NSZone *)zone
{
    if (NSShouldRetainWithZone(self, zone))
    	return [self retain];
    else
    	return [[super copyWithZone:zone] deepen];
}

- copy
{
    return [self copyWithZone: [self zone]];
}

- mutableCopyWithZone:(NSZone *)zone
{
    MAMutableValueData *new = [MAMutableValueData allocWithZone:zone];
    [new initWithValues:[self bytes] 
		count:[self count] 
		objCType:[c_type cString]];
    return new;
}

- mutableCopy
{
  return [self mutableCopyWithZone: [self zone]];
}

- (void)encodeWithCoder:(NSCoder *)coder
{
    [super encodeWithCoder:coder];
    [coder encodeObject:data]; 
    [coder encodeObject:c_type]; 
}

- (id)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    data = [[coder decodeObject] retain]; 
    c_type = [[coder decodeObject] retain]; 
    return self;
}

@end

@interface MAValueData (Private)
- deepen;
@end

@implementation MAMutableValueData

+ allocWithZone:(NSZone *)zone
{
    return NSAllocateObject(self, 0, zone);
}

+ dataWithCount:(unsigned)count objCType:(const char *)type
{
    return [[[self alloc] initWithCount:count objCType:type] autorelease];
}

- (unsigned)_initType:(const char *)type
{
    unsigned size;
    if (!type) {
    	MA_RAISE_PARAMETER;
	/* NOT REACHED */
    }
    
    size = math_aligned_size(type);
    c_type = [[NSString stringWithCString:type] retain]; 
    return size;
}

- _allocData
{
    // Allocate our storage, but don't initialize (for subclassing)
    data = (NSData *)[NSMutableData alloc];
    return data;
}

/* The following two methods must be overriden from NSMutableData */
- initWithCapacity:(unsigned)capacity
{
    [super init];
    data = [(NSMutableData *)[self _allocData] initWithCapacity:capacity];
    return self;
}

- initWithLength:(unsigned)length
{
    [super init];
    data = [(NSMutableData *)[self _allocData] initWithLength:length];
    return self;
}

- initWithCount:(unsigned)count objCType:(const char *)type
{
    unsigned type_size = [self _initType:type];
    return [self initWithLength:count*type_size];
}

- (void)setLength:(unsigned)length
{
    [(NSMutableData *)data setLength:length];
}

- (void *)mutableBytes
{
    return [(NSMutableData *)data mutableBytes];
}

- (void)setValue:(NSValue *)value atIndex:(unsigned)index;
{
    unsigned size;
    size = math_aligned_size([self objCType]);
    if (!value) {
    	[NSException raise:MAParameterException
		format:@"Tried to set with nil value"];
    }
    if (strcmp([value objCType], [self objCType]) != 0) {
    	[NSException raise:MAParameterException
		format:@"Tried to set with value of different type"];
    }
    if ((index+1)*size > [data length])
      [self setLength: (index+1)*size];
    [value getValue:[(NSMutableData *)data mutableBytes]+index*size];
}

- (void)increaseCountBy:(unsigned)extraCount 
{
    [self setLength:[self length] 
    		+ extraCount * math_aligned_size([c_type cString])];
}

- (void)setCount:(unsigned)count 
{
    [self setLength:count * math_aligned_size([c_type cString])];
}

- (void)appendValues:(const void *)values count:(unsigned)count
{
    [(NSMutableData *)data appendBytes:values 
    		length:count*math_aligned_size([c_type cString])];
}

- (void)appendValueData:(MAValueData *)other 
{
    NSString *c_other;
    
    if (!other) {
    	MA_RAISE_PARAMETER;
	/* NOT REACHED */
    }
    c_other = [NSString stringWithCString:[other objCType]];
    if (![c_type isEqual:c_other]) {
    	[NSException raise:MAArrayMismatchException
	    format:@"ValueData data types do not match"];
	/* NOT REACHED */
    }
    [(NSMutableData *)data appendData:other];
}

- (void)replaceValuesInRange:(NSRange)aRange withValues:(const void *)values
{
    unsigned size;
    size = math_aligned_size([c_type cString]);
    aRange = NSMakeRange(aRange.location*size, aRange.length*size);
    [(NSMutableData *)data replaceBytesInRange:aRange withBytes:values];
}

- copyWithZone:(NSZone *)zone
{
    MAMutableValueData *copy = NSCopyObject(self, 0, zone);
    return [copy deepen];
}

@end

@implementation MAMutableValueData (ValueCasting)

/* This method changes what type of data the receiver thinks it is holding.
   It doesn't cast the data to the new form! You need to write your own
   routine to do that, if that is what you want. Adjusts the count
   automatically if the original type is of a different size than the new
   type. Raises an exception if the size of the new type exceeds the
   size of the data */
-(void)setObjCType:(const char *)type
{
    if (math_aligned_size(type) > [self length]) {
    	[NSException raise:MAParameterException
		format:@"New type exceeds length of data"];
	/* NOT REACHED */
    }
    
    // FIXME: what if sizes don't divide evenly?
    [c_type release];
    c_type = [[NSString stringWithCString:type] retain];
}

@end
