#ifndef CACHE_SUPERBLOCK_H
#define CACHE_SUPERBLOCK_H

#include "base/endian_utils.h"
#include "persistent-data/data-structures/btree.h"

#include <boost/optional.hpp>
#include <set>

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

namespace caching {
	typedef unsigned char __u8;

	unsigned const MIN_METADATA_VERSION = 1;
	unsigned const MAX_METADATA_VERSION = 2;
	unsigned const SPACE_MAP_ROOT_SIZE = 128;
	unsigned const CACHE_POLICY_NAME_SIZE = 16;
	unsigned const CACHE_POLICY_VERSION_SIZE = 3;
	block_address const SUPERBLOCK_LOCATION = 0;

	class superblock_flags {
	public:
		enum flag {
			CLEAN_SHUTDOWN,
			NEEDS_CHECK
		};

		enum flag_bits {
			CLEAN_SHUTDOWN_BIT = 0,
			NEEDS_CHECK_BIT = 1,
		};

		superblock_flags();
		superblock_flags(uint32_t bits);

		void set_flag(flag f);
		void clear_flag(flag f);
		bool get_flag(flag f) const;
		uint32_t encode() const;
		uint32_t get_unhandled_flags() const;

	private:
		uint32_t unhandled_flags_;
		std::set<flag> flags_;
	};

	struct superblock {
		superblock();

		uint32_t csum;
		superblock_flags flags;
		uint64_t blocknr;

		__u8 uuid[16];
		uint64_t magic;
		uint32_t version;

		__u8 policy_name[CACHE_POLICY_NAME_SIZE];
		uint32_t policy_version[CACHE_POLICY_VERSION_SIZE];
		uint32_t policy_hint_size;

		__u8 metadata_space_map_root[SPACE_MAP_ROOT_SIZE];

		uint64_t mapping_root;
		uint64_t hint_root;

		uint64_t discard_root;
		uint64_t discard_block_size;
		uint64_t discard_nr_blocks;

		uint32_t data_block_size; /* in 512-byte sectors */
		uint32_t metadata_block_size; /* in 512-byte sectors */
		uint32_t cache_blocks;

		uint32_t compat_flags;
		uint32_t compat_ro_flags;
		uint32_t incompat_flags;

		uint32_t read_hits;
		uint32_t read_misses;
		uint32_t write_hits;
		uint32_t write_misses;

		boost::optional<uint64_t> dirty_root;
	};

	enum incompat_bits {
		VARIABLE_HINT_SIZE_BIT = 0
	};

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

	namespace superblock_damage {

		class damage_visitor;

		class damage {
		public:
			damage(std::string const &desc)
				: desc_(desc) {
			}

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

			std::string const &get_desc() const {
				return desc_;
			}

		private:
			std::string desc_;
		};

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

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

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

			void visit(damage const &d);

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

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

	bcache::validator::ptr superblock_validator();

	superblock read_superblock(persistent_data::block_manager::ptr bm,
				   persistent_data::block_address location = SUPERBLOCK_LOCATION);

	void write_superblock(persistent_data::block_manager::ptr bm,
			      superblock const &sb,
			      persistent_data::block_address location = SUPERBLOCK_LOCATION);

	void check_superblock(superblock const &sb,
			      persistent_data::block_address nr_metadata_blocks,
			      superblock_damage::damage_visitor &visitor);

	void check_superblock(persistent_data::block_manager::ptr bm,
			      persistent_data::block_address nr_metadata_blocks,
			      superblock_damage::damage_visitor &visitor);
}

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

#endif
