/*
 * \brief  File system for trace buffer access
 * \author Sebastian Sumpf
 * \date   2019-06-13
 */

/*
 * Copyright (C) 2019 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.
 */

#include <base/attached_rom_dataspace.h>
#include <vfs/dir_file_system.h>
#include <vfs/single_file_system.h>

#include <os/vfs.h>
#include <util/xml_generator.h>

#include <trace_session/connection.h>

#include "directory_tree.h"
#include "trace_buffer.h"
#include "value_file_system.h"


namespace Vfs_trace {

	using namespace Vfs;
	using namespace Genode;
	using Name = String<32>;

	struct File_system;
	class  Local_factory;
	class  Subject;
	struct Subject_factory;
	class  Trace_buffer_file_system;
}


class Vfs_trace::Trace_buffer_file_system : public Single_file_system
{
	private:

		/**
		 * Trace_buffer wrapper
		 */
		struct Trace_entries
		{
			Vfs::Env                   &_env;
			Constructible<Trace_buffer> _buffer { };

			Trace_entries(Vfs::Env &env) : _env(env) { }

			void setup(Dataspace_capability ds)
			{
				_buffer.construct(*((Trace::Buffer *)_env.env().rm().attach(ds)));
			}

			void flush()
			{
				if (!_buffer.constructed()) return;

				_env.env().rm().detach(_buffer->address());
				_buffer.destruct();
			}

			template <typename FUNC>
			void for_each_new_entry(FUNC && functor, bool update = true) {
				if (!_buffer.constructed()) return;
				_buffer->for_each_new_entry(functor, update);
			}
		};

		enum State { OFF, TRACE, PAUSED } _state { OFF };

		Vfs::Env          &_env;
		Trace::Connection &_trace;
		Trace::Policy_id   _policy;
		Trace::Subject_id  _id;
		size_t             _buffer_size { 1024 * 1024 };
		size_t             _stat_size { 0 };
		Trace_entries      _entries { _env };


		typedef String<32> Config;

		static Config _config()
		{
			char buf[Config::capacity()] { };

			Xml_generator xml(buf, sizeof(buf), type_name(), [&] () { });

			return Config(Cstring(buf));
		}

		void _setup_and_trace()
		{
			_entries.flush();

			try {
				_trace.trace(_id, _policy, _buffer_size);
			} catch (...) {
				error("failed to start tracing");
				return;
			}

			_entries.setup(_trace.buffer(_id));
		}

	public:

		struct Vfs_handle : Single_vfs_handle
		{
			Trace_entries &_entries;

			Vfs_handle(Directory_service &ds,
			           File_io_service   &fs,
			           Genode::Allocator &alloc,
			           Trace_entries     &entries)
			: Single_vfs_handle(ds, fs, alloc, 0), _entries(entries)
			{ }

			Read_result read(char *dst, file_size count,
			                 file_size &out_count) override
			{
				out_count = 0;
				_entries.for_each_new_entry([&](Trace::Buffer::Entry entry) {
					file_size size = min(count - out_count, entry.length());
					memcpy(dst + out_count, entry.data(), size);
					out_count += size;

					if (out_count == count)
						return false;

					return true;
				});

				return READ_OK;
			}

			Write_result write(char const *, file_size,
			                   file_size &out_count) override
			{
				out_count = 0;
				return WRITE_ERR_INVALID;
			}

			bool read_ready() override { return true; }
		};

		Trace_buffer_file_system(Vfs::Env &env,
		                         Trace::Connection &trace,
		                         Trace::Policy_id policy,
		                         Trace::Subject_id id)
		: Single_file_system(Node_type::TRANSACTIONAL_FILE, type_name(),
		                     Node_rwx::rw(), Xml_node(_config().string())),
		  _env(env), _trace(trace), _policy(policy), _id(id)
		{ }

		static char const *type_name() { return "trace_buffer"; }
		char const *type() override { return type_name(); }


		/***************************
		 ** File-system interface **
		 ***************************/

