/*****************************************************************
|
|   BlueTune - Network Stream
|
|   (c) 2002-2006 Gilles Boccon-Gibod
|   Author: Gilles Boccon-Gibod (bok@bok.net)
|
****************************************************************/

/**
 * This module implements a buffered stream cache that allows some
 * amount of seeking backward and forward in a slow-to-seek source
 * stream such as a network stream.
 * The cache will try to limit the forward-filling of the buffer to 
 * a small amount to keep as much backward-seek buffer as possible.
 */

/*----------------------------------------------------------------------
|   includes
+---------------------------------------------------------------------*/
#include "Atomix.h"
#include "BltNetworkStream.h"

/*----------------------------------------------------------------------
|   types
+---------------------------------------------------------------------*/
typedef struct {
    /* interfaces */
    ATX_IMPLEMENTS(ATX_InputStream);
    ATX_IMPLEMENTS(ATX_Referenceable);

    /* members */
    ATX_Cardinal     reference_count;
    ATX_InputStream* source;
    ATX_RingBuffer*  buffer;
    ATX_Size         buffer_size;
    ATX_Size         back_store;
    ATX_Offset       position;
    ATX_Boolean      eos;
} BLT_NetworkStream;

/*----------------------------------------------------------------------
|   constants
+---------------------------------------------------------------------*/
/**
 * Threshold below which a seek forward is implemented by reading from
 * the source instead of seeking in the source.
 */
#define BLT_NETWORK_STREAM_SEEK_AS_READ_THRESHOLD 

/**
 * Try to keep this amount of data in the back store 
 */
#define BLT_NETWORK_STREAM_MIN_BACK_STORE 4096

/*----------------------------------------------------------------------
|   forward declarations
+---------------------------------------------------------------------*/
ATX_DECLARE_INTERFACE_MAP(BLT_NetworkStream, ATX_InputStream)
ATX_DECLARE_INTERFACE_MAP(BLT_NetworkStream, ATX_Referenceable)

/*----------------------------------------------------------------------
|   BLT_NetworkStream_Create
+---------------------------------------------------------------------*/
BLT_Result 
BLT_NetworkStream_Create(BLT_Size          buffer_size,
                         ATX_InputStream*  source, 
                         ATX_InputStream** stream)
{
    ATX_Result result;

    /* allocate the object */
    BLT_NetworkStream* self = (BLT_NetworkStream*)ATX_AllocateZeroMemory(sizeof(BLT_NetworkStream));
    if (self == NULL) {
        *stream = NULL;
        return ATX_ERROR_OUT_OF_MEMORY;
    }

    /* construct the object */
    self->reference_count = 1;
    result = ATX_RingBuffer_Create(buffer_size, &self->buffer);
    if (ATX_FAILED(result)) {
        *stream = NULL;
        return result;
    }
    self->buffer_size = buffer_size;
    self->source = source;
    ATX_REFERENCE_OBJECT(source);

    /* setup the interfaces */
    ATX_SET_INTERFACE(self, BLT_NetworkStream, ATX_InputStream);
    ATX_SET_INTERFACE(self, BLT_NetworkStream, ATX_Referenceable);
    *stream = &ATX_BASE(self, ATX_InputStream);

    return ATX_SUCCESS;
}

/*----------------------------------------------------------------------
|   BLT_NetworkStream_Destroy
+---------------------------------------------------------------------*/
static BLT_Result
BLT_NetworkStream_Destroy(BLT_NetworkStream* self)
{
    ATX_RELEASE_OBJECT(self->source);
    ATX_RingBuffer_Destroy(self->buffer);
    ATX_FreeMemory(self);

    return ATX_SUCCESS;
}

/*----------------------------------------------------------------------
|   BLT_NetworkStream_ClampBackStore
+---------------------------------------------------------------------*/
static void
BLT_NetworkStream_ClampBackStore(BLT_NetworkStream* self)
{
    /** clamp the back store to ensure it is not bigger than the max
     * possible value */
    ATX_Size max_back = ATX_RingBuffer_GetSpace(self->buffer);
    if (self->back_store > max_back) self->back_store = max_back;
}

