//
// $Id: OutStream.m,v 1.9 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 "Macros.h"
#import "RunTime.h"
#import "Constants.h"
#import "ByteOrder.h"
#import <Foundation/NSException.h>
#import <Foundation/NSString.h>
#import <Foundation/NSData.h>
#import <string.h>
#import <stdlib.h>

@interface OLOutStream (PrivateMethods)

- (void) writeStringImpl: (const char*)str;
- (void) writeDoubleImpl: (double)value;
- (void) writeFloatImpl: (float)value;
- (void) writeIntImpl: (unsigned)value;
- (void) writeInt16Impl: (uint16_t)value;

@end

uint64_t __swap64(uint64_t value)
{
    uint64_t result;
    uint8_t* dest = (uint8_t*)&result;
    const uint8_t* src = (const uint8_t*)&value;

    dest[0] = src[7];
    dest[1] = src[6];
    dest[2] = src[5];
    dest[3] = src[4];
    dest[4] = src[3];
    dest[5] = src[2];
    dest[6] = src[1];
    dest[7] = src[0];
    return result;
}

@implementation OLOutStream

- (BOOL) allowsKeyedCoding
{
    return NO;
}

- (void) close
{
}

- (void) encodeBytes: (const void*)address length: (unsigned)numBytes
{
    [self writeIntImpl: numBytes];
    [self completelyWriteBytes: address count: numBytes];
}

- (void) encodeDataObject: (NSData*)data
{
    unsigned len = [data length];

    [self writeIntImpl: len];
    [self completelyWriteBytes: [data bytes] count: len];
}

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

- (void) encodeValueOfObjCType: (const char*)valueType at: (const void*)address
{
    struct objc_struct_layout slay;
    uint32_t result32;
    uint64_t result64;
    const char* selName;
    const char* selTypes;
    uint16_t nameLen;
    uint16_t typesLen;
    SEL stor;
    char* chars;
    char* endOfNum;
    long arrayCount;
    long i;
    void* pointer;
    int offset;
    const char* fieldType;
    int typeSize;

    switch (*valueType)
    {
    case _C_CHR:
    case _C_UCHR:
        [self writeByte: *(const char*)address];
        break;
    case _C_SHT:
    case _C_USHT:
        [self writeInt16Impl: *(const uint16_t*)address];
        break;
    case _C_INT:
    case _C_UINT:
        [self writeIntImpl: *(const int*)address];
        break;
    case _C_LNG:
    case _C_ULNG:
        result32 = H32_TO_N(*(const long*)address);
        [self completelyWriteBytes: (uint8_t*)&result32 count: 4];
        break;
    case _C_LNG_LNG:
    case _C_ULNG_LNG:
        result64 = H64_TO_N(*(const long long*)address);
        [self completelyWriteBytes: (uint8_t*)&result64 count: 8];
        break;
    case _C_FLT:
        [self writeFloatImpl: *(const float*)address];
        break;
    case _C_DBL:
        [self writeDoubleImpl: *(const double*)address];
        break;
    case _C_SEL:
        stor = *(SEL*)address;
        if (stor == NULL)
        {
            [self writeInt16Impl: 0];
            [self writeInt16Impl: 0];
        }
        else
        {
            selName = sel_get_name(stor);
            selTypes = sel_get_type(stor);
            nameLen = (selName == NULL) ? 0 : strlen(selName);
            typesLen = (selTypes == NULL) ? 0 : strlen(selTypes);
            [self writeInt16Impl: nameLen];
            if (nameLen != 0)
                [self completelyWriteBytes: (uint8_t*)selName count: nameLen];
            [self writeInt16Impl: typesLen];
            if (typesLen != 0)
                [self completelyWriteBytes: (uint8_t*)selTypes count: nameLen];
        }
        break;
    case _C_CHARPTR:
        chars = *(char**)address;
        if (chars == NULL)
        {
            [self writeInt16Impl: 0];
        }
        else
        {
            [self writeStringImpl: chars];
        }
        break;
    case _C_ARY_B:
        arrayCount = strtol(++valueType, &endOfNum, 10);
        /* I used to use encodeArrayOfObjCType:count:at: but the Cocoa version
         * of that message doesn't handle the type right!!! I don't know why...
         */
        typeSize = objc_sizeof_type(endOfNum);
        for (i = 0; i < arrayCount; i++)
            [self encodeValueOfObjCType: endOfNum at: address + (typeSize * i)];
        break;
    case _C_PTR:
        pointer = *(void**)address;
        if (pointer == NULL)
        {
            [NSException raise: NSInvalidArgumentException
                format: @"Won't encode a NULL pointer"];
        }
        else
        {
            [self encodeValueOfObjCType: ++valueType at: pointer];
        }
        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 encodeValueOfObjCType: fieldType at: (const uint8_t*)address + offset];
        }
        break;
    case _C_ID:
        [NSException raise: NSInvalidArgumentException
            format: @"Objects can only be encoded to OLObjectOutStream"];
    case _C_CLASS:
        [NSException raise: NSInvalidArgumentException
            format: @"Classes can only be encoded to OLObjectOutStream"];
    default:
        [NSException raise: NSInvalidArgumentException
            format: @"Will not encode type \"%s\"", valueType];
    }
}

