// This file is part of the thin-provisioning-tools source.
//
// thin-provisioning-tools is free software: you can redistribute it
// and/or modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// thin-provisioning-tools 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with thin-provisioning-tools.  If not, see
// <http://www.gnu.org/licenses/>.

#include <boost/lexical_cast.hpp>
#include <boost/optional.hpp>
#include <getopt.h>
#include <vector>
#include <fstream>

#include "persistent-data/data-structures/btree.h"
#include "persistent-data/data-structures/simple_traits.h"
#include "persistent-data/file_utils.h"
#include "persistent-data/space-maps/core.h"
#include "persistent-data/space-maps/disk_structures.h"
#include "thin-provisioning/metadata.h"
#include "thin-provisioning/superblock.h"
#include "thin-provisioning/commands.h"
#include "version.h"

using namespace thin_provisioning;

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

namespace {
	// extracted from btree_damage_visitor.h
	template <typename node>
	bool check_block_nr(node const &n) {
		if (n.get_location() != n.get_block_nr()) {
			return false;
		}
		return true;
	}

	// extracted from btree_damage_visitor.h
	template <typename node>
	bool check_max_entries(node const &n) {
		size_t elt_size = sizeof(uint64_t) + n.get_value_size();
		if (elt_size * n.get_max_entries() + sizeof(node_header) > MD_BLOCK_SIZE) {
			return false;
		}

		if (n.get_max_entries() % 3) {
			return false;
		}

		return true;
	}

	// extracted from btree_damage_visitor.h
	template <typename node>
	bool check_nr_entries(node const &n, bool is_root) {
		if (n.get_nr_entries() > n.get_max_entries()) {
			return false;
		}

		block_address min = n.get_max_entries() / 3;
		if (!is_root && (n.get_nr_entries() < min)) {
			return false;
		}

		return true;
	}

	// extracted from btree_damage_visitor.h
	template <typename node>
	bool check_ordered_keys(node const &n) {
		unsigned nr_entries = n.get_nr_entries();

		if (nr_entries == 0)
			return true; // can only happen if a root node

		uint64_t last_key = n.key_at(0);

		for (unsigned i = 1; i < nr_entries; i++) {
			uint64_t k = n.key_at(i);
			if (k <= last_key) {
				return false;
			}
			last_key = k;
		}

		return true;
	}
}

namespace {
	// FIXME: deprecated conversion from string constant to ‘char*’
	char const* metadata_block_type_name[] = {
		"unknown",
		"zero",
		"superblock",
		"btree_internal",
		"btree_leaf",
		"btree_unknown",
		"index_block",
		"bitmap_block"
	};

	enum metadata_block_type {
		UNKNOWN = 0,
		ZERO,
		SUPERBLOCK,
		BTREE_INTERNAL,
		BTREE_LEAF,
		BTREE_UNKNOWN,
		INDEX_BLOCK,
		BITMAP_BLOCK
	};

	struct block_range {
		block_range()
			: begin_(0), end_(0),
			  type_(UNKNOWN), ref_count_(-1),
			  value_size_(0), is_valid_(false)
		{
		}

		block_range(block_range const &rhs)
			: begin_(rhs.begin_), end_(rhs.end_),
			  blocknr_begin_(rhs.blocknr_begin_),
			  type_(rhs.type_), ref_count_(rhs.ref_count_),
			  value_size_(rhs.value_size_), is_valid_(rhs.is_valid_)
		{
		}

		uint64_t size() const {
			return (end_ > begin_) ? (end_ - begin_) : 0;
		}

		// returns true if r is left or right-adjacent
		bool is_adjacent_to(block_range const &r) const {
			block_range const &lhs = begin_ < r.begin_ ? *this : r;
			block_range const &rhs = begin_ < r.begin_ ? r : *this;

			if (size() && r.size() &&
			    rhs.begin_ == lhs.end_ &&
			    ((!blocknr_begin_ && !r.blocknr_begin_) ||
			     (blocknr_begin_ && r.blocknr_begin_ &&
			      *rhs.blocknr_begin_ >= *lhs.blocknr_begin_ &&
			      (*rhs.blocknr_begin_ - *lhs.blocknr_begin_ == rhs.begin_ - lhs.begin_))) &&
			    type_ == r.type_ &&
			    ref_count_ == r.ref_count_ &&
			    value_size_ == r.value_size_ &&
			    is_valid_ == r.is_valid_)
				return true;

			return false;
		}

