/*
Copyright (c) 2012, Broadcom Europe Ltd
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the copyright holder nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "containers/containers.h"
#include "containers/core/containers_io.h"
#include "containers/core/containers_common.h"
#include "containers/core/containers_utils.h"
#include "containers/core/containers_uri.h"

#define MAX_NUM_CACHED_AREAS 16
#define MAX_NUM_MEMORY_AREAS 4
#define NUM_TMP_MEMORY_AREAS 2
#define MEM_CACHE_READ_MAX_SIZE (32*1024) /* Needs to be a power of 2 */
#define MEM_CACHE_WRITE_MAX_SIZE (128*1024) /* Needs to be a power of 2 */
#define MEM_CACHE_TMP_MAX_SIZE (32*1024) /* Needs to be a power of 2 */
#define MEM_CACHE_ALIGNMENT (1*1024) /* Needs to be a power of 2 */
#define MEM_CACHE_AREA_READ_MAX_SIZE (4*1024*1024) /* Needs to be a power of 2 */

typedef struct VC_CONTAINER_IO_PRIVATE_CACHE_T
{
   int64_t start; /**< Offset to the start of the cached area in the stream */
   int64_t end;    /**< Offset to the end of the cached area in the stream */

   int64_t offset; /**< Offset of the currently cached data in the stream */
   size_t size;    /**< Size of the cached area */
   bool dirty;     /**< Whether the cache is dirty and needs to be written back */

   size_t position; /**< Current position in the cache */

   uint8_t *buffer;          /**< Pointer to the start of the valid cache area */
   uint8_t *buffer_end;      /**< Pointer to the end of the cache */

   unsigned int mem_max_size; /**< Maximum size of the memory cache */
   unsigned int mem_size; /**< Size of the memory cache */
   uint8_t *mem;          /**< Pointer to the memory cache */

   VC_CONTAINER_IO_T *io;

} VC_CONTAINER_IO_PRIVATE_CACHE_T;

typedef struct VC_CONTAINER_IO_PRIVATE_T
{
   VC_CONTAINER_IO_PRIVATE_CACHE_T *cache; /**< Current cache */

   unsigned int caches_num;
   VC_CONTAINER_IO_PRIVATE_CACHE_T caches;

   unsigned int cached_areas_num;
   VC_CONTAINER_IO_PRIVATE_CACHE_T cached_areas[MAX_NUM_CACHED_AREAS];

   int64_t actual_offset;

   struct VC_CONTAINER_IO_ASYNC_T *async_io;

} VC_CONTAINER_IO_PRIVATE_T;

/*****************************************************************************/
VC_CONTAINER_STATUS_T vc_container_io_file_open( VC_CONTAINER_IO_T *p_ctx, const char *uri,
                                                 VC_CONTAINER_IO_MODE_T mode );
VC_CONTAINER_STATUS_T vc_container_io_null_open( VC_CONTAINER_IO_T *p_ctx, const char *uri,
                                                 VC_CONTAINER_IO_MODE_T mode );
VC_CONTAINER_STATUS_T vc_container_io_net_open( VC_CONTAINER_IO_T *p_ctx, const char *uri,
                                                 VC_CONTAINER_IO_MODE_T mode );
VC_CONTAINER_STATUS_T vc_container_io_pktfile_open( VC_CONTAINER_IO_T *p_ctx, const char *uri,
                                                 VC_CONTAINER_IO_MODE_T mode );
VC_CONTAINER_STATUS_T vc_container_io_http_open( VC_CONTAINER_IO_T *p_ctx, const char *uri,
                                                 VC_CONTAINER_IO_MODE_T mode );
static VC_CONTAINER_STATUS_T io_seek_not_seekable(VC_CONTAINER_IO_T *p_ctx, int64_t offset);

static size_t vc_container_io_cache_read( VC_CONTAINER_IO_T *p_ctx,
   VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, uint8_t *data, size_t size );
static int32_t vc_container_io_cache_write( VC_CONTAINER_IO_T *p_ctx,
   VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, const uint8_t *data, size_t size );
static VC_CONTAINER_STATUS_T vc_container_io_cache_seek( VC_CONTAINER_IO_T *p_ctx,
   VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, int64_t offset );
static size_t vc_container_io_cache_refill( VC_CONTAINER_IO_T *p_ctx,
   VC_CONTAINER_IO_PRIVATE_CACHE_T *cache );
static size_t vc_container_io_cache_flush( VC_CONTAINER_IO_T *p_ctx,
   VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, int complete );

static struct VC_CONTAINER_IO_ASYNC_T *async_io_start( VC_CONTAINER_IO_T *io, int num_areas, VC_CONTAINER_STATUS_T * );
static VC_CONTAINER_STATUS_T async_io_stop( struct VC_CONTAINER_IO_ASYNC_T *ctx );
static int async_io_write( struct VC_CONTAINER_IO_ASYNC_T *ctx, VC_CONTAINER_IO_PRIVATE_CACHE_T *cache );
static VC_CONTAINER_STATUS_T async_io_wait_complete( struct VC_CONTAINER_IO_ASYNC_T *ctx,
                                                     VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, int complete  );