/*----------------------------------------------------------------------
|   BLT_NetworkStream_Read
+---------------------------------------------------------------------*/
static ATX_Result
BLT_NetworkStream_Read(ATX_InputStream* _self, 
                       void*            buffer,
                       ATX_Size         bytes_to_read,
                       ATX_Size*        bytes_read)
{
    BLT_NetworkStream* self = ATX_SELF(BLT_NetworkStream, ATX_InputStream);
    ATX_Size           buffered = ATX_RingBuffer_GetAvailable(self->buffer);
    ATX_Size           chunk;
    ATX_Size           bytes_read_storage = 0;

    /* default */
    if (bytes_read) {
        *bytes_read = 0;
    } else {
        bytes_read = &bytes_read_storage;
    }

    /* shortcut */
    if (bytes_to_read == 0) return ATX_SUCCESS;

    /* use all we can from the buffer */
    chunk = buffered > bytes_to_read ? bytes_to_read : buffered;
    if (chunk) {
        ATX_RingBuffer_Read(self->buffer, buffer, chunk);
        bytes_to_read -= chunk;
        *bytes_read += chunk;
        self->position += chunk;
        buffer = (void*)((char*)buffer+chunk);
        self->back_store += chunk;
        BLT_NetworkStream_ClampBackStore(self);
    }

    /* read what we can from the source */
    while (bytes_to_read && !self->eos) {
        ATX_Size       read_from_source = 0;
        ATX_Size       can_write = ATX_RingBuffer_GetSpace(self->buffer);
        ATX_Size       should_read = ATX_RingBuffer_GetContiguousSpace(self->buffer);
        unsigned char* in = ATX_RingBuffer_GetIn(self->buffer);
        ATX_Result     result;

        /* compute how much to read from the source */
        if (should_read > BLT_NETWORK_STREAM_MIN_BACK_STORE &&
            should_read - BLT_NETWORK_STREAM_MIN_BACK_STORE >= bytes_to_read) {
            /* leave some data in the back store */
            should_read -= BLT_NETWORK_STREAM_MIN_BACK_STORE;
        }

        /* read from the source */
        result = ATX_InputStream_Read(self->source, 
                                      in, 
                                      should_read, 
                                      &read_from_source);
        if (ATX_SUCCEEDED(result)) {
            /* adjust the ring buffer */
            ATX_RingBuffer_MoveIn(self->buffer, read_from_source);

            /* transfer some of what was read */
            chunk = (bytes_to_read <= read_from_source)?bytes_to_read:read_from_source;
            ATX_RingBuffer_Read(self->buffer, buffer, chunk);

            /* adjust counters and pointers */
            *bytes_read += chunk;
            bytes_to_read -= chunk;
            self->position += chunk;
            buffer = (void*)((char*)buffer+chunk);

            /* compute how much back-store we have now */
            if (can_write-read_from_source < self->back_store) {
                /* we have reduced (written over) the back store */
                self->back_store = can_write-read_from_source;
            }
            self->back_store += chunk;
            BLT_NetworkStream_ClampBackStore(self);

        } else if (result == ATX_ERROR_EOS) {
            /* we can't continue further */
            self->eos = ATX_TRUE;
            break;
        } else {
            return (*bytes_read == 0) ? result : ATX_SUCCESS;
        }

        /* don't loop if this was a short read */
        if (read_from_source != should_read) break;
    }

    if (self->eos && *bytes_read == 0) {
        return ATX_ERROR_EOS;
    } else {
        return ATX_SUCCESS;
    }
}

