#include "caching/metadata.h"
#include "caching/superblock.h"
#include "persistent-data/space-maps/core.h"

using namespace caching;
namespace pd = persistent_data;

//----------------------------------------------------------------

namespace {
	// FIXME: duplication
	transaction_manager::ptr
	open_tm(block_manager::ptr bm) {
		auto nr_blocks = bm->get_nr_blocks();
		if (!nr_blocks)
			throw runtime_error("Metadata is not large enough for superblock.");

		space_map::ptr sm{create_core_map(nr_blocks)};
		sm->inc(SUPERBLOCK_LOCATION);
		transaction_manager::ptr tm(new transaction_manager(bm, sm));
		return tm;
	}

	void
	copy_space_maps(space_map::ptr lhs, space_map::ptr rhs) {
		for (block_address b = 0; b < rhs->get_nr_blocks(); b++) {
			uint32_t count = rhs->get_count(b);
			if (count > 0)
				lhs->set_count(b, rhs->get_count(b));
		}
	}
}

//----------------------------------------------------------------

metadata::metadata(block_manager::ptr bm, open_type ot, unsigned metadata_version)
{
	switch (ot) {
	case CREATE:
		create_metadata(bm, metadata_version);
		break;

	default:
		throw runtime_error("unhandled open_type");
	}
}

metadata::metadata(block_manager::ptr bm)
{
	open_metadata(bm);
}

void
metadata::commit(bool clean_shutdown)
{
	commit_space_map();
	commit_mappings();
	commit_hints();
	commit_discard_bits();
	commit_superblock(clean_shutdown);
}

void
metadata::setup_hint_array(size_t width)
{
	if (width > 0)
		hints_ = hint_array::ptr(
			new hint_array(*tm_, width));
}

void
metadata::create_metadata(block_manager::ptr bm, unsigned metadata_version)
{
	tm_ = open_tm(bm);

	space_map::ptr core = tm_->get_sm();
	metadata_sm_ = create_metadata_sm(*tm_, tm_->get_bm()->get_nr_blocks());
	copy_space_maps(metadata_sm_, core);
	tm_->set_sm(metadata_sm_);

	mappings_ = mapping_array::ptr(new mapping_array(*tm_, mapping_array::ref_counter()));

	// We can't instantiate the hint array yet, since we don't know the
	// hint width.

	discard_bits_ = pd::bitset::ptr(new pd::bitset(*tm_));

	if (metadata_version >= 2)
		dirty_bits_ = pd::bitset::ptr(new pd::bitset(*tm_));
}

void
metadata::open_metadata(block_manager::ptr bm)
{
	tm_ = open_tm(bm);
	sb_ = read_superblock(tm_->get_bm());

	mappings_ = mapping_array::ptr(
		new mapping_array(*tm_,
				  mapping_array::ref_counter(),
				  sb_.mapping_root,
				  sb_.cache_blocks));

	if (sb_.hint_root)
		hints_ = hint_array::ptr(
			new hint_array(*tm_, sb_.policy_hint_size,
				       sb_.hint_root, sb_.cache_blocks));

	if (sb_.discard_root)
		discard_bits_ = pd::bitset::ptr(
			new pd::bitset(*tm_, sb_.discard_root, sb_.discard_nr_blocks));

	if (sb_.version >= 2)
		dirty_bits_ = pd::bitset::ptr(
			new pd::bitset(*tm_, *sb_.dirty_root, sb_.cache_blocks));
}

void
metadata::commit_space_map()
{
	metadata_sm_->commit();
	metadata_sm_->copy_root(&sb_.metadata_space_map_root, sizeof(sb_.metadata_space_map_root));
}

void
metadata::commit_mappings()
{
	sb_.mapping_root = mappings_->get_root();
	if (sb_.version >= 2) {
		dirty_bits_->flush();
		sb_.dirty_root = dirty_bits_->get_root();
	}
}

void
metadata::commit_hints()
{
	sb_.hint_root = hints_->get_root();
}

void
metadata::commit_discard_bits()
{
	sb_.discard_root = discard_bits_->get_root();
}

void
metadata::commit_superblock(bool clean_shutdown)
{
	if (clean_shutdown)
		sb_.flags.set_flag(superblock_flags::CLEAN_SHUTDOWN);

	write_superblock(tm_->get_bm(), sb_);

	sb_.flags.clear_flag(superblock_flags::CLEAN_SHUTDOWN);
}

//----------------------------------------------------------------