- (void) flush
{
}

- (void) writeBool: (BOOL)value
{
    [self writeByte: value ? 1 : 0];
}

- (void) writeByte: (uint8_t)byte
{
    [self completelyWriteBytes: &byte count: 1];
}

- (unsigned) writeBytes: (const uint8_t*)bytes count: (unsigned)count
{
    [self doesNotRecognizeSelector: _cmd];
    return 0;
}

- (void) writeDouble: (double)value
{
    [self writeDoubleImpl: value];
}

- (void) writeFloat: (float)value
{
    [self writeFloatImpl: value];
}

- (void) writeInt: (unsigned)value
{
    [self writeIntImpl: value];
}

- (void) writeInt16: (uint16_t)value
{
    [self writeInt16Impl: value];
}

- (void) writeInt32: (uint32_t)value
{
    uint32_t result = H32_TO_N(value);

    [self completelyWriteBytes: (uint8_t*)&result count: 4];
}

- (void) writeInt64: (uint64_t)value
{
    uint64_t result = H64_TO_N(value);

    [self completelyWriteBytes: (uint8_t*)&result count: 8];
}

- (void) writeStringAsUTF8: (NSString*)str
{
    [self writeStringImpl: [str UTF8String]];
}

@end

@implementation OLOutStream (PrivateMethods)

- (void) writeStringImpl: (const char*)str
{
    size_t len = strlen(str);
    uint16_t chunkCount;
    uint16_t i;
    uint16_t amountToWrite;

    if (len > MAX_SINGLE_CHUNK_STR)
    {
        chunkCount = (len / (UINT16_MAX + 1) +
            ((len % (UINT16_MAX + 1)) ? 1 : 0)) | BIG_STRING_TELL_BIT;
        [self writeInt16Impl: chunkCount];
        chunkCount &= ~BIG_STRING_TELL_BIT;
        for (i = 0; i < chunkCount; i++)
        {
            amountToWrite = MIN(len, UINT16_MAX);
            [self writeInt16Impl: amountToWrite];
            [self completelyWriteBytes: str count: amountToWrite];
            str += amountToWrite;
            len -= amountToWrite;
        }
    }
    else
    {
        [self writeInt16Impl: len];
        [self completelyWriteBytes: str count: len];
    }
}

- (void) writeDoubleImpl: (double)value
{
    uint64_t swapped = H64_TO_N(*(uint64_t*)&value);

    [self completelyWriteBytes: (uint8_t*)&swapped count: 8];
}

- (void) writeFloatImpl: (float)value
{
    uint32_t swapped = H32_TO_N(*(uint32_t*)&value);

    [self completelyWriteBytes: (uint8_t*)&swapped count: 4];
}

- (void) writeIntImpl: (unsigned)value
{
    uint32_t swapped = H32_TO_N(value);

    [self completelyWriteBytes: (uint8_t*)&swapped count: 4];
}

- (void) writeInt16Impl: (uint16_t)value
{
    uint16_t result = H16_TO_N(value);

    [self completelyWriteBytes: (uint8_t*)&result count: 2];
}

@end

@implementation OLOutStream (PackageMethods)

- (void) completelyWriteBytes: (const uint8_t*)buffer count: (unsigned)count
{
    unsigned total = 0;

    while (total < count)
        total += [self writeBytes: buffer + total count: count - total];
}

@end
