#ifndef THIN_SUPERBLOCK_H
#define THIN_SUPERBLOCK_H

#include "base/endian_utils.h"

#include "persistent-data/block.h"
#include "persistent-data/data-structures/ref_counter.h"

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

namespace thin_provisioning {
	namespace superblock_detail {
		using namespace base;
		using namespace persistent_data;

		unsigned const SPACE_MAP_ROOT_SIZE = 128;

		typedef unsigned char __u8;

		struct superblock_disk {
			le32 csum_;
			le32 flags_;
			le64 blocknr_;

			__u8 uuid_[16];
			le64 magic_;
			le32 version_;
			le32 time_;

			le64 trans_id_;
			/* root for userspace's transaction (for migration and friends) */
			le64 metadata_snap_;

			__u8 data_space_map_root_[SPACE_MAP_ROOT_SIZE];
			__u8 metadata_space_map_root_[SPACE_MAP_ROOT_SIZE];

			/* 2 level btree mapping (dev_id, (dev block, time)) -> data block */
			le64 data_mapping_root_;

			/* device detail root mapping dev_id -> device_details */
			le64 device_details_root_;

			le32 data_block_size_; /* in 512-byte sectors */

			le32 metadata_block_size_; /* in 512-byte sectors */
			le64 metadata_nr_blocks_;

			le32 compat_flags_;
			le32 compat_ro_flags_;
			le32 incompat_flags_;
		} __attribute__ ((packed));

		struct superblock {
			uint32_t csum_;
			uint32_t flags_;
			uint64_t blocknr_;

			unsigned char uuid_[16];
			uint64_t magic_;
			uint32_t version_;
			uint32_t time_;

			uint64_t trans_id_;
			/* root for userspace's transaction (for migration and friends) */
			uint64_t metadata_snap_;

			unsigned char data_space_map_root_[SPACE_MAP_ROOT_SIZE];
			unsigned char metadata_space_map_root_[SPACE_MAP_ROOT_SIZE];

			/* 2 level btree mapping (dev_id, (dev block, time)) -> data block */
			uint64_t data_mapping_root_;

			/* device detail root mapping dev_id -> device_details */
			uint64_t device_details_root_;

			uint32_t data_block_size_; /* in 512-byte sectors */

			uint32_t metadata_block_size_; /* in 512-byte sectors */
			uint64_t metadata_nr_blocks_;

			uint32_t compat_flags_;
			uint32_t compat_ro_flags_;
			uint32_t incompat_flags_;

			bool get_needs_check_flag() const;
			void set_needs_check_flag(bool val = true);
		};

		struct superblock_traits {
			typedef superblock_disk disk_type;
			typedef superblock value_type;
			typedef no_op_ref_counter<superblock> ref_counter;

			static void unpack(superblock_disk const &disk, superblock &core);
			static void pack(superblock const &core, superblock_disk &disk);
		};

		block_address const SUPERBLOCK_LOCATION = 0;
		uint32_t const SUPERBLOCK_MAGIC = 27022010;
		uint32_t const METADATA_VERSION = 2;

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

		class damage_visitor;

		struct damage {
			virtual ~damage() {}
			virtual void visit(damage_visitor &v) const = 0;
		};

		struct superblock_corruption : public damage {
			superblock_corruption(std::string const &desc);
			void visit(damage_visitor &v) const;

			std::string desc_;
		};

		class damage_visitor {
		public:
			virtual ~damage_visitor() {}

			void visit(damage const &d);

			virtual void visit(superblock_corruption const &d) = 0;
		};
	}

	bcache::validator::ptr superblock_validator();

	// FIXME: should we put init_superblock in here too?

	// FIXME: make the bm const, and pass by reference rather than ptr
	superblock_detail::superblock read_superblock(persistent_data::block_manager const &bm);
	superblock_detail::superblock read_superblock(persistent_data::block_manager::ptr bm);
	superblock_detail::superblock read_superblock(persistent_data::block_manager::ptr bm,
						      persistent_data::block_address location);

	void write_superblock(persistent_data::block_manager::ptr bm,
			      superblock_detail::superblock const &sb);

	void check_superblock(persistent_data::block_manager::ptr bm,
			      superblock_detail::damage_visitor &visitor,
                              persistent_data::block_address sb_location = superblock_detail::SUPERBLOCK_LOCATION);
}

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

#endif
