/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 *
 * Copyright (C) 2000 Ximian, Inc.
 *
 * Authors: Michael Zucchi <notzed@ximian.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License
 * as published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with the Gnome Library; see the file COPYING.LIB.  If not,
 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/*
  block file/cache/utility functions
*/

#include <stdio.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <glib.h>

#include "block.h"

/*#define MALLOC_CHECK*/

#ifdef MALLOC_CHECK
#include <mcheck.h>
#endif

#define d(x)
/*#define DEBUG*/

int block_log;

#ifdef IBEX_STATS
static void
init_stats(struct _memcache *index)
{
	index->stats = g_hash_table_new(g_direct_hash, g_direct_equal);
}

static void
dump_1_stat(int id, struct _stat_info *info, struct _memcache *index)
{
	printf("%d %d %d %d %d\n", id, info->read, info->write, info->cache_hit, info->cache_miss);
}

static void
dump_stats(struct _memcache *index)
{
	printf("Block reads writes hits misses\n");
	g_hash_table_foreach(index->stats, dump_1_stat, index);
}

static void
add_read(struct _memcache *index, int id)
{
	struct _stat_info *info;

	info = g_hash_table_lookup(index->stats, (void *)id);
	if (info == NULL) {
		info = g_malloc0(sizeof(*info));
		g_hash_table_insert(index->stats, (void *)id, info);
	}
	info->read++;
}

static void
add_write(struct _memcache *index, int id)
{
	struct _stat_info *info;

	info = g_hash_table_lookup(index->stats, (void *)id);
	if (info == NULL) {
		info = g_malloc0(sizeof(*info));
		g_hash_table_insert(index->stats, (void *)id, info);
	}
	info->write++;
}

static void
add_hit(struct _memcache *index, int id)
{
	struct _stat_info *info;

	info = g_hash_table_lookup(index->stats, (void *)id);
	if (info == NULL) {
		info = g_malloc0(sizeof(*info));
		g_hash_table_insert(index->stats, (void *)id, info);
	}
	info->cache_hit++;
}

static void
add_miss(struct _memcache *index, int id)
{
	struct _stat_info *info;

	info = g_hash_table_lookup(index->stats, (void *)id);
	if (info == NULL) {
		info = g_malloc0(sizeof(*info));
		g_hash_table_insert(index->stats, (void *)id, info);
	}
	info->cache_miss++;
}
#endif /* IBEX_STATS */

#ifdef MALLOC_CHECK
static void
checkmem(void *p)
{
	if (p) {
		int status = mprobe(p);

		switch (status) {
		case MCHECK_HEAD:
			printf("Memory underrun at %p\n", p);
			abort();
		case MCHECK_TAIL:
			printf("Memory overrun at %p\n", p);
			abort();
		case MCHECK_FREE:
			printf("Double free %p\n", p);
			abort();
		}
	}
}
#endif

/* simple list routines (for simplified memory management of cache/lists) */

/**
 * ibex_list_new:
 * @v: 
 * 
 * Initialise a list header.  A list header must always be initialised
 * before use.
 **/
void ibex_list_new(struct _list *v)
{
	v->head = (struct _listnode *)&v->tail;
	v->tail = 0;
	v->tailpred = (struct _listnode *)&v->head;
}

/**
 * ibex_list_addhead:
 * @l: List.
 * @n: Node to append.
 * 
 * Prepend a listnode to the head of the list @l.
 * 
 * Return value: Always @n.
 **/
struct _listnode *ibex_list_addhead(struct _list *l, struct _listnode *n)
{
	n->next = l->head;
	n->prev = (struct _listnode *)&l->head;
	l->head->prev = n;
	l->head = n;
	return n;
}

/**
 * ibex_list_addtail:
 * @l: 
 * @n: 
 * 
 * Append a listnode to the end of the list @l.
 * 
 * Return value: Always the same as @n.
 **/
struct _listnode *ibex_list_addtail(struct _list *l, struct _listnode *n)
{
	n->next = (struct _listnode *)&l->tail;
	n->prev = l->tailpred;
	l->tailpred->next = n;
	l->tailpred = n;
	return n;
}