		bool concat(block_range const &r) {
			if (!is_adjacent_to(r))
				return false;
			begin_ = std::min(begin_, r.begin_);
			end_ = std::max(end_, r.end_);
			return true;
		}

		uint64_t begin_;
		uint64_t end_; // one-pass-the-end
		boost::optional<uint64_t> blocknr_begin_;
		metadata_block_type type_;
		int64_t ref_count_; // ref_count in metadata space map
		size_t value_size_; // btree node only
		bool is_valid_;
	};

	void output_block_range(block_range const &r, std::ostream &out) {
		if (!r.size())
			return;

		if (r.end_ - r.begin_ > 1) {
			out << "<range_block type=\"" << metadata_block_type_name[r.type_]
			    << "\" location_begin=\"" << r.begin_;
			if (r.blocknr_begin_)
			    out << "\" blocknr_begin=\"" << *r.blocknr_begin_;
			out << "\" length=\"" << r.end_ - r.begin_
			    << "\" ref_count=\"" << r.ref_count_
			    << "\" is_valid=\"" << r.is_valid_;
		} else {
			out << "<single_block type=\"" << metadata_block_type_name[r.type_]
			    << "\" location=\"" << r.begin_;
			if (r.blocknr_begin_)
			    out << "\" blocknr=\"" << *r.blocknr_begin_;
			out << "\" ref_count=\"" << r.ref_count_
			    << "\" is_valid=\"" << r.is_valid_;
		}

		if (r.type_ == BTREE_INTERNAL || r.type_ == BTREE_LEAF || r.type_ == BTREE_UNKNOWN) {
			out << "\" value_size=\"" << r.value_size_ << "\"/>" << endl;
		} else
			out << "\"/>" << endl;
	}

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

	struct flags {
		flags() {
		}

		boost::optional<block_address> scan_begin_;
		boost::optional<block_address> scan_end_;
	};

	int scan_metadata_(string const &input,
			   std::ostream &out,
			   flags const &f) {
		using namespace persistent_data;
		using namespace thin_provisioning;
		using namespace sm_disk_detail;

		block_manager<>::ptr bm;
		bm = open_bm(input, block_manager<>::READ_ONLY);

		block_address scan_begin = f.scan_begin_ ? *f.scan_begin_ : 0;
		block_address scan_end = f.scan_end_ ? *f.scan_end_ : bm->get_nr_blocks();

		const std::vector<char> zeros(MD_BLOCK_SIZE, 0);

		// try to open metadata space-map (it's okay to fail)
		// note: transaction_manager and space_map must be in the same scope
		transaction_manager::ptr tm;
		checked_space_map::ptr metadata_sm;
		try {
			superblock_detail::superblock sb = read_superblock(bm);
			tm = open_tm(bm, superblock_detail::SUPERBLOCK_LOCATION);
			metadata_sm = open_metadata_sm(*tm, &sb.metadata_space_map_root_);
			tm->set_sm(metadata_sm);
		} catch (std::exception &e) {
			cerr << e.what() << endl;
		}

		block_range curr_range;
		block_range run_range;

		bcache::validator::ptr sv = superblock_validator();
		bcache::validator::ptr nv = create_btree_node_validator();
		bcache::validator::ptr iv = index_validator();
		bcache::validator::ptr bv = bitmap_validator();

		for (block_address b = scan_begin; b < scan_end; ++b) {
			block_manager<>::read_ref rr = bm->read_lock(b);

			curr_range.begin_ = b;
			curr_range.end_ = b + 1;
			curr_range.blocknr_begin_ = boost::none;
			curr_range.type_ = UNKNOWN;
			curr_range.is_valid_ = false;

			if (!memcmp(rr.data(), zeros.data(), MD_BLOCK_SIZE))
				curr_range.type_ = ZERO;

			if (curr_range.type_ == UNKNOWN && sv->check_raw(rr.data())) {
				curr_range.type_ = SUPERBLOCK;
				curr_range.is_valid_ = true;
			}

			if (curr_range.type_ == UNKNOWN && nv->check_raw(rr.data())) {
				// note: check_raw() doesn't check node_header::blocknr_
				node_ref<uint64_traits> n = btree_detail::to_node<uint64_traits>(rr);
				uint32_t flags = to_cpu<uint32_t>(n.raw()->header.flags);
				if ((flags & INTERNAL_NODE) && !(flags & LEAF_NODE))
					curr_range.type_ = BTREE_INTERNAL;
				else if (flags & LEAF_NODE)
					curr_range.type_ = BTREE_LEAF;
				else
					curr_range.type_ = BTREE_UNKNOWN;

				if (curr_range.type_ != BTREE_UNKNOWN &&
				    check_block_nr(n) &&
				    check_max_entries(n) &&
				    check_nr_entries(n, true) &&
				    check_ordered_keys(n))
					curr_range.is_valid_ = true;
				else
					curr_range.is_valid_ = false;

				curr_range.blocknr_begin_ = n.get_block_nr();
				curr_range.value_size_ = n.get_value_size();
			}

			if (curr_range.type_ == UNKNOWN && bv->check_raw(rr.data())) {
				curr_range.type_ = BITMAP_BLOCK;
				bitmap_header const *data = reinterpret_cast<bitmap_header const *>(rr.data());
				curr_range.blocknr_begin_ = to_cpu<uint64_t>(data->blocknr);
				curr_range.is_valid_ = (to_cpu<uint64_t>(data->blocknr) == b) ? true : false;
			}

			if (curr_range.type_ == UNKNOWN && iv->check_raw(rr.data())) {
				curr_range.type_ = INDEX_BLOCK;
				metadata_index const *mi = reinterpret_cast<metadata_index const *>(rr.data());
				curr_range.blocknr_begin_ = to_cpu<uint64_t>(mi->blocknr_);
				curr_range.is_valid_ = (to_cpu<uint64_t>(mi->blocknr_) == b) ? true : false;
			}

			try {
				curr_range.ref_count_ = metadata_sm ?
							static_cast<int64_t>(metadata_sm->get_count(b)) : -1;
			} catch (std::exception &e) {
				curr_range.ref_count_ = -1;
			}

			// store the current block
			if (!run_range.concat(curr_range)) {
				output_block_range(run_range, out);
				run_range = curr_range;
			}
		}

		// output the last run
		output_block_range(run_range, out);

		return 0;
	}

