/*
** Copyright 2003, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/


#include "amiga_rdb.h"

#include <ByteOrder.h>
#include <KernelExport.h>
#include <ddm_modules.h>
#ifdef _BOOT_MODE
#	include <boot/partitions.h>
#else
#	include <DiskDeviceTypes.h>
#endif
#include <util/kernel_cpp.h>

#include <unistd.h>
#include <string.h>


#define TRACE_AMIGA_RDB 0
#if TRACE_AMIGA_RDB
#	define TRACE(x) dprintf x
#else
#	define TRACE(x) ;
#endif

#ifdef _BOOT_MODE
static const char *kPartitionModuleName = "partitioning_systems/amiga_rdb/v1";
#else
static const char *kPartitionModuleName = NULL;
#endif


template<typename Type> bool
validate_check_sum(Type *type)
{
	if (type->SummedLongs() != sizeof(*type) / sizeof(uint32))
		return false;

	// check checksum
	uint32 *longs = (uint32 *)type;
	uint32 sum = 0;
	for (uint32 i = 0; i < type->SummedLongs(); i++)
		sum += B_BENDIAN_TO_HOST_INT32(longs[i]);

#if TRACE_AMIGA_RDB
	if (sum != 0)
		TRACE(("search_rdb: check sum is incorrect!\n"));
#endif

	return sum == 0;
}


#if TRACE_AMIGA_RDB
static char *
get_tupel(uint32 id)
{
	static unsigned char tupel[5];

	tupel[0] = 0xff & (id >> 24);
	tupel[1] = 0xff & (id >> 16);
	tupel[2] = 0xff & (id >> 8);
	tupel[3] = 0xff & (id);
	tupel[4] = 0;
	for (int16 i = 0;i < 4;i++) {
		if (tupel[i] < ' ' || tupel[i] > 128)
			tupel[i] = '.';
	}

	return (char *)tupel;
}
#endif


static status_t
get_next_partition(int fd, rigid_disk_block &rdb, uint32 &cookie, partition_block &partition)
{
	if (cookie == 0) {
		// first entry
		cookie = rdb.FirstPartition();
	} else if (cookie == 0xffffffff) {
		// last entry
		return B_ENTRY_NOT_FOUND;
	}

	ssize_t bytesRead = read_pos(fd, (off_t)cookie * rdb.BlockSize(), (void *)&partition,
							sizeof(partition_block));
	if (bytesRead < (ssize_t)sizeof(partition_block))
		return B_ERROR;

	// ToDo: Should we retry with the next block if the following test fails, as
	//		long as this we find partition_blocks within a reasonable range?

	if (partition.ID() != RDB_PARTITION_ID || !validate_check_sum<partition_block>(&partition))
		return B_BAD_DATA;

	cookie = partition.Next();
	return B_OK;
}


static bool
search_rdb(int fd, rigid_disk_block **_rdb)
{
	for (int32 sector = 0; sector < RDB_LOCATION_LIMIT; sector++) {
		uint8 buffer[512];
		ssize_t bytesRead = read_pos(fd, sector * 512, buffer, sizeof(buffer));
		if (bytesRead < (ssize_t)sizeof(buffer)) {
			TRACE(("search_rdb: read error: %ld\n", bytesRead));
			return false;
		}

		rigid_disk_block *rdb = (rigid_disk_block *)buffer;
		if (rdb->ID() == RDB_DISK_ID && validate_check_sum<rigid_disk_block>(rdb)) {
			// copy the RDB to a new piece of memory
			rdb = new rigid_disk_block();
			memcpy(rdb, buffer, sizeof(rigid_disk_block));

			*_rdb = rdb;
			return true;
		}
	}

	return false;
}


//	#pragma mark -
//	AmigaRDB public module interface


static status_t
amiga_rdb_std_ops(int32 op, ...)
{
	switch (op) {
		case B_MODULE_INIT:
		case B_MODULE_UNINIT:
			return B_OK;
	}

	return B_ERROR;
}


static float
amiga_rdb_identify_partition(int fd, partition_data *partition, void **_cookie)
{
	rigid_disk_block *rdb;
	if (!search_rdb(fd, &rdb))
		return B_ERROR;

	*_cookie = (void *)rdb;
	return 0.5f;
}


static status_t
amiga_rdb_scan_partition(int fd, partition_data *partition, void *_cookie)
{
	TRACE(("amiga_rdb_scan_partition(cookie = %p)\n", _cookie));

	rigid_disk_block &rdb = *(rigid_disk_block *)_cookie;

	partition->status = B_PARTITION_VALID;
	partition->flags |= B_PARTITION_PARTITIONING_SYSTEM
						| B_PARTITION_READ_ONLY;
	partition->content_size = partition->size;

	// scan all children

	partition_block partitionBlock;
	uint32 index = 0, cookie = 0;
	status_t status;

	while ((status = get_next_partition(fd, rdb, cookie, partitionBlock)) == B_OK) {
		disk_environment &environment = *(disk_environment *)&partitionBlock.environment[0];
		TRACE(("amiga_rdb: file system: %s\n", get_tupel(B_BENDIAN_TO_HOST_INT32(environment.dos_type))));

		if (environment.Start() + environment.Size() > (uint64)partition->size) {
			TRACE(("amiga_rdb: child partition exceeds existing space (%Ld bytes)\n", environment.Size()));
			continue;
		}

		partition_data *child = create_child_partition(partition->id, index++, -1);
		if (child == NULL) {
			TRACE(("amiga_rdb: Creating child at index %ld failed\n", index - 1));
			return B_ERROR;
		}

		child->offset = partition->offset + environment.Start();
		child->size = environment.Size();			
		child->block_size = environment.BlockSize();
	}

	if (status == B_ENTRY_NOT_FOUND)
		return B_OK;

	return status;
}


static void
amiga_rdb_free_identify_partition_cookie(partition_data *partition, void *_cookie)
{
	delete (rigid_disk_block *)_cookie;
}


#ifndef _BOOT_MODE
static partition_module_info gAmigaPartitionModule = {
#else
partition_module_info gAmigaPartitionModule = {
#endif
	{
		kPartitionModuleName,
		0,
		amiga_rdb_std_ops
	},
	kPartitionTypeAmiga,				// pretty_name
	0,									// flags

	// scanning
	amiga_rdb_identify_partition,		// identify_partition
	amiga_rdb_scan_partition,			// scan_partition
	amiga_rdb_free_identify_partition_cookie,	// free_identify_partition_cookie
	NULL,
//	amiga_rdb_free_partition_cookie,			// free_partition_cookie
//	amiga_rdb_free_partition_content_cookie,	// free_partition_content_cookie
};

#ifndef _BOOT_MODE
partition_module_info *modules[] = {
	&gAmigaPartitionModule,
	NULL
};
#endif

