//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
// CommonHelpers satellite library | Author: Sergei Y.Kitáev
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
using System;
using System.Threading;
using System.ComponentModel;

namespace CommonHelpers {

//**************************************************************************************************
/// <summary>
/// </summary>
public static partial class HMemory {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

[HFullFunctionalModeRequired]
public static unsafe IntPtr Clear([HOut] IntPtr ptrDestinationBlock,int? lengthInBytes)
{
   HPointerArgChecking.VerifyNotNull( ptrDestinationBlock );
   if ( lengthInBytes.HasValue && lengthInBytes < 0 )
      throw new ArgumentException(
         "Negative length for memory cleanup operation.",
         nameof( lengthInBytes ) );
   lengthInBytes = lengthInBytes ?? GetAllocatedSize( ptrDestinationBlock );
   if ( lengthInBytes == 0 )
      return ptrDestinationBlock;
   return (IntPtr)PAccessHlp.Impl.ClearMemoryBlock(
      (void*)ptrDestinationBlock, (UIntPtr)lengthInBytes );
}

[HFullFunctionalModeRequired]
public static unsafe IntPtr Copy( [HIn] IntPtr ptrSourceBlock,
                                  [HOut] IntPtr ptrDestinationBlock,
                                  int lengthInBytes )
{
   HPointerArgChecking.VerifyNotNulls( ptrSourceBlock, ptrDestinationBlock );
   if ( lengthInBytes < 0 )
      throw new ArgumentException(
         "Negative length for memory copy operation.",
         nameof( lengthInBytes ) );
   if ( lengthInBytes == 0 )
      return ptrDestinationBlock;
   ulong
      sourceAddress     = (ulong)ptrSourceBlock,
      destinationAddress= (ulong)ptrDestinationBlock,
      length            = (ulong)lengthInBytes;
   if ( sourceAddress <= destinationAddress && sourceAddress     + length >= destinationAddress ||
        sourceAddress >  destinationAddress && destinationAddress+ length >= sourceAddress )
      throw new ArgumentException(
         "Input and output buffers for memory copy operation are overlapped." );
   return (IntPtr)PAccessHlp.Impl.CopyMemoryBlock(
      (void*)ptrSourceBlock, (void*)ptrDestinationBlock, (UIntPtr)lengthInBytes );
}

//==================================================================================================

/// <exception cref="HOperationFailedException" />
public static IntPtr Allocate(int sizeInBytes)
{
   VerifyAllocationSize( sizeInBytes );
   IntPtr allocatedBlock = Win32_PAccess.HeapAlloc(
      HProcess.HeapHandle, Win32_PAccess.HEAP_ZERO_MEMORY, (UIntPtr)sizeInBytes );
   if (allocatedBlock==IntPtr.Zero)
      throw new HOperationFailedException( "Can not allocate memory block." );
   return allocatedBlock;
}

/// <exception cref="HOperationFailedException" />
public static IntPtr ReAllocate([HIn] IntPtr ptrAllocatedBlock,int newSizeInBytes)
{
   HPointerArgChecking.VerifyNotNull( ptrAllocatedBlock );
   VerifyAllocationSize( newSizeInBytes );
   if (GetAllocatedSize(ptrAllocatedBlock)==newSizeInBytes)
      return ptrAllocatedBlock;
   IntPtr newBlock = Win32_PAccess.HeapReAlloc(
      HProcess.HeapHandle, Win32_PAccess.HEAP_ZERO_MEMORY,
      ptrAllocatedBlock, (UIntPtr)newSizeInBytes );
   if (newBlock==IntPtr.Zero)
      throw new HOperationFailedException( "Can not allocate memory block." );
   return newBlock;
}

/// <exception cref="HOperationFailedException" />
public static IntPtr ReAllocate( [HIn][HVolatileAccess] ref IntPtr ptrAllocatedBlock,
                                 int newSizeInBytes )
{
   return HVolatile.Write( ref ptrAllocatedBlock,
      ReAllocate( Thread.VolatileRead(ref ptrAllocatedBlock), newSizeInBytes ) );
}

/// <exception cref="HOperationFailedException" />
public static IntPtr Duplicate([HIn] IntPtr ptrAllocatedBlock)
{
   int size = GetAllocatedSize( ptrAllocatedBlock );
   return Copy(ptrAllocatedBlock,Allocate(size),size);
}

//--------------------------------------------------------------------------------------------------

/// <exception cref="HOperationFailedException" />
public static int GetAllocatedSize(IntPtr ptrAllocatedBlock)
{
   HPointerArgChecking.VerifyNotNull( ptrAllocatedBlock );
   UIntPtr heapSizeResult = Win32_PAccess.HeapSize(
      HProcess.HeapHandle, 0, ptrAllocatedBlock );
   int size = unchecked(
      (int)heapSizeResult );
   if (size==-1)
      throw new HOperationFailedException(
         "Can not determine size of previously allocated memory block." );
   if (size<0)
      throw new HOperationFailedException(
         "Previously allocated memory block is too large (for signed integer value)." );
   return size;
}

//--------------------------------------------------------------------------------------------------

/// <exception cref="Win32Exception" />
public static bool Free([HOptional] IntPtr ptrAllocatedBlock)
{
   if ( ptrAllocatedBlock == IntPtr.Zero )
      return false;
   if ( !Win32_PAccess.HeapFree( HProcess.HeapHandle, 0, ptrAllocatedBlock ) )
      throw HWin32Error.CreateExceptionFromLastError();
   return true;
}

/// <exception cref="Win32Exception" />
public static bool Free([HOptional][HInterlockedAccess] ref IntPtr ptrAllocatedBlock)
{
   return Free( HPointer.InterlockedNullValueExchange(ref ptrAllocatedBlock) );
}

//==================================================================================================

static HMemory()
{
   CommonHelpers_PAccess.Access();
}

//--------------------------------------------------------------------------------------------------

static void VerifyAllocationSize(int sizeInBytes)
{
   if (sizeInBytes<0)
      throw new ArgumentException(
         "Incorrect size of memory allocation block." );
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
} // HMemory
//**************************************************************************************************

} // CommonHelpers