	int scan_metadata(string const &input,
			  boost::optional<string> output,
			  flags const &f) {
		try {
			if (output) {
				std::ofstream out(output->c_str());
				scan_metadata_(input, out, f);
			} else
				scan_metadata_(input, cout, f);
		} catch (std::exception &e) {
			cerr << e.what() << endl;
			return 1;
		}
		return 0;
	}
}

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

thin_scan_cmd::thin_scan_cmd()
	: command("thin_scan")
{
}

void
thin_scan_cmd::usage(std::ostream &out) const {
	out << "Usage: " << get_name() << " [options] {device|file}" << endl
	    << "Options:" << endl
	    << "  {-h|--help}" << endl
	    << "  {-o|--output} <xml file>" << endl
	    << "  {--begin} <block#>" << endl
	    << "  {--end} <block#>" << endl
	    << "  {-V|--version}" << endl;
}

int
thin_scan_cmd::run(int argc, char **argv)
{
	const char shortopts[] = "ho:V";
	const struct option longopts[] = {
		{ "help", no_argument, NULL, 'h'},
		{ "output", required_argument, NULL, 'o'},
		{ "version", no_argument, NULL, 'V'},
		{ "begin", required_argument, NULL, 1},
		{ "end", required_argument, NULL, 2},
		{ NULL, no_argument, NULL, 0 }
	};
	boost::optional<string> output;
	flags f;

	int c;
	while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) {
		switch(c) {
		case 'h':
			usage(cout);
			return 0;

		case 'o':
			output = optarg;
			break;

		case 'V':
			cout << THIN_PROVISIONING_TOOLS_VERSION << endl;
			return 0;

		case 1:
			try {
				f.scan_begin_ = boost::lexical_cast<uint64_t>(optarg);
			} catch (std::exception &e) {
				cerr << e.what() << endl;
				return 1;
			}
			break;

		case 2:
			try {
				f.scan_end_ = boost::lexical_cast<uint64_t>(optarg);
			} catch (std::exception &e) {
				cerr << e.what() << endl;
				return 1;
			}
			break;

		default:
			usage(cerr);
			return 1;
		}
	}

	if (argc == optind) {
		cerr << "No input file provided." << endl;
		usage(cerr);
		return 1;
	}

	if (f.scan_begin_ && f.scan_end_ && (*f.scan_end_ <= *f.scan_begin_)) {
		cerr << "badly formed region (end <= begin)" << endl;
		return 1;
	}

	return scan_metadata(argv[optind], output, f);
}

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