/*
 * zMemory.c
 * 
 * The Zen API 
 * Copyright (C) 2008-2009 by LMI Technologies Inc.
 * 
 * Licensed under The MIT License.
 * Redistributions of files must retain the above copyright notice.
 */ 
#include <z/zzMemory.h>
#include <stdlib.h>
#include <memory.h>

ZAPI_EXPORT(zStatus) zMemory_Construct(zMemory* memory)
{
    zMemoryClass *obj = zNULL;  
    
    zCHECK(zCalloc(&obj, 1, sizeof(zMemoryClass))); 
    
    obj->buffer = zNULL; 
    obj->position = 0; 
    obj->length = 0; 
    obj->capacity = 0; 
    obj->owned = zTRUE; 

    zHANDLE(memory, obj); 
       
    return zOK;
}

ZAPI_EXPORT(zStatus) zMemory_Destroy(zMemory memory)
{
    zMemoryClass* obj = zHANDLE_POINTER(memory);

    if (!zIS_NULL(obj))
    {
        if ((obj->owned) && (obj->capacity > 0))
        {
            zCHECK(zFree(obj->buffer)); 
        }

        zCHECK(zFree(obj)); 
    }        
    return zOK;
}

ZAPI_EXPORT(zStatus) zMemory_Attach(zMemory memory, void* buffer, zUIntPtr position, zUIntPtr length, zUIntPtr capacity)
{
    zMemoryClass* obj = zHANDLE_POINTER(memory);
    
    if (obj->owned)
    {
        zFree(obj->buffer); 
        obj->buffer = zNULL; 
        obj->capacity = 0; 
    }       

    obj->buffer = buffer; 
    obj->position = position; 
    obj->length = length; 
    obj->capacity = capacity; 
    obj->owned = zFALSE; 

    return zOK; 
}

ZAPI_EXPORT(zStatus) zMemory_Allocate(zMemory memory, zUIntPtr initialCapacity)
{
    zMemoryClass* obj = zHANDLE_POINTER(memory);
       
    if (obj->owned)
    {
        zCHECK(zFree(obj->buffer)); 
        obj->buffer = zNULL; 
        obj->capacity = 0; 
    }       

    zCHECK(zMalloc(&obj->buffer, initialCapacity)); 
    obj->position = 0; 
    obj->length = 0; 
    obj->capacity = initialCapacity; 
    obj->owned = zTRUE; 

    return zOK; 
}

ZAPI_EXPORT(zStatus) zMemory_Reallocate(zMemory memory, zUIntPtr minCapacity)
{
    zMemoryClass* obj = zHANDLE_POINTER(memory);
    zByte* buffer = zNULL; 
    zUIntPtr capacity = zMATH_MAX(minCapacity, 2*(obj->capacity + 1)); 

    zCHECK(zMalloc(&buffer, capacity)); 

    memcpy(buffer, obj->buffer, obj->length); 

    zCHECK(zFree(obj->buffer));

    obj->buffer = buffer; 
    obj->capacity = capacity; 

    return zOK; 
}

ZAPI_EXPORT(zStatus) zMemory_Read(zMemory memory, void* buffer, zUIntPtr size)
{
    zMemoryClass* obj = zHANDLE_POINTER(memory);
           
    if ((obj->position + size) > obj->length)
    {
        return zERROR_STREAM;
    }

    memcpy(buffer, &obj->buffer[obj->position], size);
    obj->position += size; 

    return zOK; 
}

ZAPI_EXPORT(zStatus) zMemory_Write(zMemory memory, const void* buffer, zUIntPtr size)
{
    zMemoryClass* obj = zHANDLE_POINTER(memory);

    if ((obj->position + size) > obj->capacity)
    {
        if (!obj->owned)
        {
            return zERROR_STREAM; 
        }
        
        zCHECK(zMemory_Reallocate(memory, obj->position + size)); 
    }

    memcpy(&obj->buffer[obj->position], buffer, size);     
    obj->position += size; 
    obj->length = zMATH_MAX(obj->length, obj->position); 

    return zOK; 
}

ZAPI_EXPORT(zStatus) zMemory_Seek(zMemory memory, z64s offset, zSeekOrigin origin)
{
    zMemoryClass* obj = zHANDLE_POINTER(memory);
    z64u originPosition;   
    z64u newPosition; 

    switch(origin)
    {
    case zSEEK_ORIGIN_BEGIN:        originPosition = 0;                 break;
    case zSEEK_ORIGIN_CURRENT:      originPosition = obj->position;     break;
    case zSEEK_ORIGIN_END:          originPosition = obj->length;       break;
    default:                        return zERROR_PARAMETER;
    }

    newPosition = (z64u) (originPosition + offset); 

    if (newPosition > obj->length)
    {
        return zERROR_PARAMETER; 
    }

    obj->position = (zUIntPtr) newPosition; 

    return zOK; 
}

ZAPI_EXPORT(zStatus) zMemory_SetLength(zMemory memory, zUIntPtr length)
{
    zMemoryClass* obj = zHANDLE_POINTER(memory);
    
    if (length > obj->capacity)
    {
        return zERROR_PARAMETER; 
    }

    obj->length = length; 
    obj->position = 0; 

    return zOK; 
}

ZAPI_EXPORT(z64u) zMemory_Length(zMemory memory)
{
    zMemoryClass* obj = zHANDLE_POINTER(memory);
    return obj->length; 
}

ZAPI_EXPORT(zUIntPtr) zMemory_Capacity(zMemory memory)
{
    zMemoryClass* obj = zHANDLE_POINTER(memory);
    return obj->capacity; 
}

ZAPI_EXPORT(z64u) zMemory_Position(zMemory memory)
{
    zMemoryClass* obj = zHANDLE_POINTER(memory);
    return obj->position; 
}

ZAPI_EXPORT(void*) zMemory_At(zMemory memory, zUIntPtr offset)
{
    zMemoryClass* obj = zHANDLE_POINTER(memory);

    if (offset > obj->length)
    {
        return zNULL; 
    }

    return &obj->buffer[offset]; 
}