/*----------------------------------------------------------------------
|   BLT_NetworkStream_Seek
+---------------------------------------------------------------------*/
static ATX_Result 
BLT_NetworkStream_Seek(ATX_InputStream* _self, ATX_Position position)
{
    BLT_NetworkStream* self = ATX_SELF(BLT_NetworkStream, ATX_InputStream);
    int                move = position-self->position;
    ATX_Result         result;

    /* shortcut */
    if (move == 0) {
        if (position == 0) {
            /* force a call to the source, because some callers will  */
            /* use this to determine if the source is seekable or not */
            ATX_Position current = 0;
            result = ATX_InputStream_Tell(self->source, &current);
            if (ATX_FAILED(result)) return result;
            return ATX_InputStream_Seek(self->source, current);
        }
        return ATX_SUCCESS;
    }
    
    /* see if we can seek entirely within our buffer */
    if ((move < 0 && -move <= (int)self->back_store) ||
        (move > 0 && move < (int)ATX_RingBuffer_GetAvailable(self->buffer))) {
        ATX_RingBuffer_MoveOut(self->buffer, move);
        self->position = position;
        self->eos = ATX_FALSE;

        /* adjust the back-store counter */
        self->back_store += move;

        return ATX_SUCCESS;
    }

    /* we're seeking outside the buffered zone */
    result = ATX_InputStream_Seek(self->source, position);
    if (ATX_FAILED(result)) return result;
    ATX_RingBuffer_Reset(self->buffer);
    self->back_store = 0;
    self->position = position;

    self->eos = ATX_FALSE;
    return ATX_SUCCESS;
}

/*----------------------------------------------------------------------
|   BLT_NetworkStream_Tell
+---------------------------------------------------------------------*/
static ATX_Result 
BLT_NetworkStream_Tell(ATX_InputStream* _self, ATX_Position* offset)
{
    BLT_NetworkStream* self = ATX_SELF(BLT_NetworkStream, ATX_InputStream);
    *offset = self->position;

    return ATX_SUCCESS;
}

/*----------------------------------------------------------------------
|   BLT_NetworkStream_GetSize
+---------------------------------------------------------------------*/
static ATX_Result 
BLT_NetworkStream_GetSize(ATX_InputStream* _self, ATX_Size* size)
{
    BLT_NetworkStream* self = ATX_SELF(BLT_NetworkStream, ATX_InputStream);
    return ATX_InputStream_GetSize(self->source, size);
}

/*----------------------------------------------------------------------
|   BLT_NetworkStream_GetAvailable
+---------------------------------------------------------------------*/
static ATX_Result 
BLT_NetworkStream_GetAvailable(ATX_InputStream* _self, ATX_Size* available)
{
    BLT_NetworkStream* self = ATX_SELF(BLT_NetworkStream, ATX_InputStream);
    ATX_Size available_from_source = 0;
    ATX_InputStream_GetAvailable(self->source, &available_from_source);
    *available = available_from_source+ATX_RingBuffer_GetAvailable(self->buffer);

    return ATX_SUCCESS;
}

/*----------------------------------------------------------------------
|   GetInterface implementation
+---------------------------------------------------------------------*/
ATX_BEGIN_GET_INTERFACE_IMPLEMENTATION(BLT_NetworkStream)
    ATX_GET_INTERFACE_ACCEPT(BLT_NetworkStream, ATX_InputStream)
    ATX_GET_INTERFACE_ACCEPT(BLT_NetworkStream, ATX_Referenceable)
ATX_END_GET_INTERFACE_IMPLEMENTATION

/*----------------------------------------------------------------------
|       ATX_InputStream interface
+---------------------------------------------------------------------*/
ATX_BEGIN_INTERFACE_MAP(BLT_NetworkStream, ATX_InputStream)
    BLT_NetworkStream_Read,
    BLT_NetworkStream_Seek,
    BLT_NetworkStream_Tell,
    BLT_NetworkStream_GetSize,
    BLT_NetworkStream_GetAvailable
ATX_END_INTERFACE_MAP

/*----------------------------------------------------------------------
|       ATX_Referenceable interface
+---------------------------------------------------------------------*/
ATX_IMPLEMENT_REFERENCEABLE_INTERFACE(BLT_NetworkStream, reference_count)

