//
// $Id: GzipOutStream.m,v 1.6 2005/05/18 06:46:52 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 "GzipOutStream.h"
#import "OutStreamPackage.h"
#import "ZlibOutStreamPackage.h"
#import "Macros.h"
#import "ByteOrder.h"
#import "DataOutStream.h"
#import <Foundation/NSString.h>
#import <Foundation/NSPathUtilities.h>
#import <Foundation/NSData.h>
#import <Foundation/NSException.h>
#import <sys/types.h>
#import <sys/stat.h>
#import <zlib.h>
#import <string.h>
#import <limits.h>
#import <time.h>

@interface OLGzipOutStream (PrivateMethods)

- (void) writeHeader;
- (void) writeLE16: (OLOutStream*)strm value: (uint16_t)val;
- (void) writeLE32: (OLOutStream*)strm value: (uint32_t)val;

@end

@implementation OLGzipOutStream

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

+ (id) streamWithOutStream: (OLOutStream*)underStream compressionLevel: (int)level
{
    OL_BEGIN_AUTO_CTOR(OLGzipOutStream)
        initWithOutStream: underStream compressionLevel: level
    OL_END_AUTO_CTOR;
}

- (id) initWithOutStream: (OLOutStream*)underStream compressionLevel: (int)level bufferSize: (unsigned)size writeZlibHeader: (BOOL)zlibHeader allocator: (OLAllocator*)alloc
{
    [super initWithOutStream: underStream compressionLevel: level
        bufferSize: size writeZlibHeader: NO allocator: alloc];
    crc = crc32(0, Z_NULL, 0);
    commentData = NULL;
    fileNameData = NULL;
    modTime = 0;
    extraFields = nil;
    headerWritten = NO;
    trailerWritten = NO;
    return self;
}

- (void) dealloc
{
    [self close];
    [allocator deallocateBytes: commentData];
    [allocator deallocateBytes: fileNameData];
    [allocator deallocateObject: extraFields];
    [super dealloc];
}

- (void) addExtraField: (const char*)identifier withData: (NSData*)data
{
    if (strlen(identifier) != 2)
    {
        [NSException raise: OLInputOutputException
            format: @"Illegal extra field identifier"];
    }
    if (extraFields == nil)
    {
        extraFields = [[allocator allocateObject: [OLDataOutStream class]]
            initWithAllocator: allocator];
    }
    [extraFields writeBytes: identifier count: 2];
    [self writeLE16: extraFields value: [data length]];
    [extraFields writeBytes: [data bytes] count: [data length]];
}

- (void) close
{
    if (!trailerWritten)
    {
        [self finish];
        [self writeLE32: stream value: crc];
        [self writeLE32: stream value: zstream->total_in];
        [super close];
        trailerWritten = YES;
    }
}

- (void) markModificationTime
{
    modTime = time(NULL);
    if (modTime == UINT_MAX)
        modTime = 0;
}

- (void) setComment: (NSString*)comment
{
    NSData* data;

    [allocator deallocateBytes: commentData];
    commentData = NULL;
    if ([comment length] != 0)
    {
        data = [comment dataUsingEncoding: NSISOLatin1StringEncoding
            allowLossyConversion: YES];
        commentData = [allocator allocateBytes: [data length] + 1];
        [data getBytes: commentData];
        commentData[[data length]] = 0;
    }
}

- (void) setOriginalFileName: (NSString*)name includeModificationTime: (BOOL)mtime
{
    NSData* data;
    struct stat statBuf;
    char fileNameBuf[PATH_MAX + 1];

    [allocator deallocateBytes: fileNameData];
    fileNameData = NULL;
    modTime = 0;
    if ([name length] != 0)
    {
        data = [[name lastPathComponent] dataUsingEncoding: NSISOLatin1StringEncoding
            allowLossyConversion: YES];
        fileNameData = [allocator allocateBytes: [data length] + 1];
        [data getBytes: fileNameData];
        fileNameData[[data length]] = 0;
        if (mtime)
        {
            if ([name getFileSystemRepresentation: fileNameBuf maxLength: PATH_MAX])
            {
                if (stat(fileNameBuf, &statBuf) == 0)
                {
                    modTime = statBuf.st_mtime;
                }
            }
        }
    }
}

- (unsigned) writeBytes: (const uint8_t*)bytes count: (unsigned)num
{
    unsigned written;

    if (!headerWritten)
    {
        [self writeHeader];
        headerWritten = YES;
    }
    written = [super writeBytes: bytes count: num];
    crc = crc32(crc, bytes, written);
    return written;
}

@end

@implementation OLGzipOutStream (PrivateMethods)

- (void) writeHeader
{
    //
    // Note: As much as you may want to, you can't include the header CRC here.
    // Older versions of gzip will choke. Believe me. I've tried it.
    //
    uint8_t flags = 0;

    [stream writeByte: GZ_ID1];
    [stream writeByte: GZ_ID2];
    [stream writeByte: Z_DEFLATED];
    if (fileNameData != NULL)
        flags |= GZ_FNAME;
    if (commentData != NULL)
        flags |= GZ_FCOMMENT;
    if (extraFields != nil)
        flags |= GZ_FEXTRA;
    [stream writeByte: flags];
    [self writeLE32: stream value: modTime];
    [stream writeByte: 0];
    [stream writeByte: GZ_OS_UNKNOWN];
    if (extraFields != nil)
    {
        [self writeLE16: stream value: [[extraFields data] length]];
        [stream writeBytes: [[extraFields data] bytes] count: [[extraFields data] length]];
    }
    if (fileNameData != NULL)
        [stream writeBytes: fileNameData count: strlen(fileNameData) + 1];
    if (commentData != NULL)
        [stream writeBytes: commentData count: strlen(commentData) + 1];
}

- (void) writeLE16: (OLOutStream*)strm value: (uint16_t)val
{
    uint16_t swapped = H16_TO_L(val);

    [strm completelyWriteBytes: (uint8_t*)&swapped count: 2];
}

- (void) writeLE32: (OLOutStream*)strm value: (uint32_t)val
{
    uint32_t swapped = H32_TO_L(val);

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

@end