static void async_io_stats_initialise( struct VC_CONTAINER_IO_ASYNC_T *ctx, int enable );
static void async_io_stats_get( struct VC_CONTAINER_IO_ASYNC_T *ctx, VC_CONTAINER_WRITE_STATS_T *stats );

/*****************************************************************************/
static VC_CONTAINER_IO_T *vc_container_io_open_core( const char *uri, VC_CONTAINER_IO_MODE_T mode,
                                                     VC_CONTAINER_IO_CAPABILITIES_T capabilities,
                                                     bool b_open, VC_CONTAINER_STATUS_T *p_status )
{
   VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS;
   VC_CONTAINER_IO_T *p_ctx = 0;
   VC_CONTAINER_IO_PRIVATE_T *private = 0;
   unsigned int uri_length, caches = 0, cache_max_size, num_areas = MAX_NUM_MEMORY_AREAS;

   /* XXX */
   uri_length = strlen(uri) + 1;

   /* Allocate our context before trying out the different io modules */
   p_ctx = malloc( sizeof(*p_ctx) + sizeof(*private) + uri_length);
   if(!p_ctx) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; }
   memset(p_ctx, 0, sizeof(*p_ctx) + sizeof(*private) + uri_length );
   p_ctx->priv = private = (VC_CONTAINER_IO_PRIVATE_T *)&p_ctx[1];
   p_ctx->uri = (char *)&private[1];
   memcpy((char *)p_ctx->uri, uri, uri_length);
   p_ctx->uri_parts = vc_uri_create();
   if(!p_ctx->uri_parts) { status = VC_CONTAINER_ERROR_OUT_OF_MEMORY; goto error; }
   vc_uri_parse(p_ctx->uri_parts, uri);

   if (b_open)
   {
      /* Open the actual i/o module */
      status = vc_container_io_null_open(p_ctx, uri, mode);
      if(status) status = vc_container_io_net_open(p_ctx, uri, mode);
      if(status) status = vc_container_io_pktfile_open(p_ctx, uri, mode);
#ifdef ENABLE_CONTAINER_IO_HTTP
      if(status) status = vc_container_io_http_open(p_ctx, uri, mode);
#endif
      if(status) status = vc_container_io_file_open(p_ctx, uri, mode);
      if(status != VC_CONTAINER_SUCCESS) goto error;

      if(!p_ctx->pf_seek || (p_ctx->capabilities & VC_CONTAINER_IO_CAPS_CANT_SEEK))
      {
         p_ctx->capabilities |= VC_CONTAINER_IO_CAPS_CANT_SEEK;
         p_ctx->pf_seek = io_seek_not_seekable;
      }
   }
   else
   {
      /* We're only creating an empty container i/o */
      p_ctx->capabilities = capabilities;
   }

   if(p_ctx->capabilities & VC_CONTAINER_IO_CAPS_NO_CACHING)
      caches = 1;

   if(mode == VC_CONTAINER_IO_MODE_WRITE) cache_max_size = MEM_CACHE_WRITE_MAX_SIZE;
   else cache_max_size = MEM_CACHE_READ_MAX_SIZE;

   if(mode == VC_CONTAINER_IO_MODE_WRITE &&
      vc_uri_path_extension(p_ctx->uri_parts) &&
      !strcasecmp(vc_uri_path_extension(p_ctx->uri_parts), "tmp"))
   {
      caches = 1;
      cache_max_size = MEM_CACHE_TMP_MAX_SIZE;
      num_areas = NUM_TMP_MEMORY_AREAS;
   }

   /* Check if the I/O needs caching */
   if(caches)
   {
      VC_CONTAINER_IO_PRIVATE_CACHE_T *cache = &p_ctx->priv->caches;
      cache->mem_max_size = cache_max_size;
      cache->mem_size = cache->mem_max_size;
      cache->io = p_ctx;
      cache->mem = malloc(p_ctx->priv->caches.mem_size);
      if(cache->mem)
      {
         cache->buffer = cache->mem;
         cache->buffer_end = cache->mem + cache->mem_size;
         p_ctx->priv->caches_num = 1;
      }
   }

   if(p_ctx->priv->caches_num)
      p_ctx->priv->cache = &p_ctx->priv->caches;

   /* Try to start an asynchronous io if we're in write mode and we've got at least 2 cache memory areas */
   if(mode == VC_CONTAINER_IO_MODE_WRITE && p_ctx->priv->cache && num_areas >= 2)
      p_ctx->priv->async_io = async_io_start( p_ctx, num_areas, 0 );

 end:
   if(p_status) *p_status = status;
   return p_ctx;

 error:
   if(p_ctx) vc_uri_release(p_ctx->uri_parts);
   if(p_ctx) free(p_ctx);
   p_ctx = 0;
   goto end;
}

/*****************************************************************************/
VC_CONTAINER_IO_T *vc_container_io_open( const char *uri, VC_CONTAINER_IO_MODE_T mode,
                                         VC_CONTAINER_STATUS_T *p_status )
{
   return vc_container_io_open_core( uri, mode, 0, true, p_status );
}

