/*
 * \brief  Sculpt storage management
 * \author Norman Feske
 * \date   2018-04-30
 */

/*
 * 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.
 */

/* local includes */
#include <storage.h>

using namespace Sculpt;


Progress Storage::update(Node const &config, Drivers::Storage_devices const &devices)
{
	bool progress = false;

	progress |= _storage_devices.update_ahci(_env, _alloc, devices.ahci.report).progressed;
	progress |= _storage_devices.update_nvme(_env, _alloc, devices.nvme.report).progressed;
	progress |= _storage_devices.update_mmc (_env, _alloc, devices.mmc .report).progressed;
	progress |= _storage_devices.update_usb (_env, _alloc, devices.usb .report).progressed;

	_storage_devices.for_each([&] (Storage_device &dev) {
		Storage_device::State const orig_state = dev.state;
		dev.process_partitions();
		progress |= (dev.state != orig_state);
	});

	_storage_devices.usb_storage_devices.for_each([&] (Usb_storage_device &dev) {
		dev.process_report(); });

	Storage_target const orig_selected_target   = _selected_target;
	Storage_target const orig_configured_target = _configured_target;

	config.with_sub_node("target",
		[&] (Node const &target) {
			Storage_target const configured_target = Storage_target::from_node(target);
			if (configured_target != _configured_target) {
				_configured_target = configured_target;
				_malconfiguration = false; } },

		[&] { _configured_target = { }; });

	if (orig_configured_target != _configured_target)
		_selected_target = { };

	if (!_selected_target.valid()) {
		if (devices.all_enumerated()) {
			if (_configured_target.valid())
				_selected_target = _malconfiguration ? Storage_target { } : _configured_target;
			else
				_selected_target = _discovery_state.detect_default_target(_storage_devices);
		}
	}


	/*
	 * Detect the removal of a USB stick that is currently in "use". Reset
	 * the '_selected_target' to enable the selection of another storage
	 * target to use.
	 */
	else if (_selected_target.valid()) {

		bool selected_target_exists = false;

		if (_selected_target.ram_fs())
			selected_target_exists = true;

		_storage_devices.for_each([&] (Storage_device const &device) {
			device.for_each_partition([&] (Partition const &partition) {
				if (device.driver    == _selected_target.driver
				 && partition.number == _selected_target.partition)
					selected_target_exists = true; }); });

		if (!selected_target_exists) {
			if (_configured_target.valid()) {
				warning("configured storage target does not exist");
				_malconfiguration = true;
			} else {
				warning("selected storage target unexpectedly vanished");
			}

			_selected_target   = { };
		}
	}

	progress |= (orig_selected_target != _selected_target);

	return { progress };
}


void Storage::gen_child_nodes(Generator &g) const
{
	auto contains_used_fs = [&] (Storage_device const &device)
	{
		if (!_selected_target.valid())
			return false;

		return (device.port   == _selected_target.port)
		    && (device.driver == _selected_target.driver);
	};

	_storage_devices.usb_storage_devices.for_each([&] (Usb_storage_device const &device) {

		if (device.usb_block_needed() || contains_used_fs(device))
			g.node("child", [&] {
				device.gen_usb_block_child_content(g); });
	});

	_storage_devices.for_each([&] (Storage_device const &device) {

		bool const device_contains_used_fs_in_partition =
			contains_used_fs(device) && !device.whole_device;

		bool const part_block_needed = device.part_block_needed_for_discovery()
		                            || device.part_block_needed_for_access()
		                            || device_contains_used_fs_in_partition;
		if (part_block_needed)
			g.node("child", [&] {
				device.gen_part_block_child_content(g); });

		device.for_each_partition([&] (Partition const &partition) {

			Storage_target const target { .driver    = device.driver,
			                              .port      = device.port,
			                              .partition = partition.number };

			if (partition.check_in_progress) {
				g.node("child", [&] {
					gen_fsck_ext2_child_content(g, target); }); }

			if (partition.format_in_progress) {
				g.node("child", [&] {
					gen_mkfs_ext2_child_content(g, target); }); }

			if (partition.fs_resize_in_progress) {
				g.node("child", [&] {
					gen_resize2fs_child_content(g, target); }); }

			if (partition.file_system.type != File_system::UNKNOWN) {
				if (partition.file_system.inspected || target == _selected_target)
					g.node("child", [&] {
						gen_fs_child_content(g, target, partition.file_system.type); });

				/*
				 * Create alias so that the default file system can be referred
				 * to as "default_fs_rw" without the need to know the name of the
				 * underlying storage target.
				 */
				if (target == _selected_target)
					gen_named_node(g, "alias", "default_fs_rw", [&] {
						g.attribute("child", target.fs()); });
			}

		}); /* for each partition */

		/* relabel partitions if needed */
		if (device.relabel_in_progress())
			g.node("child", [&] {
				gen_gpt_relabel_child_content(g, device); });

		/* expand partitions if needed */
		if (device.expand_in_progress())
			g.node("child", [&] {
				gen_gpt_expand_child_content(g, device); });

	}); /* for each device */

	if (_selected_target.ram_fs())
		gen_named_node(g, "alias", "default_fs_rw", [&] {
			g.attribute("child", "ram_fs"); });
}

