/*
 * bonobo-stream-cache.c: 
 *
 * A simple cache for streams (direct mapped, write back)
 *
 * Author:
 *	Dietmar Maurer (dietmar@helixcode.com)
 *
 * Copyright 2000 Helix Code, Inc.
 */

#include <config.h>
#include <string.h>
#include <bonobo/bonobo-exception.h>
#include <bonobo/bonobo-stream-client.h>

#include "bonobo-stream-cache.h"

/* configurable values for cache size */

#define SC_PAGE_SIZE_BITS 14
#define SC_CACHE_SIZE_BITS 5

/* some handy macros */

#define SC_PAGE_SIZE         (1 << (SC_PAGE_SIZE_BITS - 1))
#define SC_CACHE_SIZE        (1 << (SC_CACHE_SIZE_BITS - 1))

#define SC_CACHE_TAG(pos)    (pos >> (SC_PAGE_SIZE_BITS - 1))
#define SC_TAG_POS(tag)      (tag << (SC_PAGE_SIZE_BITS - 1))
#define SC_CACHE_INDEX(pos)  (SC_CACHE_TAG(pos) & (SC_CACHE_SIZE - 1))
#define SC_BLOCK_OFFSET(pos) (pos & (SC_PAGE_SIZE - 1))


typedef struct {
	char     buf [SC_PAGE_SIZE];
	long     tag;
	gboolean valid;
	gboolean dirty;
} CacheEntry; 

struct _BonoboStreamCachePrivate {
	Bonobo_Stream cs;
	long          pos;
	long          size;
	CacheEntry    cache [SC_CACHE_SIZE];
};

static void
bonobo_stream_cache_invalidate (BonoboStreamCache *stream_cache, 
				long               pos)
{
	long i, tag = SC_CACHE_TAG (pos);

	for (i = 0; i < SC_CACHE_SIZE; i++) {
		if (stream_cache->priv->cache [i].valid &&
		    (stream_cache->priv->cache [i].tag >= tag))
			stream_cache->priv->cache [i].valid = FALSE;
	}
}

static void
bonobo_stream_cache_flush (BonoboStreamCache *stream, 
			   int                index,
			   CORBA_Environment *ev)
{
	long i, end, pos;
	
	end = index < 0 ? SC_CACHE_SIZE : index + 1;

	for (i = index < 0 ? 0 : index; i < end; i++) {
		if (((index < 0) || (index == i)) &&
		    stream->priv->cache [i].valid &&
		    stream->priv->cache [i].dirty) {
			pos = SC_TAG_POS (stream->priv->cache [i].tag);
			
			Bonobo_Stream_seek (stream->priv->cs, pos,
					    Bonobo_Stream_SEEK_SET, ev);
			if (BONOBO_EX (ev))
				continue;

			bonobo_stream_client_write (stream->priv->cs,
			        stream->priv->cache [i].buf,
						    SC_PAGE_SIZE, ev);
			if (!BONOBO_EX (ev))
				stream->priv->cache [i].dirty = FALSE;
		}
	}
}

static void
bonobo_stream_cache_load (BonoboStreamCache *stream, 
			  long               tag,
			  CORBA_Environment *ev)
{
	Bonobo_Stream_iobuf *iobuf;
	long pos, index;

	pos = SC_TAG_POS (tag);
	index = SC_CACHE_INDEX (pos);

	bonobo_stream_cache_flush (stream, index, ev);
	if (BONOBO_EX (ev))
		return;

	Bonobo_Stream_seek (stream->priv->cs, pos, Bonobo_Stream_SEEK_SET, ev);
	if (BONOBO_EX (ev))
		return;

	Bonobo_Stream_read (stream->priv->cs, SC_PAGE_SIZE, &iobuf, ev);
	if (BONOBO_EX (ev))
		return;
	
	if (iobuf->_length < SC_PAGE_SIZE) /* eof  - fill with zero */
		memset (stream->priv->cache [index].buf + iobuf->_length, 0,
			SC_PAGE_SIZE - iobuf->_length);
				
	if ((pos + iobuf->_length) > stream->priv->size)
		stream->priv->size = pos + iobuf->_length;

	memcpy (stream->priv->cache [index].buf, iobuf->_buffer, 
		iobuf->_length);
	
	stream->priv->cache [index].valid = TRUE;
	stream->priv->cache [index].dirty = FALSE;
	stream->priv->cache [index].tag = tag;

	CORBA_free (iobuf);
}