/*****************************************************************************/
VC_CONTAINER_IO_T *vc_container_io_create( const char *uri, VC_CONTAINER_IO_MODE_T mode,
                                           VC_CONTAINER_IO_CAPABILITIES_T capabilities,
                                           VC_CONTAINER_STATUS_T *p_status )
{
   return vc_container_io_open_core( uri, mode, capabilities, false, p_status );
}

/*****************************************************************************/
VC_CONTAINER_STATUS_T vc_container_io_close( VC_CONTAINER_IO_T *p_ctx )
{
   unsigned int i;

   if(p_ctx)
   {
      if(p_ctx->priv)
      {
         if(p_ctx->priv->caches_num)
         {
            if(p_ctx->priv->caches.dirty)
               vc_container_io_cache_flush( p_ctx, &p_ctx->priv->caches, 1 );
         }

         if(p_ctx->priv->async_io)
            async_io_stop( p_ctx->priv->async_io );
         else if(p_ctx->priv->caches_num)
            free(p_ctx->priv->caches.mem);

         for(i = 0; i < p_ctx->priv->cached_areas_num; i++)
            free(p_ctx->priv->cached_areas[i].mem);

         if(p_ctx->pf_close)
            p_ctx->pf_close(p_ctx);
      }
      vc_uri_release(p_ctx->uri_parts);
      free(p_ctx);
   }
   return VC_CONTAINER_SUCCESS;
}

/*****************************************************************************/
size_t vc_container_io_peek(VC_CONTAINER_IO_T *p_ctx, void *buffer, size_t size)
{
   size_t ret;

   if(p_ctx->priv->cache)
   {
      /* FIXME: do something a bit more clever than this */
      int64_t offset = p_ctx->offset;
      ret = vc_container_io_read(p_ctx, buffer, size);
      vc_container_io_seek(p_ctx, offset);
      return ret;
   }

   if (p_ctx->capabilities & VC_CONTAINER_IO_CAPS_CANT_SEEK)
      return 0;

   ret = p_ctx->pf_read(p_ctx, buffer, size);
   p_ctx->pf_seek(p_ctx, p_ctx->offset);
   return ret;
}

/*****************************************************************************/
size_t vc_container_io_read(VC_CONTAINER_IO_T *p_ctx, void *buffer, size_t size)
{
   size_t ret;

   if(p_ctx->priv->cache)
      ret = vc_container_io_cache_read( p_ctx, p_ctx->priv->cache, (uint8_t*)buffer, size );
   else
   {
      ret = p_ctx->pf_read(p_ctx, buffer, size);
      p_ctx->priv->actual_offset += ret;
   }

   p_ctx->offset += ret;
   return ret;
}

/*****************************************************************************/
size_t vc_container_io_write(VC_CONTAINER_IO_T *p_ctx, const void *buffer, size_t size)
{
   int32_t ret;

   if(p_ctx->priv->cache)
      ret = vc_container_io_cache_write( p_ctx, p_ctx->priv->cache, (const uint8_t*)buffer, size );
   else
   {
      ret = p_ctx->pf_write(p_ctx, buffer, size);
      p_ctx->priv->actual_offset += ret;
   }

   p_ctx->offset += ret;
   return ret < 0 ? 0 : ret;
}

/*****************************************************************************/
size_t vc_container_io_skip(VC_CONTAINER_IO_T *p_ctx, size_t size)
{
   if(!size) return 0;

   if(size < 8)
   {
      uint8_t value[8];
      return vc_container_io_read(p_ctx, value, size);
   }

   if(p_ctx->priv->cache)
   {
      if(vc_container_io_cache_seek(p_ctx, p_ctx->priv->cache, p_ctx->offset + size)) return 0;
      p_ctx->offset += size;
      return size;
   }

   if(vc_container_io_seek(p_ctx, p_ctx->offset + size)) return 0;
   return size;
}

/*****************************************************************************/
VC_CONTAINER_STATUS_T vc_container_io_seek(VC_CONTAINER_IO_T *p_ctx, int64_t offset)
{
   VC_CONTAINER_STATUS_T status;
   unsigned int i;

   /* Check if the requested position is in one of the cached areas */
   for(i = 0; i < p_ctx->priv->cached_areas_num; i++)
   {
      VC_CONTAINER_IO_PRIVATE_CACHE_T *cache = &p_ctx->priv->cached_areas[i];
      if(offset >= cache->start && offset < cache->end)
      {
         p_ctx->priv->cache = cache;
         break;
      }
   }
   if(i == p_ctx->priv->cached_areas_num)
      p_ctx->priv->cache = p_ctx->priv->caches_num ? &p_ctx->priv->caches : 0;

   if(p_ctx->priv->cache)
   {
      status = vc_container_io_cache_seek( p_ctx, p_ctx->priv->cache, offset );
      if(status == VC_CONTAINER_SUCCESS) p_ctx->offset = offset;
      return status;
   }

   if(p_ctx->status == VC_CONTAINER_SUCCESS &&
      offset == p_ctx->offset) return VC_CONTAINER_SUCCESS;

   status = p_ctx->pf_seek(p_ctx, offset);
   if(status == VC_CONTAINER_SUCCESS) p_ctx->offset = offset;
   p_ctx->priv->actual_offset = p_ctx->offset;
   return status;
}

