//
// $Id: ObjectOutStream.m,v 1.10 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 "OutStreamPackage.h"
#import "ObjectOutStream.h"
#import "Macros.h"
#import "PointerRefTable.h"
#import "ObjectReplaceTable.h"
#import "RunTime.h"
#import "Streamable.h"
#import <Foundation/NSException.h>
#import <Foundation/NSString.h>
#import <string.h>

@interface OLObjectOutStream (PrivateMethods)

- (void) writeHandle: (uint32_t)handle;

@end

@implementation OLObjectOutStream

+ (id) streamWithOutStream: (OLOutStream*)underStream
{
    OL_BEGIN_AUTO_CTOR(OLObjectOutStream)
        initWithOutStream: underStream
    OL_END_AUTO_CTOR;
}

- (id) initWithOutStream: (OLOutStream*)underStream
{
    return [self initWithOutStream: underStream allocator: [OLAllocator defaultAllocator]];
}

- (id) initWithOutStream: (OLOutStream*)underStream allocator: (OLAllocator*)alloc
{
    [super initWithOutStream: underStream];
    allocator = OBJ_RETAIN(alloc);
    pointerMap = [[allocator allocateObject: [OLPointerRefTable class]]
        initWithAllocator: allocator];
    replacementMap = [[allocator allocateObject: [OLObjectReplaceTable class]]
        initWithAllocator: allocator];
    [self writeHeader];
    return self;
}

- (void) dealloc
{
    [self close];
    [allocator deallocateObject: replacementMap];
    [allocator deallocateObject: pointerMap];
    OBJ_RELEASE(allocator);
    [super dealloc];
}

- (void) encodeBytes: (const void*)address length: (unsigned)numBytes
{
    [stream writeByte: WIRE_TYPE_BYTE_BLOCK];
    [stream encodeBytes: address length: numBytes];
}

- (void) encodeDataObject: (NSData*)data
{
    [stream writeByte: WIRE_TYPE_DATA];
    [stream encodeDataObject: data];
}

- (void) encodeObject: (id)object
{
    [self writeObject: object];
}

- (void) encodeValueOfObjCType: (const char*)valueType at: (const void*)address
{
    SEL stor;
    uint32_t handle;

    switch (*valueType)
    {
    case _C_CHR:
    case _C_UCHR:
        [stream writeByte: WIRE_TYPE_CHAR];
        [stream encodeValueOfObjCType: valueType at: address];
        break;
    case _C_SHT:
    case _C_USHT:
        [stream writeByte: WIRE_TYPE_SHORT];
        [stream encodeValueOfObjCType: valueType at: address];
        break;
    case _C_INT:
    case _C_UINT:
        [self writeInt: *(const int*)address];
        break;
    case _C_LNG:
    case _C_ULNG:
        [stream writeByte: WIRE_TYPE_LONG];
        [stream encodeValueOfObjCType: valueType at: address];
        break;
    case _C_LNG_LNG:
    case _C_ULNG_LNG:
        [stream writeByte: WIRE_TYPE_LONG_LONG];
        [stream encodeValueOfObjCType: valueType at: address];
        break;
    case _C_FLT:
        [self writeFloat: *(const float*)address];
        break;
    case _C_DBL:
        [self writeDouble: *(const double*)address];
        break;
    case _C_ID:
        [self writeObject: *(id*)address];
        break;
    case _C_CLASS:
        [self writeClass: *(Class*)address];
        break;
    case _C_SEL:
        stor = *(SEL*)address;
        if (stor == NULL)
        {
            [stream writeByte: WIRE_TYPE_SELECTOR];
            [stream encodeValueOfObjCType: valueType at: address];
        }
        else
        {
            handle = [pointerMap lookUp: (void*)stor];
            if (handle == UINT32_MAX)
            {
                [pointerMap assign: (void*)stor];
                [stream writeByte: WIRE_TYPE_SELECTOR];
                [stream encodeValueOfObjCType: valueType at: address];
            }
            else
            {
                [self writeHandle: handle];
            }
        }
        break;
    case _C_CHARPTR:
        [stream writeByte: WIRE_TYPE_CHARPTR];
        [stream encodeValueOfObjCType: valueType at: address];
        break;
    case _C_ARY_B:
        [stream writeByte: WIRE_TYPE_ARRAY];
        [stream encodeValueOfObjCType: valueType at: address];
        break;
    case _C_PTR:
        [stream writeByte: WIRE_TYPE_POINTER];
        [stream encodeValueOfObjCType: valueType at: address];
        break;
    case _C_STRUCT_B:
        [stream writeByte: WIRE_TYPE_STRUCTURE];
        [stream encodeValueOfObjCType: valueType at: address];
        break;
    default:
        [stream encodeValueOfObjCType: valueType at: address];
    }
}