static long
bonobo_stream_cache_read (BonoboStreamCache *stream, 
			  long               count, 
			  char              *buffer,
			  CORBA_Environment *ev)
{
	long tag, bytes_read = 0;
	int index, offset, bc, d;

	while (bytes_read < count) {
		index = SC_CACHE_INDEX (stream->priv->pos);
		offset = SC_BLOCK_OFFSET (stream->priv->pos);
		tag = SC_CACHE_TAG (stream->priv->pos);

		if ((stream->priv->pos < stream->priv->size) &&
		    stream->priv->cache [index].valid &&
		    stream->priv->cache [index].tag == tag) {
			bc = SC_PAGE_SIZE - offset;

			if ((bytes_read + bc) > count)
				bc = count - bytes_read;

			if ((d = (stream->priv->pos + bc) - 
			     stream->priv->size) > 0)
				bc -= d;
			if (!bc)
				return bytes_read;

			memcpy (buffer + bytes_read, 
				stream->priv->cache [index].buf + offset, bc);
			bytes_read += bc;
			stream->priv->pos += bc;
		} else {
			bonobo_stream_cache_load (stream, tag, ev);
			if (BONOBO_EX (ev))
				break;
			if (stream->priv->pos >= stream->priv->size)
				break;
		}
	}

	return bytes_read;
}

static BonoboStream *
create_stream_cache_server (const BonoboStreamCache *stream_cache)
{
	Bonobo_Stream corba_stream;

	corba_stream = bonobo_stream_corba_object_create (
		BONOBO_OBJECT (stream_cache));

	return BONOBO_STREAM (
		bonobo_object_construct (BONOBO_OBJECT (stream_cache), 
					 corba_stream));
}

static void
bonobo_stream_cache_destroy (GtkObject *object)
{
	BonoboStreamCache *stream_cache = BONOBO_STREAM_CACHE (object);

	if (stream_cache->priv->cs)
		bonobo_object_release_unref (stream_cache->priv->cs, NULL);

	g_free (stream_cache->priv);
}

static Bonobo_StorageInfo*
impl_Bonobo_Stream_getInfo (BonoboStream                   *stream, 
			    const Bonobo_StorageInfoFields  mask,
			    CORBA_Environment              *ev)
{
	BonoboStreamCache *stream_cache = BONOBO_STREAM_CACHE (stream);
	
	return Bonobo_Stream_getInfo (stream_cache->priv->cs, mask, ev);
}

static void
impl_Bonobo_Stream_setInfo (BonoboStream                   *stream, 
			    const Bonobo_StorageInfo       *info,
			    const Bonobo_StorageInfoFields  mask, 
			    CORBA_Environment              *ev)
{
	BonoboStreamCache *stream_cache = BONOBO_STREAM_CACHE (stream);
	
	Bonobo_Stream_setInfo (stream_cache->priv->cs, info, mask, ev);
}

static void
impl_Bonobo_Stream_write (BonoboStream              *bonobo_stream, 
			  const Bonobo_Stream_iobuf *buffer,
			  CORBA_Environment         *ev)
{
	BonoboStreamCache *stream = BONOBO_STREAM_CACHE (bonobo_stream);
	long tag, bytes_written = 0;
	int index, offset, bc;
	
	while (bytes_written < buffer->_length) {
		index = SC_CACHE_INDEX (stream->priv->pos);
		offset = SC_BLOCK_OFFSET (stream->priv->pos);
		tag = SC_CACHE_TAG (stream->priv->pos);

		if (stream->priv->cache [index].valid &&
		    stream->priv->cache [index].tag == tag) {
			bc = SC_PAGE_SIZE - offset;
			if (bc > buffer->_length) 
				bc = buffer->_length;
			memcpy (stream->priv->cache [index].buf + offset,
				buffer->_buffer + bytes_written, bc);
			bytes_written += bc;
			stream->priv->pos += bc;
			stream->priv->cache [index].dirty = TRUE;
		} else {
			bonobo_stream_cache_load (stream, tag, ev);
			if (BONOBO_EX (ev))
				break;
		}
	}
}

static void
impl_Bonobo_Stream_read (BonoboStream         *stream, 
			 CORBA_long            count,
			 Bonobo_Stream_iobuf **buffer, 
			 CORBA_Environment    *ev)
{
	BonoboStreamCache *stream_cache = BONOBO_STREAM_CACHE (stream);
	CORBA_octet *data;

	if (count < 0) {
		bonobo_exception_set (ev, ex_Bonobo_Stream_IOError);
		return;
	}

	*buffer = Bonobo_Stream_iobuf__alloc ();
	CORBA_sequence_set_release (*buffer, TRUE);
	data = CORBA_sequence_CORBA_octet_allocbuf (count);
	(*buffer)->_buffer = data;
	(*buffer)->_length = bonobo_stream_cache_read (stream_cache, count, 
						       data, ev);
}

static CORBA_long
impl_Bonobo_Stream_seek (BonoboStream           *stream, 
			 CORBA_long              offset, 
			 Bonobo_Stream_SeekType  whence, 
			 CORBA_Environment      *ev)
{
	BonoboStreamCache *stream_cache = BONOBO_STREAM_CACHE (stream);
	
	stream_cache->priv->pos = Bonobo_Stream_seek (stream_cache->priv->cs, 
						      offset, whence, ev);

	return stream_cache->priv->pos;
}