/*****************************************************************************/
static VC_CONTAINER_STATUS_T io_seek_not_seekable(VC_CONTAINER_IO_T *p_ctx, int64_t offset)
{
   VC_CONTAINER_IO_PRIVATE_T *private = p_ctx->priv;

   vc_container_assert(offset >= private->actual_offset);
   if(offset == private->actual_offset)  return VC_CONTAINER_SUCCESS;

   if(offset < private->actual_offset)
   {
      p_ctx->status = VC_CONTAINER_ERROR_EOS;
      return p_ctx->status;
   }

   offset -= private->actual_offset;
   while(offset && !p_ctx->status)
   {
      uint8_t value[64];
      unsigned int ret, size = MIN(offset, 64);
      ret = p_ctx->pf_read(p_ctx, value, size);
      if(ret != size) p_ctx->status = VC_CONTAINER_ERROR_EOS;
      offset -= ret;
   }
   return p_ctx->status;
}

/*****************************************************************************/
VC_CONTAINER_STATUS_T vc_container_io_control_list(VC_CONTAINER_IO_T *context, VC_CONTAINER_CONTROL_T operation, va_list args)
{
   VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_UNSUPPORTED_OPERATION;

   if (context->pf_control)
      status = context->pf_control(context, operation, args);

   /* Option to add generic I/O control here */

   if(operation == VC_CONTAINER_CONTROL_IO_FLUSH && context->priv->cache)
   {
      status = VC_CONTAINER_SUCCESS;
      (void)vc_container_io_cache_flush( context, context->priv->cache, 1 );
   }

   if(operation == VC_CONTAINER_CONTROL_SET_IO_PERF_STATS && context->priv->async_io)
   {
      status = VC_CONTAINER_SUCCESS;
      async_io_stats_initialise(context->priv->async_io, va_arg(args, int));
   }

   if(operation == VC_CONTAINER_CONTROL_GET_IO_PERF_STATS && context->priv->async_io)
   {
      status = VC_CONTAINER_SUCCESS;
      async_io_stats_get(context->priv->async_io, va_arg(args, VC_CONTAINER_WRITE_STATS_T *));
   }

   return status;
}

/*****************************************************************************/
VC_CONTAINER_STATUS_T vc_container_io_control(VC_CONTAINER_IO_T *context, VC_CONTAINER_CONTROL_T operation, ...)
{
   VC_CONTAINER_STATUS_T result;
   va_list args;

   va_start(args, operation);
   result = vc_container_io_control_list(context, operation, args);
   va_end(args);

   return result;
}

/*****************************************************************************/
size_t vc_container_io_cache(VC_CONTAINER_IO_T *p_ctx, size_t size)
{
   VC_CONTAINER_IO_PRIVATE_T *private = p_ctx->priv;
   VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, *main_cache;
   VC_CONTAINER_STATUS_T status;

   /* Sanity checking */
   if(private->cached_areas_num >= MAX_NUM_CACHED_AREAS) return 0;

   cache = &private->cached_areas[private->cached_areas_num];
   cache->start = p_ctx->offset;
   cache->end = cache->start + size;
   cache->offset = p_ctx->offset;
   cache->position = 0;
   cache->size = 0;
   cache->io = p_ctx;

   /* Set the size of the cache area depending on the capabilities of the i/o */
   if(p_ctx->capabilities & VC_CONTAINER_IO_CAPS_CANT_SEEK)
      cache->mem_max_size = MEM_CACHE_AREA_READ_MAX_SIZE;
   else if((p_ctx->capabilities & VC_CONTAINER_IO_CAPS_SEEK_SLOW) &&
           size <= MEM_CACHE_AREA_READ_MAX_SIZE)
      cache->mem_max_size = MEM_CACHE_AREA_READ_MAX_SIZE;
   else
      cache->mem_max_size = MEM_CACHE_READ_MAX_SIZE;

   cache->mem_size = size;
   if(cache->mem_size > cache->mem_max_size) cache->mem_size = cache->mem_max_size;
   cache->mem = malloc(cache->mem_size);

   cache->buffer = cache->mem;
   cache->buffer_end = cache->mem + cache->mem_size;

   if(!cache->mem) return 0;
   private->cached_areas_num++;

   /* Copy any data we've got in the current cache into the new cache */
   main_cache = p_ctx->priv->cache;
   if(main_cache && main_cache->position < main_cache->size)
   {
      cache->size = main_cache->size - main_cache->position;
      if(cache->size > cache->mem_size) cache->size = cache->mem_size;
      memcpy(cache->buffer, main_cache->buffer + main_cache->position, cache->size);
      main_cache->position += cache->size;
   }

   /* Read the rest of the cache directly from the stream */
   if(cache->mem_size > cache->size)
   {
      size_t ret = cache->io->pf_read(cache->io, cache->buffer + cache->size,
                                      cache->mem_size - cache->size);
      cache->size += ret;
      cache->io->priv->actual_offset = cache->offset + cache->size;
   }

   status = vc_container_io_seek(p_ctx, cache->end);
   if(status != VC_CONTAINER_SUCCESS)
      return 0;

   if(p_ctx->capabilities & VC_CONTAINER_IO_CAPS_CANT_SEEK)
      return cache->size;
   else
      return size;
}

