//
// $Id: ObjectInStream.m,v 1.11 2005/05/18 06:46:53 will_mason Exp $
//
// vi: set ft=objc:

/*
 * ObjectiveLib - a library of containers and algorithms for Objective-C
 *
 * Copyright (c) 2004
 * Will Mason
 *
 * Portions:
 *
 * Copyright (c) 1994
 * Hewlett-Packard Company
 *
 * Copyright (c) 1996,1997
 * Silicon Graphics Computer Systems, Inc.
 *
 * Copyright (c) 1997
 * Moscow Center for SPARC Technology
 *
 * Copyright (c) 1999 
 * Boris Fomitchev
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * You may contact the author at will_mason@users.sourceforge.net.
 */

#import "ObjectInStream.h"
#import "InStreamPackage.h"
#import "Macros.h"
#import "HashMap.h"
#import "RunTime.h"
#import "Streamable.h"
#import "SharedPointerTable.h"
#import <Foundation/NSString.h>
#import <Foundation/NSException.h>
#import <Foundation/NSData.h>
#import <Foundation/NSValue.h>

extern uint64_t __swap64(uint64_t);

@interface OLObjectInStream (PrivateMethods)

- (const char*) lookUpTypeName: (uint8_t)type;
- (void) verifyType: (uint8_t)type;

@end

@implementation OLObjectInStream

+ (id) streamWithInStream: (OLInStream*)underStream
{
    OL_BEGIN_AUTO_CTOR(OLObjectInStream)
        initWithInStream: underStream
    OL_END_AUTO_CTOR;
}

- (id) initWithInStream: (OLInStream*)underStream
{
    OLEqualTo* eq;

    [super initWithInStream: underStream];
    eq = [[allocator allocateObject: [OLEqualTo class]] init];
    classes = [[allocator allocateObject: [OLHashMap class]]
        initWithTableSize: 100 keyEqual: eq allocator: allocator];
    [allocator deallocateObject: eq];
    sharedPointers = [[allocator allocateObject: [OLSharedPointerTable class]]
        initWithAllocator: allocator];
    [self readHeader];
    return self;
}

- (void) dealloc
{
    [self close];
    [allocator deallocateObject: sharedPointers];
    [allocator deallocateObject: classes];
    [super dealloc];
}

- (void*) decodeBytesWithReturnedLength: (unsigned*)numBytes
{
    [self verifyType: WIRE_TYPE_BYTE_BLOCK];
    return [stream decodeBytesWithReturnedLength: numBytes];
}

- (NSData*) decodeDataObject
{
    [self verifyType: WIRE_TYPE_DATA];
    return [stream decodeDataObject];
}

- (id) decodeObject
{
    return [self readObject];
}

- (void) decodeValueOfObjCType: (const char*)valueType at: (void*)address
{
    uint8_t type;

    switch (*valueType)
    {
    case _C_CHR:
    case _C_UCHR:
        [self verifyType: WIRE_TYPE_CHAR];
        [stream decodeValueOfObjCType: valueType at: address];
        break;
    case _C_SHT:
    case _C_USHT:
        [self verifyType: WIRE_TYPE_SHORT];
        [stream decodeValueOfObjCType: valueType at: address];
        break;
    case _C_INT:
    case _C_UINT:
        *(unsigned*)address = [self readInt];
        break;
    case _C_LNG:
    case _C_ULNG:
        [self verifyType: WIRE_TYPE_LONG];
        [stream decodeValueOfObjCType: valueType at: address];
        break;
    case _C_LNG_LNG:
    case _C_ULNG_LNG:
        [self verifyType: WIRE_TYPE_LONG_LONG];
        [stream decodeValueOfObjCType: valueType at: address];
        break;
    case _C_FLT:
        *(float*)address = [self readFloat];
        break;
    case _C_DBL:
        *(double*)address = [self readDouble];
        break;
    case _C_ID:
        *(id*)address = [self readObject];
        break;
    case _C_CLASS:
        *(Class*)address = [self readClass];
        break;
    case _C_SEL:
        type = [stream readByte];
        if (type == WIRE_TYPE_SELECTOR)
        {
            [stream decodeValueOfObjCType: valueType at: address];
            [sharedPointers addPointer: *(void**)address];
        }
        else if (type == WIRE_TYPE_HANDLE)
        {
            *(SEL*)address = [sharedPointers lookUp: [stream readInt32]];
        }
        else
        {
            [NSException raise: OLInputOutputException
                format: @"Found unexpected type in stream - \"%s\"",
                [self lookUpTypeName: type]];
        }
        break;
    case _C_CHARPTR:
        [self verifyType: WIRE_TYPE_CHARPTR];
        [stream decodeValueOfObjCType: valueType at: address];
        break;
    case _C_ARY_B:
        [self verifyType: WIRE_TYPE_ARRAY];
        [stream decodeValueOfObjCType: valueType at: address];
        break;
    case _C_PTR:
        [self verifyType: WIRE_TYPE_POINTER];
        [stream decodeValueOfObjCType: valueType at: address];
        break;
    case _C_STRUCT_B:
        [self verifyType: WIRE_TYPE_STRUCTURE];
        [stream decodeValueOfObjCType: valueType at: address];
        break;
    default:
        [stream decodeValueOfObjCType: valueType at: address];
    }
}