/**
 * ibex_list_remove:
 * @n: The node to remove.
 * 
 * Remove a listnode from a list.
 * 
 * Return value: Always the same as @n.
 **/
struct _listnode *ibex_list_remove(struct _listnode *n)
{
	n->next->prev = n->prev;
	n->prev->next = n->next;
	return n;
}

static struct _memblock *
memblock_addr(struct _block *block)
{
	return (struct _memblock *)(((char *)block) - G_STRUCT_OFFSET(struct _memblock, data));
}

/* read/sync the rootblock into the block_cache structure */
static int
ibex_block_read_root(struct _memcache *block_cache)
{
	lseek(block_cache->fd, 0, SEEK_SET);
	if (read(block_cache->fd, &block_cache->root, sizeof(block_cache->root)) != sizeof(block_cache->root)) {
		
		return -1;
	}
	return 0;
}

static int
ibex_block_sync_root(struct _memcache *block_cache)
{
	lseek(block_cache->fd, 0, SEEK_SET);
	if (write(block_cache->fd, &block_cache->root, sizeof(block_cache->root)) != sizeof(block_cache->root)) {
		return -1;
	}
	return fsync(block_cache->fd);
}


/**
 * ibex_block_dirty:
 * @block: 
 * 
 * Dirty a block.  This will cause it to be written to disk on
 * a cache sync, or when the block is flushed from the cache.
 **/
void
ibex_block_dirty(struct _block *block)
{
	memblock_addr(block)->flags |= BLOCK_DIRTY;
}

static void
sync_block(struct _memcache *block_cache, struct _memblock *memblock)
{
	if (block_log)
		printf("writing block %d\n", memblock->block);

	lseek(block_cache->fd, memblock->block, SEEK_SET);
	if (write(block_cache->fd, &memblock->data, sizeof(memblock->data)) != -1) {
		memblock->flags &= ~BLOCK_DIRTY;
	}
#ifdef IBEX_STATS
	add_write(block_cache, memblock->block);
#endif
}

/**
 * ibex_block_cache_sync:
 * @block_cache: 
 * 
 * Ensure the block cache is fully synced to disk.
 **/
void
ibex_block_cache_sync(struct _memcache *block_cache)
{
	struct _memblock *memblock;

	if (block_cache->failed)
		return;

	memblock = (struct _memblock *)block_cache->nodes.head;
	while (memblock->next) {
#ifdef MALLOC_CHECK
		checkmem(memblock);
#endif
		if (memblock->flags & BLOCK_DIRTY) {
			sync_block(block_cache, memblock);
		}
		memblock = memblock->next;
	}

	block_cache->root.flags |= IBEX_ROOT_SYNCF;
	if (ibex_block_sync_root(block_cache) != 0) {
		block_cache->root.flags &= ~IBEX_ROOT_SYNCF;
	}

#ifdef IBEX_STATS
	dump_stats(block_cache);
#endif

}

#ifdef MALLOC_CHECK
static void
check_cache(struct _memcache *block_cache)
{
	struct _memblock *mw, *mn;

	checkmem(block_cache);
	checkmem(block_cache->index);

	mw = (struct _memblock *)block_cache->nodes.head;
	mn = mw->next;
	while (mn) {
		checkmem(mw);
		mw = mn;
		mn = mn->next;
	}
}
#endif

/**
 * ibex_block_cache_flush:
 * @block_cache: 
 * 
 * Ensure the block cache is fully synced to disk, and then flush
 * its contents from memory.
 **/
void
ibex_block_cache_flush(struct _memcache *block_cache)
{
	struct _memblock *mw, *mn;

	ibex_block_cache_sync(block_cache);

	mw = (struct _memblock *)block_cache->nodes.head;
	mn = mw->next;
	while (mn) {
		g_hash_table_remove(block_cache->index, (void *)mw->block);
		g_free(mw);
		mw = mn;
		mn = mn->next;
	}
	ibex_list_new(&block_cache->nodes);
}

/**
 * ibex_block_read:
 * @block_cache: 
 * @blockid: 
 * 
 * Read the data of a block by blockid.  The data contents is backed by
 * the block cache, and should be considered static.
 *
 * TODO; should this return a NULL block on error?
 *
 * Return value: The address of the block data (which may be cached).
 **/