/*****************************************************************************/
static size_t vc_container_io_cache_refill( VC_CONTAINER_IO_T *p_ctx,
   VC_CONTAINER_IO_PRIVATE_CACHE_T *cache )
{
   size_t ret = vc_container_io_cache_flush( p_ctx, cache, 1 );

   if(ret) return 0; /* TODO what should we do there ? */

   if(p_ctx->priv->actual_offset != cache->offset)
   {
      if(cache->io->pf_seek(cache->io, cache->offset) != VC_CONTAINER_SUCCESS)
         return 0;
   }

   ret = cache->io->pf_read(cache->io, cache->buffer, cache->buffer_end - cache->buffer);
   cache->size = ret;
   cache->position = 0;
   cache->io->priv->actual_offset = cache->offset + ret;
   return ret;
}

/*****************************************************************************/
static size_t vc_container_io_cache_refill_bypass( VC_CONTAINER_IO_T *p_ctx,
   VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, uint8_t *buffer, size_t size )
{
   size_t ret = vc_container_io_cache_flush( p_ctx, cache, 1 );

   if(ret) return 0; /* TODO what should we do there ? */

   if(p_ctx->priv->actual_offset != cache->offset)
   {
      if(cache->io->pf_seek(cache->io, cache->offset) != VC_CONTAINER_SUCCESS)
         return 0;
   }

   ret = cache->io->pf_read(cache->io, buffer, size);
   cache->size = cache->position = 0;
   cache->offset += ret;
   cache->io->priv->actual_offset = cache->offset;
   return ret;
}

/*****************************************************************************/
static size_t vc_container_io_cache_read( VC_CONTAINER_IO_T *p_ctx,
   VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, uint8_t *data, size_t size )
{
   size_t read = 0, bytes, ret;

   while(size)
   {
      bytes = cache->size - cache->position; /* Bytes left in cache */

#if 1 // FIXME Only if stream is seekable
      /* Try to read directly from the stream if the cache just gets in the way */
      if(!bytes && size > cache->mem_size)
      {
         bytes = cache->mem_size;
         ret = vc_container_io_cache_refill_bypass( p_ctx, cache, data + read, bytes);
         read += ret;

         if(ret != bytes) /* We didn't read as many bytes as we had hoped */
            goto end;

         size -= bytes;
         continue;
      }
#endif

      /* Refill the cache if it is empty */
      if(!bytes) bytes = vc_container_io_cache_refill( p_ctx, cache );
      if(!bytes) goto end;

      /* We do have some data in the cache so override the status */
      p_ctx->status = VC_CONTAINER_SUCCESS;

      /* Read data directly from the cache */
      if(bytes > size) bytes = size;
      memcpy(data + read, cache->buffer + cache->position, bytes);
      cache->position += bytes;
      read += bytes;
      size -= bytes;
   }

 end:
   vc_container_assert(cache->offset + cache->position == p_ctx->offset + read);
   return read;
}

/*****************************************************************************/
static int32_t vc_container_io_cache_write( VC_CONTAINER_IO_T *p_ctx,
   VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, const uint8_t *data, size_t size )
{
   int32_t written = 0;
   size_t bytes, ret;

   /* If we do not have a write cache then we need to flush it */
   if(cache->size && !cache->dirty)
   {
      ret = vc_container_io_cache_flush( p_ctx, cache, 1 );
      if(ret) return -(int32_t)ret;
   }

   while(size)
   {
      bytes = (cache->buffer_end - cache->buffer) - cache->position; /* Space left in cache */

      /* Flush the cache if it is full */
      if(!bytes)
      {
         /* Cache full, flush it */
         ret = vc_container_io_cache_flush( p_ctx, cache, 0 );
         if(ret)
         {
            written -= ret;
            return written;
         }
         continue;
      }

      if(bytes > size) bytes = size;

      if(!p_ctx->priv->async_io && bytes == cache->mem_size)
      {
         /* Write directly from the buffer */
         ret = cache->io->pf_write(cache->io, data + written, bytes);
         cache->offset += ret;
         cache->io->priv->actual_offset += ret;
      }
      else
      {
         /* Write in the cache */
         memcpy(cache->buffer + cache->position, data + written, bytes);
         cache->position += bytes;
         cache->dirty = 1;
         ret = bytes;
      }

      written += ret;
      if(ret != bytes) goto end;

      size -= bytes;
   }

 end:
   vc_container_assert(cache->offset + (int64_t)cache->position == p_ctx->offset + written);
   if(cache->position > cache->size) cache->size = cache->position;
   return written;
}

