/*****************************************************************************/
// Copyright 2006-2019 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE:  Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/

#include "dng_memory_stream.h"

#include "dng_bottlenecks.h"
#include "dng_exceptions.h"
#include "dng_safe_arithmetic.h"
#include "dng_utils.h"

/*****************************************************************************/

dng_memory_stream::dng_memory_stream (dng_memory_allocator &allocator,
									  dng_abort_sniffer *sniffer,
						   			  uint32 pageSize)

	:	dng_stream (sniffer,
					kDefaultBufferSize,
					kDNGStreamInvalidOffset)

	,	fAllocator (allocator)
	,	fPageSize  (pageSize )

	,	fPageCount      (0)
	,	fPagesAllocated (0)
	,	fPageList       (NULL)

	,	fMemoryStreamLength (0)

    ,   fLengthLimit (0)

	{

	}

/*****************************************************************************/

dng_memory_stream::~dng_memory_stream ()
	{

	if (fPageList)
		{

		for (uint32 index = 0; index < fPageCount; index++)
			{

			delete fPageList [index];

			}

		free (fPageList);

		}

	}

/*****************************************************************************/

uint64 dng_memory_stream::DoGetLength ()
	{

	return fMemoryStreamLength;

	}

/*****************************************************************************/

void dng_memory_stream::DoRead (void *data,
							    uint32 count,
							    uint64 offset)
	{

	if (offset + count > fMemoryStreamLength)
		{

		ThrowEndOfFile ();

		}

	uint64 baseOffset = offset;

	while (count)
		{

		uint32 pageIndex  = (uint32) (offset / fPageSize);
		uint32 pageOffset = (uint32) (offset % fPageSize);

		uint32 blockCount = Min_uint32 (fPageSize - pageOffset, count);

		const uint8 *sPtr = fPageList [pageIndex]->Buffer_uint8 () +
						    pageOffset;

		uint8 *dPtr = ((uint8 *) data) + (uint32) (offset - baseOffset);

		DoCopyBytes (sPtr, dPtr, blockCount);

		offset += blockCount;
		count  -= blockCount;

		}

	}

/*****************************************************************************/

void dng_memory_stream::DoSetLength (uint64 length)
	{

    if (fLengthLimit && length > fLengthLimit)
        {

        Throw_dng_error (dng_error_end_of_file,
                         "dng_memory_stream::fLengthLimit",
                         NULL,
                         true);

        }

	while (length > fPageCount * (uint64) fPageSize)
		{

		if (fPageCount == fPagesAllocated)
			{

			uint32 newSizeTemp1 = 0;
			uint32 newSizeTemp2 = 0;

			if (!SafeUint32Add  (fPagesAllocated, 32u, &newSizeTemp1) ||
				!SafeUint32Mult (fPagesAllocated,  2u, &newSizeTemp2))
				{
				ThrowOverflow ("Arithmetic overflow in DoSetLength");
				}

			uint32 newSize = Max_uint32 (newSizeTemp1,
										 newSizeTemp2);

			uint32 numBytes;

			if (!SafeUint32Mult (newSize,
								 sizeof (dng_memory_block *),
								 &numBytes))
				{
				ThrowOverflow ("Arithmetic overflow in DoSetLength");
				}

			dng_memory_block **list = (dng_memory_block **) malloc (numBytes);

			if (!list)
				{

				ThrowMemoryFull ();

				}

			if (fPageCount)
				{

				// The multiplication here is safe against overflow.
				// fPageCount can never reach a value that is large enough to
				// cause overflow because the computation of numBytes above
				// would fail before a list of that size could be allocated.

				DoCopyBytes (fPageList,
							 list,
							 fPageCount * (uint32) sizeof (dng_memory_block *));

				}

			if (fPageList)
				{

				free (fPageList);

				}

			fPageList = list;

			fPagesAllocated = newSize;

			}

		fPageList [fPageCount] = fAllocator.Allocate (fPageSize);

		fPageCount++;

		}

	fMemoryStreamLength = length;

	}

/*****************************************************************************/

void dng_memory_stream::DoWrite (const void *data,
							     uint32 count,
							     uint64 offset)
	{

	DoSetLength (Max_uint64 (fMemoryStreamLength,
							 offset + count));

	uint64 baseOffset = offset;

	while (count)
		{

		uint32 pageIndex  = (uint32) (offset / fPageSize);
		uint32 pageOffset = (uint32) (offset % fPageSize);

		uint32 blockCount = Min_uint32 (fPageSize - pageOffset, count);

		const uint8 *sPtr = ((const uint8 *) data) + (uint32) (offset - baseOffset);

		uint8 *dPtr = fPageList [pageIndex]->Buffer_uint8 () +
					  pageOffset;

		DoCopyBytes (sPtr, dPtr, blockCount);

		offset += blockCount;
		count  -= blockCount;

		}

	}

/*****************************************************************************/

void dng_memory_stream::CopyToStream (dng_stream &dstStream,
									  uint64 count)
	{

	if (count < kBigBufferSize)
		{

		dng_stream::CopyToStream (dstStream, count);

		}

	else
		{

		Flush ();

		uint64 offset = Position ();

		if (offset + count > Length ())
			{

			ThrowEndOfFile ();

			}

		while (count)
			{

			uint32 pageIndex  = (uint32) (offset / fPageSize);
			uint32 pageOffset = (uint32) (offset % fPageSize);

			uint32 blockCount = (uint32) Min_uint64 (fPageSize - pageOffset, count);

			const uint8 *sPtr = fPageList [pageIndex]->Buffer_uint8 () +
								pageOffset;

			dstStream.Put (sPtr, blockCount);

			offset += blockCount;
			count  -= blockCount;

			}

		SetReadPosition (offset);

		}

	}

/*****************************************************************************/
