/*
 * Copyright (c) 2008-2026 Jonathan Schleifer <js@nil.im>
 *
 * All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3.0 only,
 * as published by the Free Software Foundation.
 *
 * This program 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
 * version 3.0 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3.0 along with this program. If not, see
 * <https://www.gnu.org/licenses/>.
 */

#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 objc_autoreleaseReturnValue(
	    [[self alloc] initWithMemoryAddress: address
					   size: size
				       writable: writable]);
}

- (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) {
		objc_release(self);
		@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