/*****************************************************************************/
static VC_CONTAINER_STATUS_T vc_container_io_cache_seek(VC_CONTAINER_IO_T *p_ctx,
   VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, int64_t offset)
{
   VC_CONTAINER_STATUS_T status;
   size_t shift, ret;

   /* Check if the seek position is within our cache */
   if(offset >= cache->offset && offset < cache->offset + (int64_t)cache->size)
   {
      cache->position = offset - cache->offset;
      return VC_CONTAINER_SUCCESS;
   }

   shift = cache->buffer - cache->mem;
   if(!cache->dirty && shift && cache->size &&
      offset >= cache->offset - (int64_t)shift && offset < cache->offset)
   {
      /* We need to refill the partial bit of the cache that we didn't take care of last time */
      status = cache->io->pf_seek(cache->io, cache->offset - shift);
      if(status != VC_CONTAINER_SUCCESS) return status;
      cache->offset -= shift;
      cache->buffer -= shift;

      ret = cache->io->pf_read(cache->io, cache->buffer, shift);
      vc_container_assert(ret == shift); /* FIXME: ret must = shift */
      cache->size += shift;
      cache->position = offset - cache->offset;
      cache->io->priv->actual_offset = cache->offset + ret;
      return VC_CONTAINER_SUCCESS;
   }

   if(cache->dirty) vc_container_io_cache_flush( p_ctx, cache, 1 );
   // FIXME: what if all the data couldn't be flushed ?

   if(p_ctx->priv->async_io) async_io_wait_complete( p_ctx->priv->async_io, cache, 1 );

   status = cache->io->pf_seek(cache->io, offset);
   if(status != VC_CONTAINER_SUCCESS) return status;

   vc_container_io_cache_flush( p_ctx, cache, 1 );

   cache->offset = offset;
   cache->io->priv->actual_offset = offset;
   return VC_CONTAINER_SUCCESS;
}

/*****************************************************************************/
static size_t vc_container_io_cache_flush( VC_CONTAINER_IO_T *p_ctx,
   VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, int complete )
{
   size_t ret = 0, shift;

   if(cache->position > cache->size) cache->size = cache->position;

   if(cache->dirty && cache->size)
   {
      if(p_ctx->priv->actual_offset != cache->offset)
      {
         if(p_ctx->priv->async_io) async_io_wait_complete( p_ctx->priv->async_io, cache, complete );

         if(cache->io->pf_seek(cache->io, cache->offset) != VC_CONTAINER_SUCCESS)
            return 0;
      }

      if(p_ctx->priv->async_io)
      {
         ret = async_io_write( p_ctx->priv->async_io, cache );
         if(async_io_wait_complete( p_ctx->priv->async_io, cache, complete ) != VC_CONTAINER_SUCCESS)
            ret = 0;
      }
      else
         ret = cache->io->pf_write(cache->io, cache->buffer, cache->size);

      cache->io->priv->actual_offset = cache->offset + ret;
      ret = cache->position - ret;
   }
   cache->dirty = 0;

   cache->offset += cache->size;
   if(cache->mem_size == cache->mem_max_size)
   {
      shift = cache->offset &(MEM_CACHE_ALIGNMENT-1);
      cache->buffer = cache->mem + shift;
   }

   cache->position = cache->size = 0;
   return ret;
}

/*****************************************************************************
 * Asynchronous I/O.
 * This is here to keep the I/O as busy as possible by allowing the writer
 * to continue its work while the I/O is taking place in the background.
 *****************************************************************************/

#ifdef ENABLE_CONTAINERS_ASYNC_IO
#include "vcos.h"

#define NUMPC(c,n,s) ((c) < (1<<(s)) ? (n) : ((n) / (c >> (s))))

static void stats_initialise(VC_CONTAINER_STATS_T *st, uint32_t shift)
{
   memset(st, 0, sizeof(VC_CONTAINER_STATS_T));
   st->shift = shift;
}

static void stats_add_value(VC_CONTAINER_STATS_T *st, uint32_t count, uint32_t num)
{
   uint32_t numpc;
   int i, j;

   if(count == 0)
      return;

   numpc = NUMPC(count, num, st->shift);
   // insert in the right place
   i=0;
   while(i < VC_CONTAINER_STATS_BINS && st->record[i].count != 0 && st->record[i].numpc > numpc)
      i++;

   if(st->record[i].count != 0 && st->record[i].numpc == numpc)
   {
      // equal numpc, can merge now
      st->record[i].count += count;
      st->record[i].num += num;
   }
   else
   {
      // shift higher records up
      for(j=VC_CONTAINER_STATS_BINS; j>i; j--)
         st->record[j] = st->record[j-1];

      // write record in
      st->record[i].count = count;
      st->record[i].num = num;
      st->record[i].numpc = numpc;

      // if full, join the two closest records
      if(st->record[VC_CONTAINER_STATS_BINS].count)
      {
         uint32_t min_diff = 0;
         j = -1;

         // find closest, based on difference between numpc
         for(i=0; i<VC_CONTAINER_STATS_BINS; i++)
         {
            uint32_t diff = st->record[i].numpc - st->record[i+1].numpc;
            if(j == -1 || diff < min_diff)
            {
               j = i;
               min_diff = diff;
            }
         }

         // merge these records
         st->record[j].count += st->record[j+1].count;
         st->record[j].num += st->record[j+1].num;
         st->record[j].numpc = NUMPC(st->record[j].count, st->record[j].num, st->shift);

         // shift down higher records
         while(++j < VC_CONTAINER_STATS_BINS)
            st->record[j] = st->record[j+1];

         // zero the free top record
         st->record[VC_CONTAINER_STATS_BINS].count = 0;
         st->record[VC_CONTAINER_STATS_BINS].num = 0;
         st->record[VC_CONTAINER_STATS_BINS].numpc = 0;
      }
   }
}