		Open_result open(char const  *path, unsigned,
		                 Vfs::Vfs_handle **out_handle,
		                 Allocator   &alloc) override
		{
			if (!_single_file(path))
				return OPEN_ERR_UNACCESSIBLE;

			*out_handle = new (alloc) Vfs_handle(*this, *this, alloc, _entries);
			return OPEN_OK;
		}

		Stat_result stat(char const *path, Stat &out) override
		{
			Stat_result res = Single_file_system::stat(path, out);
			if (res != STAT_OK) return res;

			/* update file size */
			if (_state == TRACE)
				_entries.for_each_new_entry([&](Trace::Buffer::Entry entry) {
					_stat_size += entry.length(); return true; }, false);

			out.size = _stat_size;

			return res;
		}


		/***********************
		 ** FS event handlers **
		 ***********************/

		bool resize_buffer(size_t size)
		{
			if (size == 0) return false;

			_buffer_size = size;

			switch (_state) {
				case TRACE:
					_trace.pause(_id);
					_setup_and_trace();
					break;
				case PAUSED:
					_state = OFF;
					break;
				case OFF:
					break;
			}
			return true;
		}

		void trace(bool enable)
		{
			if (enable) {
				switch (_state) {
					case TRACE:  break;
					case OFF:    _setup_and_trace(); break;
					case PAUSED: _trace.resume(_id); break;

				}
				_state = TRACE;
			} else {
				switch (_state) {
					case OFF:    return;
					case PAUSED: return;
					case TRACE:  _trace.pause(_id); _state = PAUSED; return;
				}
			}
		}
};


struct Vfs_trace::Subject_factory : File_system_factory
{
	Vfs::Env                               &_env;
	Value_file_system<bool, 6>             _enabled_fs
	  { "enable", "false\n"};

	Value_file_system<Number_of_bytes, 16> _buffer_size_fs
	  { "buffer_size", "1M\n"};

	String<17>                             _buffer_string
	  { _buffer_size_fs.buffer() };

	Trace_buffer_file_system               _trace_fs;

	Subject_factory(Vfs::Env &env,
	                Trace::Connection &trace,
	                Trace::Policy_id policy,
	                Trace::Subject_id id)
	: _env(env), _trace_fs(env, trace, policy, id) { }

	Vfs::File_system *create(Vfs::Env &, Xml_node node) override
	{
		if (node.has_type(Value_file_system<unsigned>::type_name())) {
			if (_enabled_fs.matches(node))     return &_enabled_fs;
			if (_buffer_size_fs.matches(node)) return &_buffer_size_fs;
		}

		if (node.has_type(Trace_buffer_file_system::type_name()))
			return &_trace_fs;

		return nullptr;
	}
};


class Vfs_trace::Subject : private Subject_factory,
                           public  Vfs::Dir_file_system
{
	private:

		typedef String<200> Config;

		Watch_handler<Subject> _enable_handler {
		  _enabled_fs, "/enable",
		  Subject_factory::_env.alloc(),
		  *this, &Subject::_enable_subject };

		Watch_handler<Subject> _buffer_size_handler {
		  _buffer_size_fs, "/buffer_size",
		  Subject_factory::_env.alloc(),
		  *this, &Subject::_buffer_size };


		static Config _config(Xml_node node)
		{
			char buf[Config::capacity()] { };

			Xml_generator xml(buf, sizeof(buf), "dir", [&] () {
				xml.attribute("name", node.attribute_value("name", Vfs_trace::Name()));
				xml.node("value", [&] () { xml.attribute("name", "enable"); });
				xml.node("value", [&] () { xml.attribute("name", "buffer_size"); });
				xml.node(Trace_buffer_file_system::type_name(), [&] () {});
			});

			return Config(Cstring(buf));
		}


		/********************
		 ** Watch handlers **
		 ********************/

		void _enable_subject()
		{
			_enabled_fs.value(_enabled_fs.value() ? "true\n" : "false\n");
			_trace_fs.trace(_enabled_fs.value());
		}

		void _buffer_size()
		{
			Number_of_bytes size = _buffer_size_fs.value();

			if (_trace_fs.resize_buffer(size) == false) {
				/* restore old value */
				_buffer_size_fs.value(_buffer_string);
				return;
			}

			_buffer_string = _buffer_size_fs.buffer();
		}

	public:

		Subject(Vfs::Env &env, Trace::Connection &trace,
		        Trace::Policy_id policy, Xml_node node)
		: Subject_factory(env, trace, policy, node.attribute_value("id", 0u)),
		  Dir_file_system(env, Xml_node(_config(node).string()), *this)
		{ }


		static char const *type_name() { return "trace_node"; }
		char const *type() override { return type_name(); }
};