struct _block *
ibex_block_read(struct _memcache *block_cache, blockid_t blockid)
{
	struct _memblock *memblock;

#ifdef MALLOC_CHECK
	check_cache(block_cache);
#endif

	/* nothing can read the root block directly */
	ibex_block_cache_assert(block_cache, blockid != 0);
	ibex_block_cache_assert(block_cache, blockid < block_cache->root.roof);

	memblock = g_hash_table_lookup(block_cache->index, (void *)blockid);

#ifdef MALLOC_CHECK
	check_cache(block_cache);
#endif

	if (memblock) {
		d(printf("foudn blockid in cache %d = %p\n", blockid, &memblock->data));

		/* 'access' page */
		ibex_list_remove((struct _listnode *)memblock);
		ibex_list_addtail(&block_cache->nodes, (struct _listnode *)memblock);

#ifdef MALLOC_CHECK
		check_cache(block_cache);
#endif

#ifdef IBEX_STATS
		add_hit(block_cache, memblock->block);
#endif

#ifdef MALLOC_CHECK
		check_cache(block_cache);
#endif

		return &memblock->data;
	}
#ifdef IBEX_STATS
	add_miss(block_cache, blockid);
	add_read(block_cache, blockid);
#endif
	if (block_log)
		printf("miss block %d\n", blockid);

	d(printf("loading blockid from disk %d\n", blockid));
	memblock = g_malloc(sizeof(*memblock));
	memblock->block = blockid;
	memblock->flags = 0;
	lseek(block_cache->fd, blockid, SEEK_SET);
	memset(&memblock->data, 0, sizeof(memblock->data));
	read(block_cache->fd, &memblock->data, sizeof(memblock->data));

	ibex_list_addtail(&block_cache->nodes, (struct _listnode *)memblock);
	g_hash_table_insert(block_cache->index, (void *)blockid, memblock);
	if (block_cache->count >= CACHE_SIZE) {
		struct _memblock *old = (struct _memblock *)block_cache->nodes.head;
		d(printf("discaring cache block %d\n", old->block));
		g_hash_table_remove(block_cache->index, (void *)old->block);
		ibex_list_remove((struct _listnode *)old);
		if (old->flags & BLOCK_DIRTY) {
			/* are we about to un-sync the file?  update root and sync it */
			if (block_cache->root.flags & IBEX_ROOT_SYNCF) {
				d(printf("Unsyncing root block\n"));

				block_cache->root.flags &= ~IBEX_ROOT_SYNCF;
				if (ibex_block_sync_root(block_cache) != 0) {
					/* what do we do?  i dont know! */
					g_warning("Could not sync root block of index: %s", strerror(errno));
				}
			}
			sync_block(block_cache, old);
		}
		g_free(old);
	} else {
		block_cache->count++;
	}

	d(printf("  --- cached blocks : %d\n", block_cache->count));

#ifdef MALLOC_CHECK
	check_cache(block_cache);
#endif
	return &memblock->data;
}

void
ibex_block_cache_fail(struct _memcache *block_cache, char *where, int line, char *why)
{
	block_cache->failed = TRUE;
	block_cache->root.flags &= ~IBEX_ROOT_SYNCF;
	/* and blow it away, we can do nothing better yet */
	ftruncate(block_cache->fd, 0);

	g_warning("%s(%d): Integrity assertion failed: '%s' on file '%s'", where, line, why, block_cache->name);

	longjmp(block_cache->failenv, 1);
}

/**
 * ibex_block_cache_open:
 * @name: 
 * @flags: Flags as to open(2), should use O_RDWR and optionally O_CREAT.
 * @mode: Mose as to open(2)
 * 
 * Open a block file.
 * 
 * FIXME; this currently also initialises the word and name indexes
 * because their pointers are stored in the root block.  Should be
 * upto the caller to manage these pointers/data.
 *
 * Return value: NULL if the backing file could not be opened.
 **/