typedef struct VC_CONTAINER_IO_ASYNC_T
{
   VC_CONTAINER_IO_T *io;
   VCOS_THREAD_T thread;
   VCOS_SEMAPHORE_T spare_sema;
   VCOS_SEMAPHORE_T queue_sema;
   VCOS_EVENT_T wake_event;
   int quit;

   unsigned int num_area;
   uint8_t *mem[MAX_NUM_MEMORY_AREAS];    /**< Base address of memory areas */
   uint8_t *buffer[MAX_NUM_MEMORY_AREAS]; /**< When queued for writing, pointer to start of valid cache area */
   size_t size[MAX_NUM_MEMORY_AREAS];     /**< When queued for writing, size of valid area to write */
   unsigned int cur_area;

   unsigned char stack[3000];
   int error;

   int stats_enable;
   VC_CONTAINER_WRITE_STATS_T stats;

} VC_CONTAINER_IO_ASYNC_T;

/*****************************************************************************/
static void async_io_stats_initialise( struct VC_CONTAINER_IO_ASYNC_T *ctx, int enable )
{
   ctx->stats_enable = enable;
   stats_initialise(&ctx->stats.write, 8);
   stats_initialise(&ctx->stats.wait, 0);
   stats_initialise(&ctx->stats.flush, 0);
}

static void async_io_stats_get( struct VC_CONTAINER_IO_ASYNC_T *ctx, VC_CONTAINER_WRITE_STATS_T *stats )
{
   *stats = ctx->stats;
}

static void *async_io_thread(VOID *argv)
{
   VC_CONTAINER_IO_ASYNC_T *ctx = argv;
   unsigned int write_area = 0;

   while (1)
   {
      unsigned long time = 0;

      vcos_event_wait(&ctx->wake_event);
      if(ctx->quit) break;

      while(vcos_semaphore_trywait(&ctx->queue_sema) == VCOS_SUCCESS)
      {
         uint8_t *buffer = ctx->buffer[write_area];
         size_t size = ctx->size[write_area];

         if(ctx->stats_enable)
            time = vcos_getmicrosecs();

         if(ctx->io->pf_write(ctx->io, buffer, size) != size)
            ctx->error = 1;

         if(ctx->stats_enable)
            stats_add_value(&ctx->stats.write, size, vcos_getmicrosecs() - time);

         /* Signal that the write is done */
         vcos_semaphore_post(&ctx->spare_sema);

         if(++write_area == ctx->num_area)
            write_area = 0;
      }
   }

   return NULL;
}

static int async_io_write( VC_CONTAINER_IO_ASYNC_T *ctx, VC_CONTAINER_IO_PRIVATE_CACHE_T *cache )
{
   unsigned long time = 0;
   unsigned int offset;

   if(ctx->stats_enable)
      time = vcos_getmicrosecs();

   /* post the current area */
   ctx->buffer[ctx->cur_area] = cache->buffer;
   ctx->size[ctx->cur_area] = cache->size;
   vcos_semaphore_post(&ctx->queue_sema);
   vcos_event_signal(&ctx->wake_event);

   /* now we need to grab another area */
   vcos_semaphore_wait(&ctx->spare_sema);
   if(++ctx->cur_area == ctx->num_area)
      ctx->cur_area = 0;

   if(ctx->stats_enable)
      stats_add_value(&ctx->stats.wait, 1, vcos_getmicrosecs() - time);

   /* alter cache mem to point to the new cur_area */
   offset = cache->buffer - cache->mem;
   cache->mem = ctx->mem[ctx->cur_area];
   cache->buffer = cache->mem + offset;
   cache->buffer_end = cache->mem + cache->mem_size;

   return ctx->error ? 0 : cache->size;
}

static VC_CONTAINER_STATUS_T async_io_wait_complete( struct VC_CONTAINER_IO_ASYNC_T *ctx,
                                                     VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, int complete )
{
   unsigned int time = 0;

   if(ctx->stats_enable)
      time = vcos_getmicrosecs();

   if(complete)
   {
      int num;
      /* Need to make sure that all memory areas have been written out, so should have num-1 spare */
      for(num=0; num<ctx->num_area-1; num++)
         vcos_semaphore_wait(&ctx->spare_sema);

      for(num=0; num<ctx->num_area-1; num++)
         vcos_semaphore_post(&ctx->spare_sema);
   }
   else
   {
      /* Need to make sure we can acquire one memory area */
      vcos_semaphore_wait(&ctx->spare_sema);
      vcos_semaphore_post(&ctx->spare_sema);
   }

   if(ctx->stats_enable)
      stats_add_value(&ctx->stats.flush, 1, vcos_getmicrosecs() - time);

   return ctx->error ? VC_CONTAINER_ERROR_FAILED : VC_CONTAINER_SUCCESS;
}

