//
//  TPCircularBuffer.c
//  Circular/Ring buffer implementation
//
//  https://github.com/michaeltyson/TPCircularBuffer
//
//  Created by Michael Tyson on 10/12/2011.
//
//  Copyright (C) 2012-2013 A Tasty Pixel
//
//  This software is provided 'as-is', without any express or implied
//  warranty.  In no event will the authors be held liable for any damages
//  arising from the use of this software.
//
//  Permission is granted to anyone to use this software for any purpose,
//  including commercial applications, and to alter it and redistribute it
//  freely, subject to the following restrictions:
//
//  1. The origin of this software must not be misrepresented; you must not
//     claim that you wrote the original software. If you use this software
//     in a product, an acknowledgment in the product documentation would be
//     appreciated but is not required.
//
//  2. Altered source versions must be plainly marked as such, and must not be
//     misrepresented as being the original software.
//
//  3. This notice may not be removed or altered from any source distribution.
//

#include <winpr/wlog.h>

#include "TPCircularBuffer.h"
#include "rdpsnd_main.h"

#include <mach/mach.h>
#include <stdio.h>

#define reportResult(result, operation) (_reportResult((result), (operation), __FILE__, __LINE__))
static inline bool _reportResult(kern_return_t result, const char* operation, const char* file,
                                 int line)
{
	if (result != ERR_SUCCESS)
	{
		WLog_DBG(TAG, "%s:%d: %s: %s\n", file, line, operation, mach_error_string(result));
		return false;
	}
	return true;
}

bool TPCircularBufferInit(TPCircularBuffer* buffer, int length)
{

	// Keep trying until we get our buffer, needed to handle race conditions
	int retries = 3;
	while (true)
	{

		buffer->length = round_page(length); // We need whole page sizes

		// Temporarily allocate twice the length, so we have the contiguous address space to
		// support a second instance of the buffer directly after
		vm_address_t bufferAddress;
		kern_return_t result = vm_allocate(mach_task_self(), &bufferAddress, buffer->length * 2,
		                                   VM_FLAGS_ANYWHERE); // allocate anywhere it'll fit
		if (result != ERR_SUCCESS)
		{
			if (retries-- == 0)
			{
				reportResult(result, "Buffer allocation");
				return false;
			}
			// Try again if we fail
			continue;
		}

		// Now replace the second half of the allocation with a virtual copy of the first half.
		// Deallocate the second half...
		result = vm_deallocate(mach_task_self(), bufferAddress + buffer->length, buffer->length);
		if (result != ERR_SUCCESS)
		{
			if (retries-- == 0)
			{
				reportResult(result, "Buffer deallocation");
				return false;
			}
			// If this fails somehow, deallocate the whole region and try again
			vm_deallocate(mach_task_self(), bufferAddress, buffer->length);
			continue;
		}

		// Re-map the buffer to the address space immediately after the buffer
		vm_address_t virtualAddress = bufferAddress + buffer->length;
		vm_prot_t cur_prot, max_prot;
		result = vm_remap(mach_task_self(),
		                  &virtualAddress,  // mirror target
		                  buffer->length,   // size of mirror
		                  0,                // auto alignment
		                  0,                // force remapping to virtualAddress
		                  mach_task_self(), // same task
		                  bufferAddress,    // mirror source
		                  0,                // MAP READ-WRITE, NOT COPY
		                  &cur_prot,        // unused protection struct
		                  &max_prot,        // unused protection struct
		                  VM_INHERIT_DEFAULT);
		if (result != ERR_SUCCESS)
		{
			if (retries-- == 0)
			{
				reportResult(result, "Remap buffer memory");
				return false;
			}
			// If this remap failed, we hit a race condition, so deallocate and try again
			vm_deallocate(mach_task_self(), bufferAddress, buffer->length);
			continue;
		}

		if (virtualAddress != bufferAddress + buffer->length)
		{
			// If the memory is not contiguous, clean up both allocated buffers and try again
			if (retries-- == 0)
			{
				WLog_DBG(TAG, "Couldn't map buffer memory to end of buffer");
				return false;
			}

			vm_deallocate(mach_task_self(), virtualAddress, buffer->length);
			vm_deallocate(mach_task_self(), bufferAddress, buffer->length);
			continue;
		}

		buffer->buffer = (void*)bufferAddress;
		buffer->fillCount = 0;
		buffer->head = buffer->tail = 0;

		return true;
	}
	return false;
}

void TPCircularBufferCleanup(TPCircularBuffer* buffer)
{
	vm_deallocate(mach_task_self(), (vm_address_t)buffer->buffer, buffer->length * 2);
	memset(buffer, 0, sizeof(TPCircularBuffer));
}

void TPCircularBufferClear(TPCircularBuffer* buffer)
{
	int32_t fillCount;
	if (TPCircularBufferTail(buffer, &fillCount))
	{
		TPCircularBufferConsume(buffer, fillCount);
	}
}