struct Vfs_trace::Local_factory : File_system_factory
{
	Vfs::Env          &_env;

	Trace::Connection  _trace;
	enum { MAX_SUBJECTS = 128 };
	Trace::Subject_id  _subjects[MAX_SUBJECTS];
	unsigned           _subject_count { 0 };
	Trace::Policy_id   _policy_id { 0 };

	Directory_tree     _tree { _env.alloc() };

	void _install_null_policy()
	{
		using namespace Genode;
		Constructible<Attached_rom_dataspace> null_policy;

		try {
			null_policy.construct(_env.env(), "null");
			_policy_id = _trace.alloc_policy(null_policy->size());
		}
		catch (Out_of_caps) { throw; }
		catch (Out_of_ram)  { throw; }
		catch (...) {
				error("failed to attach 'null' trace policy."
				      "Please make sure it is provided as a ROM module.");
				throw;
		}

		/* copy policy into trace session */
		void *dst = _env.env().rm().attach(_trace.policy(_policy_id));
		memcpy(dst, null_policy->local_addr<void*>(), null_policy->size());
		_env.env().rm().detach(dst);
	}

	size_t _config_session_ram(Xml_node config)
	{
		try {
			Genode::Number_of_bytes ram;
			config.attribute("ram").value(&ram);
			return ram;
		} catch (...) {
			Genode::error("mandatory 'ram' attribute missing");
			throw Genode::Exception();
		}
	}

	Local_factory(Vfs::Env &env, Xml_node config)
	: _env(env), _trace(env.env(), _config_session_ram(config), 512*1024, 0)
	{
		bool success = false;
		while (!success) {
			try {
				_subject_count = _trace.subjects(_subjects, MAX_SUBJECTS);
				success = true;
			} catch(Genode::Out_of_ram) {
				_trace.upgrade_ram(4096);
				success = false;
			}
		}

		for (unsigned i = 0; i < _subject_count; i++) {
			_tree.insert(_trace.subject_info(_subjects[i]), _subjects[i]);
		}

		_install_null_policy();
	}

	Vfs::File_system *create(Vfs::Env&, Xml_node node) override
	{
		if (node.has_type(Subject::type_name()))
			return new (_env.alloc()) Subject(_env, _trace, _policy_id, node);

		return nullptr;
	}
};


class Vfs_trace::File_system : private Local_factory,
                               public  Vfs::Dir_file_system
{
	private:

		typedef String<512*1024> Config;

		static char const *_config(Vfs::Env &vfs_env, Directory_tree &tree)
		{
			char *buf = (char *)vfs_env.alloc().alloc(Config::capacity());
			Xml_generator xml(buf, Config::capacity(), "node", [&] () {
				tree.xml(xml);
			});

			return buf;
		}

	public:

		File_system(Vfs::Env &vfs_env, Genode::Xml_node node)
		: Local_factory(vfs_env, node),
			Vfs::Dir_file_system(vfs_env, Xml_node(_config(vfs_env, _tree)), *this)
		{ }

		char const *type() override { return "trace"; }
};


/**************************
 ** VFS plugin interface **
 **************************/

extern "C" Vfs::File_system_factory *vfs_file_system_factory(void)
{
	struct Factory : Vfs::File_system_factory
	{
		Vfs::File_system *create(Vfs::Env &vfs_env,
		                         Genode::Xml_node node) override
		{
			try { return new (vfs_env.alloc())
				Vfs_trace::File_system(vfs_env, node); }
			catch (...) { Genode::error("could not create 'trace_fs' "); }
			return nullptr;
		}
	};

	static Factory factory;
	return &factory;
}