static VC_CONTAINER_IO_ASYNC_T *async_io_start( VC_CONTAINER_IO_T *io, int num_areas, VC_CONTAINER_STATUS_T *status )
{
   VC_CONTAINER_IO_ASYNC_T *ctx = 0;
   VCOS_UNSIGNED pri = 0;

   /* Allocate our context  */
   ctx = malloc(sizeof(*ctx));
   if(!ctx) goto error_spare_sema;
   memset(ctx, 0, sizeof(*ctx));
   ctx->io = io;

   ctx->mem[0] = io->priv->cache->mem;

   for(ctx->num_area = 1; ctx->num_area < num_areas; ctx->num_area++)
   {
      ctx->mem[ctx->num_area] = malloc(io->priv->cache->mem_size);
      if(!ctx->mem[ctx->num_area])
         break;
   }

   if(ctx->num_area == 1) // no real benefit in asynchronous writes
      goto error_spare_sema;

   async_io_stats_initialise(ctx, 0);

   if(vcos_semaphore_create(&ctx->spare_sema, "async_spare_sem", ctx->num_area-1) != VCOS_SUCCESS)
      goto error_spare_sema;

   if(vcos_semaphore_create(&ctx->queue_sema, "async_queue_sem", 0) != VCOS_SUCCESS)
      goto error_queue_sema;

   if (vcos_event_create(&ctx->wake_event, "async_wake_event") != VCOS_SUCCESS)
      goto error_event;

   // run this thread at a slightly higher priority than the calling thread - that means that
   // we prefer to write to the SD card rather than filling the memory buffer.
   pri = vcos_thread_get_priority(vcos_thread_current());
   if(vcos_thread_create_classic(&ctx->thread, "async_io", async_io_thread, ctx,
         ctx->stack, sizeof(ctx->stack), pri-1, 10, VCOS_START) != VCOS_SUCCESS)
      goto error_thread;

   if(status) *status = VC_CONTAINER_SUCCESS;
   return ctx;

 error_thread:
   vcos_event_delete(&ctx->wake_event);
 error_event:
   vcos_semaphore_delete(&ctx->queue_sema);
 error_queue_sema:
   vcos_semaphore_delete(&ctx->spare_sema);
 error_spare_sema:
   if(ctx) free(ctx);
   if(status) *status = VC_CONTAINER_ERROR_FAILED;
   return 0;
}

static VC_CONTAINER_STATUS_T async_io_stop( VC_CONTAINER_IO_ASYNC_T *ctx )
{
   /* Block if a write operation is already in progress */
   //vcos_semaphore_wait(&ctx->sema);
   // XXX block until all done

   ctx->quit = 1;
   vcos_event_signal(&ctx->wake_event);
   vcos_thread_join(&ctx->thread,NULL);
   vcos_event_delete(&ctx->wake_event);
   vcos_semaphore_delete(&ctx->queue_sema);
   vcos_semaphore_delete(&ctx->spare_sema);

   while(ctx->num_area > 0)
      free(ctx->mem[--ctx->num_area]);

   free(ctx);
   return VC_CONTAINER_SUCCESS;
}
#else

static struct VC_CONTAINER_IO_ASYNC_T *async_io_start( VC_CONTAINER_IO_T *io, int num_areas, VC_CONTAINER_STATUS_T *status )
{
   VC_CONTAINER_PARAM_UNUSED(io);
   VC_CONTAINER_PARAM_UNUSED(num_areas);
   if(status) *status = VC_CONTAINER_ERROR_FAILED;
   return 0;
}

static int async_io_write( struct VC_CONTAINER_IO_ASYNC_T *ctx, VC_CONTAINER_IO_PRIVATE_CACHE_T *cache )
{
   VC_CONTAINER_PARAM_UNUSED(ctx);
   VC_CONTAINER_PARAM_UNUSED(cache);
   return 0;
}

static VC_CONTAINER_STATUS_T async_io_wait_complete( struct VC_CONTAINER_IO_ASYNC_T *ctx,
                                                     VC_CONTAINER_IO_PRIVATE_CACHE_T *cache, int complete )
{
   VC_CONTAINER_PARAM_UNUSED(ctx);
   VC_CONTAINER_PARAM_UNUSED(cache);
   VC_CONTAINER_PARAM_UNUSED(complete);
   return 0;
}

static VC_CONTAINER_STATUS_T async_io_stop( struct VC_CONTAINER_IO_ASYNC_T *ctx )
{
   VC_CONTAINER_PARAM_UNUSED(ctx);
   return VC_CONTAINER_SUCCESS;
}

static void async_io_stats_initialise( struct VC_CONTAINER_IO_ASYNC_T *ctx, int enable )
{
   VC_CONTAINER_PARAM_UNUSED(ctx);
   VC_CONTAINER_PARAM_UNUSED(enable);
}

static void async_io_stats_get( struct VC_CONTAINER_IO_ASYNC_T *ctx, VC_CONTAINER_WRITE_STATS_T *stats )
{
   VC_CONTAINER_PARAM_UNUSED(ctx);
   VC_CONTAINER_PARAM_UNUSED(stats);
}

#endif