- (void) replaceObject: (id)obj withObject: (id)rep
{
    [replacementMap assign: obj replacement: rep];
}

- (unsigned) systemVersion
{
    return OL_STREAM_VERSION;
}

- (unsigned) versionForClassName: (NSString*)className
{
    Class cls = NSClassFromString(className);

    return (cls == nil) ? NSNotFound : class_get_version(cls);
}

- (void) writeBool: (BOOL)value
{
    [stream writeByte: WIRE_TYPE_BOOL];
    [stream writeBool: value];
}

- (void) writeClass: (Class)cls
{
    Class spr;
    uint16_t len;
    uint32_t handle = [pointerMap lookUp: cls];

    [stream writeByte: WIRE_TYPE_CLASS];
    if (handle != UINT32_MAX)
    {
        [self writeHandle: handle];
    }
    else
    {
        while (YES)
        {
            [pointerMap assign: cls];
            [stream writeByte: WIRE_TYPE_CLASS_NAME];
            len = strlen(cls->name);
            [stream writeInt16: len];
            [stream completelyWriteBytes: cls->name count: len];
            [stream writeInt: class_get_version(cls)];
            spr = class_get_super_class(cls);
            if (spr == cls || spr == NULL || [pointerMap lookUp: spr] != UINT32_MAX)
                break;
            cls = spr;
        }
        [stream writeByte: WIRE_TYPE_END_CLASS];
    }
}

- (void) writeDouble: (double)value
{
    [stream writeByte: WIRE_TYPE_DOUBLE];
    [stream writeDouble: value];
}

- (void) writeFloat: (float)value
{
    [stream writeByte: WIRE_TYPE_FLOAT];
    [stream writeFloat: value];
}

- (void) writeHeader
{
    [stream writeInt32: OL_STREAM_MAGIC];
    [stream writeInt32: OL_STREAM_VERSION];
}

- (void) writeInt: (unsigned)value
{
    [stream writeByte: WIRE_TYPE_INT];
    [stream writeInt: value];
}

- (void) writeInt16: (uint16_t)value
{
    [stream writeByte: WIRE_TYPE_INT16];
    [stream writeInt16: value];
}

- (void) writeInt32: (uint32_t)value
{
    [stream writeByte: WIRE_TYPE_INT32];
    [stream writeInt32: value];
}

- (void) writeInt64: (uint64_t)value
{
    [stream writeByte: WIRE_TYPE_INT64];
    [stream writeInt64: value];
}

- (void) writeObject: (id)object
{
    uint32_t handle;
    Class cls;
    id origObject;

    object = [replacementMap lookUp: object];
    [stream writeByte: WIRE_TYPE_INSTANCE];
    if (object == nil)
    {
        [stream writeByte: WIRE_TYPE_NIL];
    }
    else
    {
        handle = [pointerMap lookUp: object];
        if (handle != UINT32_MAX)
        {
            [self writeHandle: handle];
        }
        else
        {
            if ([object respondsToSelector: @selector(replacementObjectForCoder:)])
            {
                origObject = object;
                object = [object replacementObjectForCoder: self];
                if (origObject != object)
                {
                    handle = [pointerMap lookUp: object];
                    if (handle != UINT32_MAX)
                    {
                        [self writeHandle: handle];
                        return;
                    }
                }
            }
            [stream writeByte: WIRE_TYPE_OBJECT_DATA];
            cls = [object respondsToSelector: @selector(classForCoder)] ?
                [object classForCoder] :
                [object class];
            [self writeClass: cls];
            if ([object respondsToSelector: @selector(writeSelfToStream:)])
            {
                [object writeSelfToStream: self];
            }
            else if ([object respondsToSelector: @selector(encodeWithCoder:)])
            {
                [object encodeWithCoder: self];
            }
            else
            {
                [NSException raise: OLInputOutputException
                    format: @"The object of type %s does not respond to writeSelfToStream: or to encodeWithCoder:"];
            }
            [pointerMap assign: object];
        }
    }
}

- (void) writeStringAsUTF8: (NSString*)str
{
    [stream writeByte: WIRE_TYPE_UTF8_DATA];
    [stream writeStringAsUTF8: str];
}

@end

@implementation OLObjectOutStream (PrivateMethods)

- (void) writeHandle: (uint32_t)handle
{
    [stream writeByte: WIRE_TYPE_HANDLE];
    [stream writeInt32: handle];
}

@end