static void
impl_Bonobo_Stream_truncate (BonoboStream      *stream, 
			     const CORBA_long   new_size, 
			     CORBA_Environment *ev)
{
	BonoboStreamCache *stream_cache = BONOBO_STREAM_CACHE (stream);
	
	bonobo_stream_cache_invalidate (stream_cache, new_size);
	
	stream_cache->priv->size = new_size;

	Bonobo_Stream_truncate (stream_cache->priv->cs, new_size, ev);
}

static void
impl_Bonobo_Stream_copyTo (BonoboStream      *stream, 
			   const CORBA_char  *dest,
			   const CORBA_long   bytes, 
			   CORBA_long        *read_bytes,
			   CORBA_long        *written_bytes, 
			   CORBA_Environment *ev)
{
	BonoboStreamCache *stream_cache = BONOBO_STREAM_CACHE (stream);

	Bonobo_Stream_copyTo (stream_cache->priv->cs, dest, bytes, read_bytes,
			      written_bytes, ev);
}

static void
impl_Bonobo_Stream_commit (BonoboStream      *stream, 
			   CORBA_Environment *ev)
{
	BonoboStreamCache *stream_cache = BONOBO_STREAM_CACHE (stream);

	bonobo_stream_cache_flush (stream_cache, -1, ev);
	 
	Bonobo_Stream_commit (stream_cache->priv->cs, ev);
}

static void
impl_Bonobo_Stream_revert (BonoboStream      *stream, 
			   CORBA_Environment *ev)
{
	BonoboStreamCache *stream_cache = BONOBO_STREAM_CACHE (stream);

	bonobo_stream_cache_invalidate (stream_cache, 0);

	Bonobo_Stream_revert (stream_cache->priv->cs, ev);
}

static void
bonobo_stream_cache_init (BonoboStreamCache *stream)
{
	stream->priv = g_new0 (BonoboStreamCachePrivate, 1);
}

static void
bonobo_stream_cache_class_init (BonoboStreamCacheClass *class)
{
	GtkObjectClass *object_class = (GtkObjectClass *) class;
	BonoboStreamClass *sclass = BONOBO_STREAM_CLASS (class);
	
	sclass->get_info = impl_Bonobo_Stream_getInfo;
	sclass->set_info = impl_Bonobo_Stream_setInfo;
	sclass->write    = impl_Bonobo_Stream_write;
	sclass->read     = impl_Bonobo_Stream_read;
	sclass->seek     = impl_Bonobo_Stream_seek;
	sclass->truncate = impl_Bonobo_Stream_truncate;
	sclass->copy_to  = impl_Bonobo_Stream_copyTo;
	sclass->commit   = impl_Bonobo_Stream_commit;
	sclass->revert   = impl_Bonobo_Stream_revert;

	object_class->destroy = bonobo_stream_cache_destroy;
}

GtkType
bonobo_stream_cache_get_type (void)
{
	static GtkType type = 0;

	if (!type) {
		GtkTypeInfo info = {
			"BonoboStreamCache",
			sizeof (BonoboStreamCache),
			sizeof (BonoboStreamCacheClass),
			(GtkClassInitFunc) bonobo_stream_cache_class_init,
			(GtkObjectInitFunc) bonobo_stream_cache_init,
			NULL, /* reserved 1 */
			NULL, /* reserved 2 */
			(GtkClassInitFunc) NULL
		};
		
		type = gtk_type_unique (bonobo_stream_get_type (), &info);
	}
  
	return type;
}

/** 
 * bonobo_stream_cache_create:
 * @cs: a reference to the stream we want to cache
 * @opt_ev: an optional environment
 *
 * Returns a new BonoboStream object
 */
BonoboStream *
bonobo_stream_cache_create (Bonobo_Stream      cs,
			    CORBA_Environment *opt_ev)
{
	BonoboStreamCache *stream;
	CORBA_Environment  ev, *my_ev;

	bonobo_return_val_if_fail (cs != NULL, NULL, opt_ev);
	
	if (!(stream = gtk_type_new (bonobo_stream_cache_get_type ()))) {
		if (opt_ev)
			bonobo_exception_set (opt_ev, ex_Bonobo_Storage_IOError);
		return NULL;
	}

	if (!opt_ev) {
		CORBA_exception_init (&ev);
		my_ev = &ev;
	} else
		my_ev = opt_ev;

	stream->priv->cs = bonobo_object_dup_ref (cs, my_ev);

	if (BONOBO_EX (my_ev)) {
		if (!opt_ev)
			CORBA_exception_free (&ev);
		bonobo_object_unref (BONOBO_OBJECT (stream));
		return NULL;
	}

	if (!opt_ev)
		CORBA_exception_free (&ev);

	if (!create_stream_cache_server (stream)) {
		bonobo_object_unref (BONOBO_OBJECT (stream));
		bonobo_exception_set (opt_ev, ex_Bonobo_Storage_IOError);
		return NULL;
	}

	return BONOBO_STREAM (stream);
}



syntax highlighted by Code2HTML, v. 0.9.1