/*
 * \brief  Utility for status reporting
 * \author Norman Feske
 * \date   2014-01-07
 */

/*
 * Copyright (C) 2014-2017 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 _INCLUDE__OS__REPORTER_H_
#define _INCLUDE__OS__REPORTER_H_

#include <base/attached_dataspace.h>
#include <base/node.h>
#include <report_session/connection.h>

namespace Genode {
	class Reporter;
	class Expanding_reporter;
}


class Genode::Reporter
{
	public:

		using Name = String<100>;

	private:

		Env &_env;

		Name const _node_type;
		Name const _label;

		size_t const _buffer_size;

		bool _enabled = false;

		struct Connection
		{
			Report::Connection report;
			Attached_dataspace ds;

			Connection(Env &env, char const *name, size_t buffer_size)
			: report(env, name, buffer_size), ds(env.rm(), report.dataspace())
			{ }
		};

		Constructible<Connection> _conn { };

		/**
		 * Return size of report buffer
		 */
		size_t _size() const { return _enabled ? _conn->ds.size() : 0; }

		/**
		 * Return pointer to report buffer
		 */
		char *_base() { return _enabled ? _conn->ds.local_addr<char>() : 0; }

	public:

		Reporter(Env &env, char const *node_type, char const *label = nullptr,
		         size_t buffer_size = 4096)
		:
			_env(env), _node_type(node_type), _label(label ? label : node_type),
			_buffer_size(buffer_size)
		{ }

		/**
		 * Enable or disable reporting
		 */
		void enabled(bool enabled)
		{
			if (enabled == _enabled) return;

			if (enabled)
				_conn.construct(_env, _label.string(), _buffer_size);
			else
				_conn.destruct();

			_enabled = enabled;
		}

		/**
		 * Return true if reporter is enabled
		 */
		bool enabled() const { return _enabled; }

		Name name() const { return _label; }

		/**
		 * Clear report buffer
		 */
		void clear() { bzero(_base(), _size()); }

		using Result = Attempt<Ok, Buffer_error>;

		/**
		 * Report data buffer
		 */
		Result generate(Const_byte_range_ptr const &ptr)
		{
			if (ptr.num_bytes > _size())
				return Buffer_error::EXCEEDED;

			if (_base()) {
				memcpy(_base(), ptr.start, ptr.num_bytes);
				_conn->report.submit(ptr.num_bytes);
			}
			return Ok();
		}

		/**
		 * Report textual representation of data generated by 'fn'
		 *
		 * The functor 'fn' is called with an 'Generator &' as argument.
		 */
		Result generate(auto const &fn)
		{
			Generator::Result const result =
				Generator::generate(Byte_range_ptr(_base(), _size()), _node_type, fn);

			return result.convert<Result>(
				[&] (size_t used) {
					if (enabled())
						_conn->report.submit(used);
					return Ok();
				},
				[&] (Buffer_error e) { return e; });
		}

		/**
		 * Report XML data generated by 'fn'
		 *
		 * The functor 'fn' is called with an 'Xml_generator &' as argument.
		 */
		Result generate_xml(auto const &fn)
		{
			Xml_generator::Result const result = 
				Xml_generator::generate({ _base(), _size() }, _node_type, fn);

			return result.convert<Result>(
				[&] (size_t used) {
					if (enabled())
						_conn->report.submit(used);
					return Ok();
				},
				[&] (Buffer_error e) { return e; });
		}
};


/**
 * Reporter that increases the report buffer capacity on demand
 *
 * This convenience wrapper of the 'Reporter' alleviates the need to handle
 * the exhaustion of the 'Xml_generator' buffer manually. In most cases, the
 * only reasonable way to handle such an exception is upgrading the report
 * buffer as done by this class. Furthermore, in contrast to the regular
 * 'Reporter', which needs to be 'enabled', the 'Expanding_reporter' is
 * implicitly enabled at construction time.
 */
class Genode::Expanding_reporter
{
	public:

		using Label     = Session_label;
		using Node_type = String<64>;

		struct Initial_buffer_size { size_t value; };

	private:

		Env &_env;

		Node_type const _type;
		Label     const _label;

		Constructible<Reporter> _reporter { };

		size_t _buffer_size;

		void _construct()
		{
			_reporter.construct(_env, _type.string(), _label.string(), _buffer_size);
			_reporter->enabled(true);
		}

		void _increase_report_buffer()
		{
			_buffer_size += 4096;
			_construct();
		}

	public:

		Expanding_reporter(Env &env, Node_type const &type, Label const &label,
		                   Initial_buffer_size const size = { 4096 })
		:
			_env(env), _type(type), _label(label), _buffer_size(size.value)
		{
			_construct();
		}

		Expanding_reporter(Env &env, Node_type const &type)
		:
			Expanding_reporter(env, type, type)
		{  }

		void generate(auto const &fn)
		{
			while (_reporter->generate(fn) == Buffer_error::EXCEEDED)
				_increase_report_buffer();
		}

		void generate_xml(auto const &fn)
		{
			while (_reporter->generate_xml(fn) == Buffer_error::EXCEEDED)
				_increase_report_buffer();
		}
};

#endif /* _INCLUDE__OS__REPORTER_H_ */