struct _memcache *
ibex_block_cache_open(const char *name, int flags, int mode)
{
	struct _memcache *block_cache = g_malloc0(sizeof(*block_cache));

	d(printf("opening ibex file: %s", name));

	/* setup cache */
	ibex_list_new(&block_cache->nodes);
	block_cache->count = 0;
	block_cache->index = g_hash_table_new(g_direct_hash, g_direct_equal);
	block_cache->fd = open(name, flags, mode);
	block_cache->failed = FALSE;
	block_cache->name = g_strdup(name);

	if (block_cache->fd == -1) {
		g_hash_table_destroy(block_cache->index);
		g_free(block_cache);
		return NULL;
	}

	if (ibex_block_cache_setjmp(block_cache) != 0) {
		close(block_cache->fd);
		g_hash_table_destroy(block_cache->index);
		g_free(block_cache);
		return NULL;		
	}

	ibex_block_read_root(block_cache);
	if (block_cache->root.roof == 0
	    || memcmp(block_cache->root.version, IBEX_VERSION, 4)
	    || ((block_cache->root.flags & IBEX_ROOT_SYNCF) == 0)) {
		d(printf("Initialising superblock\n"));
		/* reset root data */
		memcpy(block_cache->root.version, IBEX_VERSION, 4);
		block_cache->root.roof = 1024;
		block_cache->root.free = 0;
		block_cache->root.words = 0;
		block_cache->root.names = 0;
		block_cache->root.tail = 0;	/* list of tail blocks */
		block_cache->root.flags = 0;
		ibex_block_sync_root(block_cache);
		/* reset the file contents */
		ftruncate(block_cache->fd, 1024);
	} else {
		d(printf("superblock already initialised:\n"
			 " roof = %d\n free = %d\n words = %d\n names = %d\n tail = %d\n",
			 block_cache->root.roof, block_cache->root.free,
			 block_cache->root.words, block_cache->root.names, block_cache->root.tail));
	}
	/* FIXME: this should be moved higher up in the object tree */
	{
		struct _IBEXWord *ibex_create_word_index_mem(struct _memcache *bc, blockid_t *wordroot, blockid_t *nameroot);

		block_cache->words = ibex_create_word_index_mem(block_cache, &block_cache->root.words,&block_cache->root.names);
	}

#ifdef IBEX_STATS
	init_stats(block_cache);
#endif

	return block_cache;
}

/**
 * ibex_block_cache_close:
 * @block_cache: 
 * 
 * Close the block file, sync any remaining cached data
 * to disk, and free all resources.
 **/
void
ibex_block_cache_close(struct _memcache *block_cache)
{
	struct _memblock *mw, *mn;

	ibex_block_cache_sync(block_cache);
	close(block_cache->fd);
	g_free(block_cache->name);

	mw = (struct _memblock *)block_cache->nodes.head;
	mn = mw->next;
	while (mn) {
		g_free(mw);
		mw = mn;
		mn = mw->next;
	}

	g_hash_table_destroy(block_cache->index);

	g_free(block_cache);
}

/**
 * ibex_block_free:
 * @block_cache: 
 * @blockid: 
 * 
 * Return a block to the free pool.
 **/
void
ibex_block_free(struct _memcache *block_cache, blockid_t blockid)
{
	struct _block *block = ibex_block_read(block_cache, blockid);

	block->next = block_number(block_cache->root.free);
	block_cache->root.free = blockid;
	ibex_block_dirty((struct _block *)block);
}

/**
 * ibex_block_get:
 * @block_cache: 
 * 
 * Allocate a new block, or access a previously freed block and return
 * its block id.  The block will have zeroed contents.
 * 
 * Return value: 0 if there are no blocks left (disk full/read only
 * file, etc).
 **/
blockid_t
ibex_block_get(struct _memcache *block_cache)
{
	struct _block *block;
	blockid_t head;

	if (block_cache->root.free) {
		head = block_cache->root.free;
		block = ibex_block_read(block_cache, head);
		block_cache->root.free = block_location(block->next);
	} else {
		/* TODO: check the block will fit first */
		/* TODO: no need to read this block, can allocate it manually (saves a syscall/read) */
		head = block_cache->root.roof;
		block_cache->root.roof += BLOCK_SIZE;
		block = ibex_block_read(block_cache, head);
	}

	ibex_block_cache_assert(block_cache, head != 0);

	d(printf("new block = %d\n", head));
	block->next = 0;
	block->used = 0;
	ibex_block_dirty(block);
	return head;
}