- (BOOL) readBool
{
    [self verifyType: WIRE_TYPE_BOOL];
    return [stream readBool];
}

- (Class) readClass
{
    char* nameBuf;
    unsigned nameBufLen = 30;
    uint16_t len;
    int version;
    Class cls;
    Class origCls = nil;
    NSNumber* versionObj;
    NSString* name;
    uint8_t type;

    [self verifyType: WIRE_TYPE_CLASS];
    type = [stream readByte];
    if (type == WIRE_TYPE_HANDLE)
    {
        origCls = [sharedPointers lookUp: [stream readInt32]];
    }
    else
    {
        if (type != WIRE_TYPE_CLASS_NAME)
        {
            [NSException raise: OLInputOutputException
                format: @"Expected a type of \"%s\" but found \"%s\"",
                [self lookUpTypeName: WIRE_TYPE_CLASS_NAME], [self lookUpTypeName: type]];
        }
        nameBuf = [allocator allocateBytes: nameBufLen];
        do
        {
            len = [stream readInt16];
            if (nameBufLen < len + 1)
            {
                nameBufLen = len + 1;
                [allocator deallocateBytes: nameBuf];
                nameBuf = [allocator allocateBytes: nameBufLen];
            }
            [self completelyReadBytes: nameBuf count: len];
            nameBuf[len] = 0;
            cls = objc_get_class(nameBuf);
            if (cls == nil)
            {
                [allocator deallocateBytes: nameBuf];
                [NSException raise: OLClassNotFoundException
                    format: @"The class \"%s\" is not registered", nameBuf];
            }
            if (origCls == nil)
                origCls = cls;
            [sharedPointers addPointer: cls];
            version = [stream readInt];
            name = [[allocator allocateObject: [NSString class]]
                initWithUTF8String: nameBuf];
            versionObj = [[allocator allocateObject: [NSNumber class]]
                initWithInt: version];
            [classes assignKey: name value: versionObj];
            [allocator deallocateObject: name];
            [allocator deallocateObject: versionObj];
            type = [stream readByte];
            if (type != WIRE_TYPE_CLASS_NAME && type != WIRE_TYPE_END_CLASS)
            {
                [allocator deallocateBytes: nameBuf];
                [NSException raise: OLInputOutputException
                    format: @"Found unexpected type in stream - \"%s\"",
                    [self lookUpTypeName: type]];
            }
        } while (type == WIRE_TYPE_CLASS_NAME);
        [allocator deallocateBytes: nameBuf];
    }
    return origCls;
}

- (double) readDouble
{
    [self verifyType: WIRE_TYPE_DOUBLE];
    return [stream readDouble];
}

- (float) readFloat
{
    [self verifyType: WIRE_TYPE_FLOAT];
    return [stream readFloat];
}

- (void) readHeader
{
    if ([stream readInt32] != OL_STREAM_MAGIC)
    {
        [NSException raise: OLInputOutputException
            format: @"The magic number indentifying the stream is incorrect"];
    }
    systemVersion = [stream readInt32];
}

- (unsigned) readInt
{
    [self verifyType: WIRE_TYPE_INT];
    return [stream readInt];
}

- (uint16_t) readInt16
{
    [self verifyType: WIRE_TYPE_INT16];
    return [stream readInt16];
}

- (uint32_t) readInt32
{
    [self verifyType: WIRE_TYPE_INT32];
    return [stream readInt32];
}

- (uint64_t) readInt64
{
    [self verifyType: WIRE_TYPE_INT64];
    return [stream readInt64];
}

