//
// $Id: InStream.m,v 1.12 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 "InStreamPackage.h"
#import "RunTime.h"
#import "Macros.h"
#import "PointerData.h"
#import "ByteOrder.h"
#import <Foundation/NSString.h>
#import <Foundation/NSException.h>
#import <Foundation/NSData.h>
#import <stdlib.h>
#import <limits.h>

extern uint64_t __swap64(uint64_t);

@interface OLInStream (PrivateMethods)

- (double) readDoubleImpl;
- (float) readFloatImpl;
- (unsigned) readIntImpl;
- (uint16_t) readInt16Impl;

@end

@implementation OLInStream

- (id) init
{
    return [self initWithAllocator: [OLAllocator defaultAllocator]];
}

- (id) initWithAllocator: (OLAllocator*)alloc
{
    [super init];
    if (![alloc supportsAutorelease])
    {
        allocator = nil;
        [NSException raise: NSInvalidArgumentException
            format: @"The allocator does not support autorelease"];
    }
    allocator = OBJ_RETAIN(alloc);
    return self;
}

- (void) dealloc
{
    OBJ_RELEASE(allocator);
    [super dealloc];
}

- (BOOL) allowsKeyedCoding
{
    return NO;
}

- (void) close
{
}

- (void*) decodeBytesWithReturnedLength: (unsigned*)numBytes
{
    NSMutableData* data;

    *numBytes = [self readIntImpl];
    data = [[allocator allocateObject: [NSMutableData class]]
        initWithLength: *numBytes];
    [self completelyReadBytes: [data mutableBytes] count: *numBytes];
    OBJ_COND_AUTORELEASE(data);
    return [data mutableBytes];
}

- (NSData*) decodeDataObject
{
    NSMutableData* data;
    unsigned numBytes;

    numBytes = [self readIntImpl];
    data = [[allocator allocateObject: [NSMutableData class]]
        initWithLength: numBytes];
    [self completelyReadBytes: [data mutableBytes] count: numBytes];
    return OBJ_COND_AUTORELEASE(data);
}

- (id) decodeObject
{
    [NSException raise: OLInputOutputException
        format: @"Objects can only be read to OLObjectInStream"];
    return nil;
}

- (void) decodeValueOfObjCType: (const char*)valueType at: (void*)address
{
    union MixedResults
    {
        uint8_t result8;
        uint16_t result16;
        uint32_t result32;
        uint64_t result64;
    };
    uint16_t nameLen;
    uint16_t typesLen;
    uint8_t* nameBuf;
    uint8_t* typesBuf;
    NSString* nameStr;
    NSString* typesStr;
    uint16_t strLen;
    uint16_t chunkCount;
    uint16_t i;
    union MixedResults results;
    long arrayCount;
    char* endOfNum;
    struct objc_struct_layout slay;
    int offset;
    const char* fieldType;
    int typeSize;
    long cur;
    OLPointerData* pointerData;

    switch (*valueType)
    {
    case _C_CHR:
    case _C_UCHR:
        *(uint8_t*)address = [self readByte];
        break;
    case _C_SHT:
    case _C_USHT:
        [self completelyReadBytes: &results.result8 count: 2];
        *(short*)address = N16_TO_H(results.result16);
        break;
    case _C_INT:
    case _C_UINT:
        *(unsigned*)address = [self readIntImpl];
        break;
    case _C_LNG:
    case _C_ULNG:
        [self completelyReadBytes: &results.result8 count: 4];
        *(long*)address = N32_TO_H(results.result32);
        break;
    case _C_LNG_LNG:
    case _C_ULNG_LNG:
        [self completelyReadBytes: &results.result8 count: 8];
        *(long long*)address = N64_TO_H(results.result64);
        break;
    case _C_FLT:
        *(float*)address = [self readFloatImpl];
        break;
    case _C_DBL:
        *(double*)address = [self readDoubleImpl];
        break;
    case _C_SEL:
        nameLen = [self readInt16Impl];
        if (nameLen != 0)
        {
            nameBuf = [allocator allocateBytes: nameLen + 1];
            [self completelyReadBytes: nameBuf count: nameLen];
            nameBuf[nameLen] = 0;
        }
        else
        {
            nameBuf = NULL;
        }
        typesLen = [self readInt16Impl];
        if (typesLen != 0)
        {
            typesBuf = [allocator allocateBytes: nameLen + 1];
            [self completelyReadBytes: typesBuf count: nameLen];
            typesBuf[nameLen] = 0;
        }
        else
        {
            typesBuf = NULL;
        }
        if (typesLen != 0)
            *(SEL*)address = sel_get_typed_uid(nameBuf, typesBuf);
        else
            *(SEL*)address = sel_get_any_typed_uid(nameBuf);
        if (*(SEL*)address == NULL)
        {
            if (typesLen != 0)
                *(SEL*)address = sel_register_typed_name(nameBuf, typesBuf);
            else
                *(SEL*)address = sel_register_name(nameBuf);
            if (*(SEL*)address == NULL)
            {
                nameStr = [NSString stringWithUTF8String: nameBuf];
                typesStr = [NSString stringWithUTF8String: typesBuf];
                if (nameBuf != NULL)
                    [allocator deallocateBytes: nameBuf];
                if (typesBuf != NULL)
                    [allocator deallocateBytes: typesBuf];
                [NSException raise: OLInputOutputException
                    format: @"Unable to register the selector \"%@\" with types \"%@\"",
                    nameStr, typesStr];
            }
        }
        if (nameBuf != NULL)
            [allocator deallocateBytes: nameBuf];
        if (typesBuf != NULL)
            [allocator deallocateBytes: typesBuf];
        break;
    case _C_CHARPTR:
        pointerData = [[allocator allocateObject: [OLPointerData class]]
            initWithAllocator: allocator];
        strLen = [self readInt16Impl];
        if (strLen & BIG_STRING_TELL_BIT)
        {
            chunkCount = strLen & ~BIG_STRING_TELL_BIT;
            for (i = 0; i < chunkCount; i++)
                [pointerData readFromStream: self count: [self readInt16Impl]];
        }
        else
        {
            [pointerData readFromStream: self count: strLen];
        }
        [pointerData nullTerminate];
        *(char**)address = [pointerData bytes];
        OBJ_COND_AUTORELEASE(pointerData);
        break;
    case _C_ARY_B:
        arrayCount = strtol(++valueType, &endOfNum, 10);
        typeSize = objc_sizeof_type(endOfNum);
        for (cur = 0; cur < arrayCount; cur++)
            [self decodeValueOfObjCType: endOfNum at: address + (typeSize * cur)];
        break;
    case _C_PTR:
        pointerData = [[allocator allocateObject: [OLPointerData class]]
            initWithAllocator: allocator size: objc_sizeof_type(++valueType)];
        *(void**)address = [pointerData bytes];
        [self decodeValueOfObjCType: valueType at: [pointerData bytes]];
        OBJ_COND_AUTORELEASE(pointerData);
        break;
    case _C_STRUCT_B:
        objc_layout_structure(valueType, &slay);
        while (objc_layout_structure_next_member(&slay))
        {
            objc_layout_structure_get_info(&slay, &offset, NULL, &fieldType);
            [self decodeValueOfObjCType: fieldType at: (uint8_t*)address + offset];
        }
        break;
    case _C_ID:
        [NSException raise: NSInvalidArgumentException
            format: @"Objects can only be decoded by OLObjectInStream"];
    case _C_CLASS:
        [NSException raise: NSInvalidArgumentException
            format: @"Classes can only be decoded by OLObjectInStream"];
    default:
        [NSException raise: NSInvalidArgumentException
            format: @"Will not decode type \"%s\"", valueType];
    }
}

