/*
 * Copyright (c) 2008-2023 Jonathan Schleifer <js@nil.im>
 *
 * All rights reserved.
 *
 * This file is part of ObjFW. It may be distributed under the terms of the
 * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
 * the packaging of this file.
 *
 * Alternatively, it may be distributed under the terms of the GNU General
 * Public License, either version 2 or 3, which can be found in the file
 * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
 * file.
 */

#include "config.h"

#include <errno.h>

#import "OFMemoryStream.h"

#import "OFInvalidArgumentException.h"
#import "OFOutOfRangeException.h"
#import "OFWriteFailedException.h"
#import "OFSeekFailedException.h"

@implementation OFMemoryStream
+ (instancetype)streamWithMemoryAddress: (void *)address
				   size: (size_t)size
			       writable: (bool)writable
{
	return [[[self alloc] initWithMemoryAddress: address
					       size: size
					   writable: writable] autorelease];
}

- (instancetype)init
{
	OF_INVALID_INIT_METHOD
}

- (instancetype)initWithMemoryAddress: (void *)address
				 size: (size_t)size
			     writable: (bool)writable
{
	self = [super init];

	@try {
		if (size > SSIZE_MAX || (ssize_t)size != (OFStreamOffset)size)
			@throw [OFOutOfRangeException exception];

		_address = address;
		_size = size;
		_writable = writable;
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (size_t)lowlevelReadIntoBuffer: (void *)buffer length: (size_t)length
{
	if (SIZE_MAX - _position < length || _position + length > _size)
		length = _size - _position;

	memcpy(buffer, _address + _position, length);
	_position += length;

	return length;
}

- (size_t)lowlevelWriteBuffer: (const void *)buffer length: (size_t)length
{
	size_t bytesWritten = length;

	if (!_writable)
		@throw [OFWriteFailedException exceptionWithObject: self
						   requestedLength: length
						      bytesWritten: 0
							     errNo: EBADF];

	if (SIZE_MAX - _position < length || _position + length > _size)
		bytesWritten = _size - _position;

	memcpy(_address + _position, buffer, bytesWritten);
	_position += bytesWritten;

	if (bytesWritten != length)
		@throw [OFWriteFailedException exceptionWithObject: self
						   requestedLength: length
						      bytesWritten: bytesWritten
							     errNo: EFBIG];

	return bytesWritten;
}

- (bool)lowlevelIsAtEndOfStream
{
	return (_position == _size);
}

- (OFStreamOffset)lowlevelSeekToOffset: (OFStreamOffset)offset
				whence: (OFSeekWhence)whence
{
	OFStreamOffset new;

	switch (whence) {
	case OFSeekSet:
		new = offset;
		break;
	case OFSeekCurrent:
		new = (OFStreamOffset)_position + offset;
		break;
	case OFSeekEnd:
		new = (OFStreamOffset)_size + offset;
		break;
	default:
		@throw [OFInvalidArgumentException exception];
	}

	if (new < 0 || new > (OFStreamOffset)_size)
		@throw [OFSeekFailedException exceptionWithStream: self
							   offset: offset
							   whence: whence
							    errNo: EINVAL];

	return (_position = (size_t)new);
}
@end