- (id) readObject
{
    uint8_t type;
    id object = nil;
    Class cls;
    BOOL initWithStream = YES;

    [self verifyType: WIRE_TYPE_INSTANCE];
    type = [stream readByte];
    if (type == WIRE_TYPE_NIL)
    {
        // object is already nil
    }
    else if (type == WIRE_TYPE_HANDLE)
    {
        object = [sharedPointers lookUp: [stream readInt32]];
    }
    else if (type == WIRE_TYPE_OBJECT_DATA)
    {
        cls = [self readClass];
        if ([cls instancesRespondToSelector: @selector(initWithObjectInStream:)])
        {
            initWithStream = YES;
        }
        else if ([cls instancesRespondToSelector: @selector(initWithCoder:)])
        {
            initWithStream = NO;
        }
        else
        {
            [NSException raise: OLInputOutputException
                format: @"Instances of the class \"%s\" do not respond to either initWithObjectInStream: or initWithCoder:", cls->name];
        }
        object = [allocator allocateObject: cls];
        object = initWithStream ?
            [object initWithObjectInStream: self] :
            [object initWithCoder: self];
        object = [object awakeAfterUsingCoder: self];
        [sharedPointers addObject: object];
        OBJ_COND_AUTORELEASE(object);
    }
    else
    {
        [NSException raise: OLInputOutputException
            format: @"Found unexpected type in stream - \"%s\"",
            [self lookUpTypeName: type]];
    }
    return object;
}

- (NSString*) readStringAsUTF8
{
    [self verifyType: WIRE_TYPE_UTF8_DATA];
    return [stream readStringAsUTF8];
}

- (unsigned) systemVersion
{
    return systemVersion;
}

- (unsigned) versionForClassName: (NSString*)className
{
    id num = [classes valueForKey: className];

    return (num == nil) ? NSNotFound : [num intValue];
}

@end

@implementation OLObjectInStream (PrivateMethods)

#define OL_TYPE_NAME_ENTRY(t,n) \
    if (type == WIRE_TYPE_##t) \
        return #n
        
- (const char*) lookUpTypeName: (uint8_t)type
{
    OL_TYPE_NAME_ENTRY(HANDLE, handle);
    OL_TYPE_NAME_ENTRY(CLASS, class);
    OL_TYPE_NAME_ENTRY(END_CLASS, end class);
    OL_TYPE_NAME_ENTRY(STRING, string);
    OL_TYPE_NAME_ENTRY(INSTANCE, id);
    OL_TYPE_NAME_ENTRY(NIL, nil);
    OL_TYPE_NAME_ENTRY(DATA, data object);
    OL_TYPE_NAME_ENTRY(BOOL, BOOL);
    OL_TYPE_NAME_ENTRY(DOUBLE, double);
    OL_TYPE_NAME_ENTRY(FLOAT, float);
    OL_TYPE_NAME_ENTRY(INT, unsigned int);
    OL_TYPE_NAME_ENTRY(INT16, uint16_t);
    OL_TYPE_NAME_ENTRY(INT32, uint32_t);
    OL_TYPE_NAME_ENTRY(INT64, uint64_t);
    OL_TYPE_NAME_ENTRY(CHAR, unsigned char);
    OL_TYPE_NAME_ENTRY(SHORT, unsigned short);
    OL_TYPE_NAME_ENTRY(LONG, unsigned long);
    OL_TYPE_NAME_ENTRY(LONG_LONG, unsigned long long);
    OL_TYPE_NAME_ENTRY(SELECTOR, SEL);
    OL_TYPE_NAME_ENTRY(CHARPTR, char*);
    OL_TYPE_NAME_ENTRY(ARRAY, array);
    OL_TYPE_NAME_ENTRY(POINTER, void*);
    OL_TYPE_NAME_ENTRY(NULL, NULL);
    OL_TYPE_NAME_ENTRY(STRUCTURE, structure);
    OL_TYPE_NAME_ENTRY(OBJECT_DATA, object data);
    OL_TYPE_NAME_ENTRY(CLASS_NAME, class name);
    return "unknown";
}

- (void) verifyType: (uint8_t)type
{
    uint8_t foundType = [stream readByte];

    if (foundType != type)
    {
        [NSException raise: OLInputOutputException
            format: @"Expected to read a value of type \"%s\", but found a type of \"%s\"",
            [self lookUpTypeName: type], [self lookUpTypeName: foundType]];
    }
}

@end