- (BOOL) readBool
{
    uint8_t value;

    value = [self readByte];
    return value ? YES : NO;
}

- (uint8_t) readByte
{
    uint8_t byte;

    [self completelyReadBytes: &byte count: 1];
    return byte;
}

- (unsigned) readBytes: (uint8_t*)buffer count: (unsigned)max
{
    [self doesNotRecognizeSelector: _cmd];
    return UINT_MAX;
}

- (double) readDouble
{
    return [self readDoubleImpl];
}

- (float) readFloat
{
    return  [self readFloatImpl];
}

- (unsigned) readInt
{
    return [self readIntImpl];
}

- (uint16_t) readInt16
{
    return [self readInt16Impl];
}

- (uint32_t) readInt32
{
    uint32_t value;

    [self completelyReadBytes: (uint8_t*)&value count: 4];
    return N32_TO_H(value);
}

- (uint64_t) readInt64
{
    uint64_t value;

    [self completelyReadBytes: (uint8_t*)&value count: 8];
    return N64_TO_H(value);
}

- (NSString*) readStringAsUTF8
{
    uint16_t count;
    uint16_t count2;
    uint16_t i;
    NSMutableData* buffer;
    unsigned oldLength;
    NSString* value;
    uint8_t* simpleBuffer;
    uint8_t zero = 0;

    count = [self readInt16Impl];
    if (count & BIG_STRING_TELL_BIT)
    {
        buffer = [[allocator allocateObject: [NSMutableData class]] init];
        count &= ~BIG_STRING_TELL_BIT;
        for (i = 0; i < count; i++)
        {
            count2 = [self readInt16Impl];
            oldLength = [buffer length];
            [buffer increaseLengthBy: count2];
            [self completelyReadBytes: (uint8_t*)[buffer mutableBytes] + oldLength count: count2];
        }
        [buffer appendBytes: &zero length: 1];
        value = [NSString stringWithUTF8String: [buffer bytes]];
        value = [[allocator allocateObject: [NSString class]]
            initWithUTF8String: [buffer bytes]];
        [allocator deallocateObject: buffer];
    }
    else
    {
        simpleBuffer = [allocator allocateBytes: count + 1];
        [self completelyReadBytes: simpleBuffer count: count];
        simpleBuffer[count] = 0;
        value = [[allocator allocateObject: [NSString class]]
            initWithUTF8String: simpleBuffer];
        [allocator deallocateBytes: simpleBuffer];
    }
    return OBJ_COND_AUTORELEASE(value);
}

@end

@implementation OLInStream (PackageMethods)

- (OLAllocator*) allocator
{
    return allocator;
}

- (void) completelyReadBytes: (uint8_t*)buffer count: (unsigned)count
{
    unsigned cur;
    unsigned total = 0;

    while (total < count)
    {
        cur = [self readBytes: buffer + total count: count - total];
        if (cur == UINT_MAX)
            [NSException raise: OLEndOfStreamException format: @""];
        total += cur;
    }
}

@end

@implementation OLInStream (PrivateMethods)

- (double) readDoubleImpl
{
    uint64_t value;

    [self completelyReadBytes: (uint8_t*)&value count: 8];
    value = N64_TO_H(value);

    return *(double*)&value;
}

- (float) readFloatImpl
{
    uint32_t value;

    [self completelyReadBytes: (uint8_t*)&value count: 4];
    value = N32_TO_H(value);

    return *(float*)&value;
}

- (unsigned) readIntImpl
{
    uint32_t value;

    [self completelyReadBytes: (uint8_t*)&value count: 4];
    return N32_TO_H(value);
}

- (uint16_t) readInt16Impl
{
    uint16_t value;

    [self completelyReadBytes: (uint8_t*)&value count: 2];
    return N16_TO_H(value);
}

@end
