/*
 * \brief  GPT utils
 * \author Josef Soentgen
 * \date   2018-05-01
 */

/*
 * Copyright (C) 2018 Genode Labs GmbH
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU Affero General Public License version 3.
 */

#ifndef _UTIL_H_
#define _UTIL_H_

/* Genode includes */
#include <base/fixed_stdint.h>
#include <base/entrypoint.h>
#include <block_session/connection.h>
#include <util/misc_math.h>
#include <util/string.h>


namespace Util {

	using namespace Genode;

	void init_random(Genode::Allocator &);
	void get_random(uint8_t *dest, size_t len);

	using Label = Genode::String<128>;

	using Size_string = Genode::String<64>;
	uint64_t convert(Size_string const &);

	using sector_t = Block::sector_t;

	sector_t align_start(size_t, size_t, sector_t);
	sector_t size_to_lba(size_t, uint64_t);

	struct Block_io;

	uint32_t crc32(void const * const, size_t);
	size_t extract_ascii(char *, size_t, uint8_t const *, size_t);
	size_t convert_ascii(uint8_t *, size_t, uint8_t const *, size_t);

	/*
	 * Wrapper to get suffixed uint64_t values
	 */
	class Number_of_bytes
	{
		uint64_t _n;

		public:

		/**
		 * Default constructor
		 */
		Number_of_bytes() : _n(0) { }

		/**
		 * Constructor, to be used implicitly via assignment operator
		 */
		Number_of_bytes(Genode::uint64_t n) : _n(n) { }

		/**
		 * Convert number of bytes to 'size_t' value
		 */
		operator Genode::uint64_t() const { return _n; }

		void print(Output &output) const
		{
			using Genode::print;

			enum { KB = 1024UL, MB = KB*1024UL, GB = MB*1024UL };

			if      (_n      == 0) print(output, 0);
			else if (_n % GB == 0) print(output, _n/GB, "G");
			else if (_n % MB == 0) print(output, _n/MB, "M");
			else if (_n % KB == 0) print(output, _n/KB, "K");
			else                   print(output, _n);
		}
	};

	inline size_t parse(Span const &s, Number_of_bytes &result)
	{
		unsigned long res = 0;

		/* convert numeric part of string */
		size_t i = parse_unsigned(s, res);

		/* handle suffixes */
		if (i > 0 && i < s.num_bytes)
			switch (s.start[i]) {
			case 'G': res *= 1024; [[fallthrough]];
			case 'M': res *= 1024; [[fallthrough]];
			case 'K': res *= 1024; i++;
			default: break;
			}

		result = res;
		return i;
	}
};


/*
 * Block_io wraps a Block::Connection for synchronous operations
 */
struct Util::Block_io
{
	struct Io_error : Genode::Exception { };

	using Packet_descriptor = Block::Packet_descriptor;

	Block::Connection<> &_block;
	Packet_descriptor    _p;

	/**
	 * Constructor
	 *
	 * \param block  reference to underlying Block::Connection
	 * \param block_size  logical block size of the Block::Connection
	 * \param lba         LBA to start access from
	 * \param count       number of LBAs to access
	 * \param write       set type of operation, write if true, read
	 *                    if false
	 *
	 * \throw Io_error
	 */
	Block_io(Block::Connection<> &block, size_t block_size, Entrypoint &ep,
	         sector_t lba, size_t count,
	         bool write = false, void const *data = nullptr, size_t len = 0)
	:
		_block(block),
		_p(_block.alloc_packet(block_size * count),
		   write ? Packet_descriptor::WRITE
		         : Packet_descriptor::READ, lba, count)
	{
		if (write) {
			if (data && len) {
				void *p = addr<void*>();
				Genode::memcpy(p, data, len);
			} else {
				Genode::error("invalid data for write");
				throw Io_error();
			}

		}

		_block.tx()->submit_packet(_p);

		/* block for job completion */
		while (!_block.tx()->ack_avail())
			ep.wait_and_dispatch_one_io_signal();

		_p = _block.tx()->get_acked_packet();
		if (!_p.succeeded()) {
			Genode::error("could not ", write ? "write" : "read",
			              " block-range [", _p.block_number(), ",",
			              _p.block_number() + count, ")");
			_block.tx()->release_packet(_p);
			throw Io_error();
		}
	}

	~Block_io() { _block.tx()->release_packet(_p); }

	template <typename T> T addr()
	{
		return reinterpret_cast<T>(_block.tx()->packet_content(_p));
	}
};

#endif /* _UTIL_H_ */
