/**
 * File:
 *   include/restore/ui.ycp
 *
 * Package:
 *   Restore module
 *
 * Summary:
 *   User interface functions.
 *
 * Authors:
 *   Ladislav Slezak <lslezak@suse.cz>
 *
 * $Id: ui.ycp,v 1.59 2004/11/05 08:29:48 locilka Exp $
 *
 * All user interface functions.
 *
 */

{

textdomain "restore";

import "Wizard";
import "Wizard_hw";
import "Progress";
import "Restore";
import "Mode";
import "URL";

import "Popup";
import "Label";
import "Package";
import "Sequencer";

include "restore/helps.ycp";
include "restore/summary_dialog.ycp";
import "Label";
import "NetworkPopup";

string restorepackagename = nil;

list archivecontentscache = nil;

list<string> restoredfiles = [];
list<string> failedfiles = [];
integer restoredpackages = 0;
boolean bloaderstatus = nil;
boolean susestatus = nil;

map<string, any> packagestoinstall = $[];
map<string, any> packagestouninstall = $[];
// mounted directory
string mountdir = "";

// map with detected removable devices
map<string,map> removabledevices = nil;

// last user input, used for dialog skipping
symbol lastret = nil;

/**
 * Try to detect all removable devices present in the system
 * @return map Removable devices info
 */

define map<string, map> DetectRemovable() ``{
    map<string, map> ret = $[];

    // detect floppy devices
    list<map> devices = (Mode::test () == false) ? (list<map>) SCR::Read(.probe.floppy) : [$["bus":"Floppy", "class_id":262, "dev_name":"/dev/fd0", "notready":true, "old_unique_key":"xjDN.oZ89vuho4Y3", "resource":$["size":[$["unit":"cinch", "x":350, "y":0], $["unit":"sectors", "x":2880, "y":512]]], "sub_class_id":3, "unique_key":"sPPV.oZ89vuho4Y3"]];
    integer num = 0;

    foreach(map dev, devices, ``{
	    string dev_name = dev["dev_name"]:"";
	    string device = dev["device"]:"";

	    if (device == "")
	    {
		if (dev["bus"]:"" == "Floppy")
		{
		    // floppy disk drive - combo box item
		    device = _("Floppy");
		}
	    }

	    if (dev_name != "")
	    {
		ret = add(ret, dev_name, $[ "device" : device, "type" : "fd" + num + "://" ]);
	    }

	    num = num + 1;
	}
    );

    // detect cdrom devices
    devices = (Mode::test () == false) ? (list<map>) SCR::Read(.probe.cdrom) : [$["bus":"IDE", "cdtype":"cdrom", "class_id":262, "dev_name":"/dev/hdc", "device":"CD-540E", "driver":"ide-cdrom", "notready":true, "old_unique_key":"3JYE.3LYJ0fijWD1", "resource":$["size":[$["unit":"sectors", "x":0, "y":512]]], "rev":"1.0A", "sub_class_id":2, "unique_key":"hY5p.ZxKxy3YdB66"]];
    num = 0;

    foreach(map dev, devices, ``{
	    string dev_name = dev["dev_name"]:"";
	    string device = dev["device"]:"";

	    if (dev_name != "")
	    {
		ret = add(ret, dev_name, $[ "device" : device, "type" : "cd" + num + "://" ]);
	    }

	    num = num + 1;
	}
    );

    return ret;
}

/**
 * Propose next file name of volume from file name
 * @param volume Previuos volume name
 * @return string Proposed next volume name
 */

define string ProposeNextVolume(string volume) ``{
    // increase number in file name
    integer pos = findlastof(volume, "/");
    string volumedir = (pos != nil) ? substring(volume, 0, pos + 1) : "";
    string volumefile = (pos != nil) ? substring(volume, pos + 1) : volume;

    // ignore leading zeroes, 0xxx means octal number in tointeger() builtin
    integer volumenum = tointeger(regexpsub(volumefile, "0*([0-9]+)([^0-9]*)", "\\1"));

    string volumebase = regexpsub(volumefile, "([0-9]+)([^0-9]*)", "\\2");
    string newvolume = "";

    if (volumenum != nil)
    {
	volumenum = volumenum + 1;
	newvolume = sformat("%1", volumenum);

	if (size(newvolume) == 1)
	{
	    newvolume = "0" + newvolume;
	}

	return volumedir + newvolume + volumebase;
    }
    else
    {
	return "";
    }
}

/**
 * Create list of removable devices for combo box widget.
 * @param dev Map with devices
 * @param sel Preselected device
 * @return list Combo box content
 */

define list CreateDeviceList(map<string,map> dev, string sel) ``{
    list ret = [];

    // add selected device list if it's missing in map
    if (sel != nil && sel != "" && !haskey(dev, sel))
    {
	ret = [ `item(`id(sel), sel) ];
    }

    foreach(string d, map info, dev, ``{
	    ret = add(ret, `item(`id(info["device"]:"" + " (" + d + ")"), info["device"]:"" + " (" + d + ")", sel == d));
	}
    );

    return ret;
}

/**
 * Enable/disable widget in file selction dialog according to
 * selected input type
 * @param type Symbol of widget which will be enabled (possible values are `file, `nfs, `removable)
 */

define void ShadowButtons(symbol type) ``{
    UI::ChangeWidget(`id(`filename), `Enabled, type == `file);
    UI::ChangeWidget(`id(`selectfile), `Enabled, type == `file);

    UI::ChangeWidget(`id(`nfsserver), `Enabled, type == `nfs);
    UI::ChangeWidget(`id(`nfsfilename), `Enabled, type == `nfs);
    UI::ChangeWidget(`id(`selecthost), `Enabled, type == `nfs);

    UI::ChangeWidget(`id(`device), `Enabled, type == `removable);
    UI::ChangeWidget(`id(`remfilename), `Enabled, type == `removable);
    UI::ChangeWidget(`id(`remfile), `Enabled, type == `removable);
}


/**
 * Convert selected device name in combobox to URL-like equivalent
 * @param selected Selected string in combo box
 * @param dev Devices info
 * @return string Device name in URL-like syntax
 */

define string ComboToDevice(string selected, map<string,map> dev) ``{
    string ret = "";

    foreach(string d, map info, dev, ``{
	    if (selected == info["device"]:"" + " (" + d + ")")
	    {
		ret = info["type"]:"cd://";
	    }
	}
    );

    if (ret == "")
    {
	ret = "dev://" + selected + ":";
    }

    return ret;
}

/**
 * Backup archive is selected in this dialog.
 * @param multivolume True = first archive file is entered, otherwise volume parts are entered
 * @param askformore False: ask only for one volume part, true: ask until all volumes are entered
 * @return symbol UI::UserInput() result
 */
define symbol ArchiveSelectionDialog(boolean multivolume, boolean askformore, string input) ``{

    y2debug("input: %1", input);

    // cache removable devices
    if (removabledevices == nil)
    {
	removabledevices = DetectRemovable();
	y2milestone("Detected removable devices: %1", removabledevices);
    }

    if (multivolume == false && Mode::config () == false)
    {
	// clear previous selection
	Restore::ResetArchiveSelection();
    }

    string file_name = "";
    string nfs_server = "";
    string nfs_file = "";
    string cd_file = "";

    symbol type = `file;
    string dev = "";

    string urlinput = (input != nil && input != "") ? input : Restore::inputname;
    string proposal = "";

    if (urlinput != nil)
    {
	y2debug("urlinput: %1", urlinput);

	map parsed_url = URL::Parse(urlinput);
	string scheme = parsed_url["scheme"]:"file";

	if (scheme == "nfs")
	{
	    type = `nfs;
	    nfs_server = parsed_url["host"]:"";
	    nfs_file = parsed_url["path"]:"";
	    proposal = nfs_file;
	}
	else if (regexpmatch(scheme, "^cd[0-9]*") || regexpmatch(scheme, "^fd[0-9]*"))
	{
	    type = `removable;

	    string devindex = regexpsub(scheme, "[cf]d0*([0-9]*)", "\\1");

	    if (devindex == nil || devindex == "")
	    {
		devindex = "0";
	    }

	    if (Mode::test () == false)
	    {
		path devpath = regexpmatch(scheme, "^cd[0-9]*") ? .probe.cdrom : .probe.floppy;
		dev = lookup(select((list<map>) SCR::Read(devpath), tointeger(devindex), $[]), "dev_name", "");
	    }

	    cd_file = parsed_url["path"]:"";
	    proposal = cd_file;
	}
	else if (scheme == "dev")
	{
	    type = `removable;
	    dev = "/dev/" + parsed_url["host"]:"";
	    file_name = parsed_url["path"]:"";
	    proposal = file_name;
	}
	else
	{
	    type = `file;
	    file_name = parsed_url["path"]:"";
	    proposal = file_name;
	}

	if (urlinput == input && multivolume == true)
	{
	    proposal = ProposeNextVolume(proposal);

	    if (proposal != "")
	    {
		if (type == `removable)
		{
		    cd_file = proposal;
		}
		else if (type == `nfs)
		{
		    nfs_file = proposal;
		}
		else
		{
		    file_name = proposal;
		}
	    }
	}
    }

    // unmount previous file system
    Restore::Umount();

    term contents = `VBox(
		// frame label
		`Frame((multivolume == false ? _("Backup archive") : _("Multivolume archive")),
			`HBox(
			    `RadioButtonGroup(`id(`source), `opt(`notify),
				`VBox(
				    `VSpacing(0.5),
				    // radio button label
				    `Left(`RadioButton(`id(`file),  `opt(`notify), _("&Local file"), type == `file)),
				    `HBox(
					`HSpacing(2),
					// text entry label
					`TextEntry(`id(`filename), _("Archive file na&me:"), file_name),
					`HSpacing(1),
					// push button label
					`Bottom(`PushButton(`id(`selectfile), _("&Select file...")))
				    ),
				    `VSpacing(1),
				    // radio button label
				    `Left(`RadioButton(`id(`nfs), `opt(`notify), _("Network (N&FS)"), type == `nfs)),
				    `HBox(
					`HSpacing(2),
					// text entry label
					`TextEntry(`id(`nfsserver), _("I&P adress or name of NFS server:"), nfs_server),
					`HSpacing(1),
					// push button label
					`Bottom(`PushButton(`id(`selecthost), _("Select &host...")))
				    ),
				    `HBox(
					`HSpacing(2),
					// text entry label
					`TextEntry(`id(`nfsfilename), _("&Archive file name:"), nfs_file)
				    ),
				    `VSpacing(1),
				    // radio button label
				    `Left(`RadioButton(`id(`removable), `opt(`notify), _("Rem&ovable device"), type == `removable)),
				    `HBox(
					`HSpacing(2),
					// combo box label
					`Left(`ComboBox(`id(`device), `opt(`editable), _("&Device"), CreateDeviceList(removabledevices, dev)))
				    ),
				    `HBox(
					`HSpacing(2),
					// text entry label
					`TextEntry(`id(`remfilename), _("Archi&ve file name:"), cd_file),
					`HSpacing(1),
					// push button label
					`Bottom(`PushButton(`id(`remfile), _("S&elect file...")))
				    ),
				    `VSpacing(1)
				)
			    ),
			    `HSpacing(1)
			)
		    ),
		`VSpacing(1)
	);

    // dialog header
    string title = (multivolume == false) ? _("Archive selection") : _("Multivolume archive selection");

    Wizard::SetContents(title, contents, (multivolume == true ? ArchiveMultiSelectionHelp() : ArchiveSelectionHelp()), true, true);

    ShadowButtons(type);

    any ret = nil;

    do
    {
	ret = UI::UserInput();

	if (ret == `selectfile)
	{
	    string file = UI::AskForExistingFile("/", "*.tar", _("Select Archive File"));

	    if (file != nil && file != "")
	    {
		UI::ChangeWidget(`id(`filename), `Value, file);
	    }
	}
	if (ret == `selecthost)
	{
	    string selectedhost = NetworkPopup::NFSServer((string) UI::QueryWidget(`id(`nfsserver), `Value));

	    if (selectedhost != "" && selectedhost != nil)
	    {
		UI::ChangeWidget(`id(`nfsserver), `Value, selectedhost);
	    }
	}
	else if (ret == `nfs || ret == `removable || ret == `file)
	{
	    ShadowButtons((symbol) UI::QueryWidget(`id(`source), `CurrentButton));
	}
	else if (ret == `remfile)
	{
	    string selected = (string) UI::QueryWidget(`id(`device), `Value);
	    string device = ComboToDevice(selected, removabledevices);
	    string fname = (string) UI::QueryWidget(`id(`remfilename), `Value);

	    // file selection from removable device - mount device
	    map mount = Restore::MountInput(device + fname);

	    if (mount["success"]:false == true)
	    {
		string mountpnt = mount["mpoint"]:"/";
		string file = UI::AskForExistingFile(mountpnt + "/", "*.tar", _("Select Archive File"));

		if (file != nil && file != "")
		{
		    // check if file is under mountpoint directory
		    if (substring(file, 0, size(mountpnt)) != mountpnt)
		    {
			// error message - selected file is out of mounted file system
			Popup::Error(_("The selected file is not on the mounted device."));
		    }
		    else
		    {
			// set file name
			UI::ChangeWidget(`id(`remfilename), `Value, substring(file, size(mountpnt)));
		    }
		}

		// umount file system
		SCR::Execute(.target.umount, mountpnt);
	    }
	    else
	    {
		// error message
		Popup::Error(_("Cannot mount file system."));
	    }
	}
	else if (ret == `next)
	{
	    symbol type = (symbol) UI::QueryWidget(`id(`source), `CurrentButton);

	    if (Mode::test () == true)
	    {
		input = "file:///tmp/archive.tar";
	    }
	    else if (type == `file)
	    {
		string fname = (string) UI::QueryWidget(`id(`filename), `Value);

		if (fname == "")
		{
		    // error message - file name is missing
		    Popup::Error(_("Enter a valid file name."));
		    input = "";
		}
		else
		{
		    input = "file://" + fname;
		}
	    }
	    else if (type == `nfs)
	    {
		string server = (string) UI::QueryWidget(`id(`nfsserver), `Value);
		string file = (string) UI::QueryWidget(`id(`nfsfilename), `Value);

		if (server == "" || file == "")
		{
		    // error message - file or server name is missing
		    Popup::Error(_("Enter a valid server and file name."));
		    input = "";
		}
		else
		{
		    input = "nfs://" + server + ":" + file;
		}
	    }
	    else if (type == `removable)
	    {
		string selected = (string) UI::QueryWidget(`id(`device), `Value);
		string device = ComboToDevice(selected, removabledevices);
		string fname = (string) UI::QueryWidget(`id(`remfilename), `Value);

		y2milestone("Selected removable device: %1", device);

		if (device == "" || fname == "")
		{
		    // error message - file or device name is missing
		    Popup::Error(_("Enter a valid device and file name."));
		    input = "";
		}
		else
		{
		    input = device + fname;
		}
	    }
	    else
	    {
		y2error("Unknown source type %1", type);
	    }

	    if (input != "")
	    {
		boolean configure = true;

		if (Mode::config ())
		{
		    // popup question
		    boolean answer = Popup::YesNo(_("Detailed configuration requires reading the archive.
If an archive is not read, full restoration will be configured.

Read the selected archive?
"));

		    configure = answer;
		    Restore::completerestoration = !answer;
		    y2debug("completerestoration: %1", Restore::completerestoration);

		    if (!configure)
		    {
			Restore::runbootloader = true;
			Restore::runSuSEconfig = true;
			Restore::restoreRPMdb = true;
			Restore::inputname = input;
		    }
		}

		if (configure)
		{
		    boolean readresult = false;
		    boolean lastvolume = false;

		    // progress message
		    UI::OpenDialog(`Label(_("Reading archive contents...")));

		    y2debug("Restore::IsMultiVolume(): %1", Restore::IsMultiVolume());

		    if (Restore::IsMultiVolume() == false)
		    {
			readresult = Restore::Read(input);
		    }
		    else
		    {
			// read next volume
			map nextresult = Restore::ReadNextVolume(input);
			readresult = nextresult["success"]:false;
			lastvolume = nextresult["lastvolume"]:false;
		    }

		    UI::CloseDialog();

		    if (readresult == false)
		    {
			// error message - %1 is archive file name
			Popup::Error(sformat(_("Cannot read backup archive file '%1'."), input));
			Restore::Umount();
			ret = `dummy;
		    }
		    else
		    {
			restoredfiles = [];
			failedfiles = [];

			if (Restore::IsMultiVolume() == true && askformore == true)
			{
			    // umount source and ask for next volume
			    Restore::Umount();

			    if (lastvolume == false)
			    {
				if (multivolume == true)
				{
				    symbol widget = nil;

				    if (type == `file)
				    {
					widget = `filename;
				    }
				    else if (type == `removable)
				    {
					widget = `remfilename;
				    }
				    else if (type == `nfs)
				    {
					widget = `nfsfilename;
				    }
				    else
				    {
					y2warning("Unknown source type: %1", type);
				    }

				    string fn = (string) UI::QueryWidget(`id(widget), `Value);
				    string prop = ProposeNextVolume(fn);

				    if (prop != "")
				    {
					UI::ChangeWidget(`id(widget), `Value, prop);
				    }

				    ret = `dummy;
				}
			    }
			    else
			    {
				// last volume - test all volumes together
				boolean testall = Restore::TestAllVolumes();

				y2debug("TestAllVolumes(): %1", testall);

				if (testall == false)
				{
				    y2error("Test Restore::TestAllVolumes() failed");
				    // error message - multi volume archive consistency check failed
				    Popup::Error(_("Test of all volumes failed.
	An archive file is probably corrupted.
	"));

				    ret = `back;
				}
			    }
			}
		    }
		}
		else
		{
		    ret = `noconfig;
		}
	    }
	    else
	    {
		ret = `dummy;
	    }
	}
	else if (ret == `cancel)
	{
	    ret = `abort;
	}
    }
    while (ret != `next && ret != `abort && ret != `back && ret != `multi && ret != `noconfig);

    return (symbol) ret;
}

/**
 * Display archive property - date of backup, user comment...
 * @return symbol UI::UserInput() result
 */
define symbol ArchivePropertyDialog() ``{

    if (Mode::config () == false)
    {
	y2milestone("missing packages %1: ", Restore::GetMissingPackages());
	y2milestone("extra packages %1: ", Restore::GetExtraPackages());
	y2milestone("mismatched packages %1: ", Restore::GetMismatchedPackages());
    }

    string date = Restore::GetArchiveDate();
    string hostname = Restore::GetArchiveHostname();
    string comment = Restore::GetArchiveComment();
    string archname = Restore::GetInputName();

    string multivolume = (Restore::IsMultiVolume() == true) ? _("Yes") : _("No");

    term contents = `HBox(
	    `HSpacing(2),
	    `VBox(
		`VSpacing(1),
		// label text
		`Left(`HBox(`Label(`id(`flabel), _("Archive file name:")), `HSpacing(2), `Label(`id(`flabel2), archname))),
		`VSpacing(0.5),
		// label text
		`Left(`HBox(`Label(`id(`dlabel), _("Date of backup:")), `HSpacing(2), `Label(`id(`dlabel2), date))),
		`VSpacing(0.5),
		// label text
		`Left(`HBox(`Label(`id(`hlabel), _("Backup host name:")), `HSpacing(2), `Label(`id(`hlabel2), hostname))),
		`VSpacing(0.5),
		// label text
		`Left(`HBox(`Label(`id(`mlabel), _("Multivolume archive:")), `HSpacing(2), `Label(`id(`mlabel2), multivolume))),
		`VSpacing(1.0),
		// multi line widget label
		`MultiLineEdit(`id(`description), `opt(`disabled), _("Archive &description:"), comment),
		`VSpacing(1.0),
		// push button label
		`PushButton(`id(`details), `opt(`key_F2), _("&Archive content...")),
		`VSpacing(1),
		// push button label
		`PushButton(`id(`options), `opt(`key_F7), _("E&xpert options...")),
		`VSpacing(1.5)
	    ),
	    `HSpacing(2)
	);

    // dialog header
    Wizard::SetContents(_("Archive properties"),
	contents, ArchivePropertyHelp(), true, true);

    any ret = nil;

    do
    {
	ret = UI::UserInput();

	if (ret == `cancel)
	{
	    ret = `abort;
	}
    }
    while (ret != `next && ret != `abort && ret != `back && ret != `details && ret != `options);

    if (Restore::IsMultiVolume() == true && Restore::TestAllVolumes() == false && ret == `next)
    {
	// ask for next volumes
	ret = `multi;
    }

    lastret = (symbol) ret;
    return (symbol) ret;
}

/**
 * Return content for table widget - list of backup files
 * @param packagesinfo Map $[ "packagename" : $[ "files" : ["files in the archive"] ] ]
 * @return list Table content
 */
define list<term> CreateArchiveContentTree(map<string, map<string, any> > packagesinfo) ``{
    list<term> ret = [];
    integer num = 0;

    if (packagesinfo != nil)
    {
	foreach(string p, map<string, any> info, packagesinfo, ``{
		list<string> files = info["files"]:[];
		list<term> itemfiles = [];
		string version = info["vers"]:"";

		itemfiles = maplist(string s, files, ``{return `item(s);});

		if (p == "")
		{
		    // package name for files not owned by any package
		    p = _("-- No package --");
		}

		ret = add(ret, `item(`id(num), p + "-" + version, itemfiles));
		num = num + 1;
	    }
	);
    }

    return ret;
}

/**
 * Display content of backup archive in the table.
 * @return symbol UI::UserInput() result
 */
define symbol ArchiveContentsDialog() ``{

    Wizard::ClearContents();

    if (archivecontentscache == nil)
    {
	archivecontentscache = CreateArchiveContentTree(Restore::GetArchiveInfo());
    }

    term contents = `HBox(
	    `HSpacing(2),
	    `VBox(
		`VSpacing(1),
		// tree label
		`Tree(`id(`tree), _("Archive &contents:"), CreateArchiveContentTree(Restore::GetArchiveInfo())),
		`VSpacing(1.5)
	    ),
	    `HSpacing(2)
	);

    Wizard::SetNextButton(`next, Label::OKButton() );

    // dialog header
    Wizard::SetContents(_("Archive contents"), contents, ArchiveContentHelp(), true, true);

    any ret = nil;

    do
    {
	ret = UI::UserInput();

	if (ret == `cancel)
	{
	    ret = `abort;
	}
    }
    while (ret != `next && ret != `abort && ret != `back);

    Wizard::RestoreNextButton();

    return (symbol) ret;
}

/**
 * Dialog with options.
 * @return UI::UserInput() result
 */
define symbol RestoreOptionsDialog() ``{
    term contents = `HBox(
	    `HSpacing(2),
	    `VBox(
		`VSpacing(1),
		// check box label - restore option
		`Left(`CheckBox(`id(`lilo), _("Activate &boot loader configuration after restoration"), Restore::runbootloader)),
		`VSpacing(0.2),
		// check box label - restore option
		`Left(`CheckBox(`id(`susecfg), _("Run &SuSEconfig after restoration"), Restore::runSuSEconfig)),
		`VSpacing(1),
		// check box label - restore option
		`Left(`TextEntry(`id(`target), _("Target Directory"), Restore::targetDirectory)),
		`VSpacing(1.5)
	    ),
	    `HSpacing(2)
	);

    Wizard::SetNextButton(`next, Label::OKButton() );

    // dialog header
    Wizard::SetContents(_("Restore options"), contents, RestoreOptionsHelp(), true, true);

    any ret = nil;
    string target_dir = "/";

    do
    {
	ret = UI::UserInput();

	target_dir = (string)UI::QueryWidget(`id(`target), `Value);

	if (size(target_dir) == 0 || substring(target_dir, 0, 1) != "/")
	{
	    // error message - entered directory is empty or doesn't start with / character
	    Popup::Error(_("The target directory is invalid or the path is not absolute."));

	    ret = nil;
	}
    }
    while(ret != `next && ret != `abort && ret != `back);

    if (ret == `cancel)
    {
	ret = `abort;
    }
    else
    {
	Restore::runbootloader = (boolean) UI::QueryWidget(`id(`lilo), `Value);
	Restore::runSuSEconfig = (boolean) UI::QueryWidget(`id(`susecfg), `Value);
	Restore::targetDirectory = (string)UI::QueryWidget(`id(`target), `Value);
    }

    Wizard::RestoreNextButton();

    return (symbol) ret;
}

/**
 * Create content for table widget - columns: selection mark, package name, backup version, installed version, description
 * @param contents Map $[ "packagename" : $[ "ver" : "version", "descr" : "short description" ] ]
 * @param defaultval if true "X" is in the first column, else " "
 * @param selected Selected packages (only for autoinstallation, otherwise should be nil)
 * @return list Contents for Table widget
 */
define list CreateTableContentsWithMismatched(map<string, map<string, string> > contents, map selected, boolean defaultval) ``{
    list ret = [];
    integer num = 0;

    string defval = (defaultval == true) ? "X" : " ";

    if (contents != nil)
    {
	foreach(string p, map<string, string> m, contents, ``{

		string ver = m["ver"]:"";
		string descr = m["descr"]:"";
		string installed = m["inst"]:"";

		if (selected != nil)
		{
		    defval = haskey(selected, p) ? "X" : " ";
		}

		ret = add(ret, `item(`id(num), defval, p, ver, installed, descr));
		num = num + 1;
	    }
	);
    }

    return ret;
}


/**
 * Dialog for package selection - packages to install
 * @return symbol UI::UserInput() result
 */
define symbol SelectionInstallDialog() ``{

    map<string, map<string, string> > missingpackages = Restore::GetMissingPackages();

    // add mismatched packages
    foreach(string p, map<string, string> info, Restore::GetMismatchedPackages(), ``{
	    missingpackages = add(missingpackages, p, info);
	}
    );

    // if all packages are installed return `next (or `back)
    if (size(missingpackages) == 0)
    {
	return lastret;
    }

    list missing = CreateTableContentsWithMismatched(missingpackages, packagestoinstall, true);
    // table header
    term header = `header(" ", _("Package"), _("Version"), _("Installed version"), _("Description"));

    term contents = `HBox(
	    `HSpacing(2),
	    `VBox(
		`VSpacing(1),
		`Table(`id(`pkg), `opt(`notify), header, missing),
		`VSpacing(1),
		`HBox(
		    // push button label
		    `PushButton(`id(`all), _("&Select all")),
		    // push button label
		    `PushButton(`id(`none), _("&Deselect all"))
		),
		`VSpacing(1.5)
	    ),
	    `HSpacing(2)
	);

    // dialog header
    Wizard::SetContents(_("Package restoration -- installation"), contents, InstallPackageHelp(), true, true);

    any ret = nil;

    do
    {
	ret = UI::UserInput();

	if (ret == `all)
	{
	    UI::ChangeWidget(`id(`pkg), `Items, CreateTableContentsWithMismatched(missingpackages, nil, true));
	}
	else if (ret == `none)
	{
	    UI::ChangeWidget(`id(`pkg), `Items, CreateTableContentsWithMismatched(missingpackages, nil, false));
	}
	else if (ret == `pkg)
	{
	    integer current = (integer) UI::QueryWidget(`id(`pkg), `CurrentItem);
	    string current_value = (string) select((term) UI::QueryWidget(`id(`pkg), `Item(current)), 1, " ");

	    if (current_value == " ")
	    {
		current_value = "X";
	    }
	    else
	    {
		current_value = " ";
	    }

	    UI::ChangeWidget(`id(`pkg), `Item(current, 0), current_value);
	}
	else if (ret == `cancel)
	{
	    ret = `abort;
	}
    }
    while (ret != `next && ret != `abort && ret != `back);

    if (ret != `abort)
    {
	integer num = size(missingpackages);
	integer i = 0;

	packagestoinstall = $[];

	while (i < num)
	{
	    string s = (string) select((term) UI::QueryWidget(`id(`pkg), `Item(i)), 1, " ");
	    string p = (string) select((term) UI::QueryWidget(`id(`pkg), `Item(i)), 2, " ");

	    if (s == "X")
	    {
		map i = missingpackages[p]:$[];
		string v = i["ver"]:"";
		packagestoinstall = add(packagestoinstall, p, $[ "ver" : v ]);

		// change default restore status to 'restore' for packages which will be installed
		if (haskey(Restore::GetArchiveInfo(), p))
		{
		    Restore::SetRestoreSelection(p, $[ "sel_type" : "X" ] );
		}
	    }
	    else
	    {
		// change default restore status to 'do not restore' for packages which will not be installed
		if (haskey(Restore::GetArchiveInfo(), p))
		{
		    Restore::SetRestoreSelection(p, $[ "sel_type" : " " ] );
		}

		if (haskey(packagestoinstall, p))
		{
		    packagestoinstall = remove(packagestoinstall, p);
		}
	    }

	    i = i + 1;
	}

	y2milestone("Selected packages to install: %1", packagestoinstall);
    }

    // TODO: warn if some packages are not available on CDs and display path selection dialog to packages
    // LATER: allow to select package from backup archive (YOU stores packages to /var/... and they can be used)

    lastret = (symbol) ret;
    return (symbol) ret;
}


/**
 * Create content for table widget - columns: selection mark, package name, version, description
 * @param contents Map $[ "packagename" : $[ "ver" : "version", "descr" : "short description" ] ]
 * @param defaultval if true "X" is in the first column, else " "
 * @param selected Selected packages (only for autoinstallation, otherwise should be nil)
 * @return list Contents for Table widget
 */
define list CreateTableContents(map<string, map<string, string> > contents, map selected, boolean defaultval) ``{
    list ret = [];
    integer num = 0;

    string defval = (defaultval == true) ? "X" : " ";

    if (contents != nil)
    {
	foreach(string p, map<string, string> m, contents, ``{

		string ver = m["ver"]:"";
		string descr = m["descr"]:"";

		if (selected != nil)
		{
		    defval = haskey(selected, p) ? "X" : " ";
		}

		ret = add(ret, `item(`id(num), defval, p, ver, descr));
		num = num + 1;
	    }
	);
    }

    return ret;
}


/**
 * Dialog for package selection - packages to uninstall
 * @return symbol UI::UserInput() result
 */
define symbol SelectionUninstallDialog() ``{

    map<string, map<string, string> > extrapackages = Restore::GetExtraPackages();

    // if none extra package is installed return `next (or `back)
    if (size(extrapackages) == 0)
    {
	return lastret;
    }

    list extra = CreateTableContents(extrapackages, packagestouninstall, true);
    // table header
    term header = `header(" ", _("Package"), _("Version"), _("Description"));

    term contents = `HBox(
	    `HSpacing(2),
	    `VBox(
		`VSpacing(1),
		`Table(`id(`pkg), `opt(`notify), header, extra),
		`VSpacing(1),
		`HBox(
		    // push button label
		    `PushButton(`id(`all), _("&Select all")),
		    // push button label
		    `PushButton(`id(`none), _("&Deselect all"))
		),
		`VSpacing(1.5)
	    ),
	    `HSpacing(2)
	);

    // dialog header
    Wizard::SetContents(_("Package restoration -- uninstallation"), contents, UninstallPackageHelp(), true, true);

    any ret = nil;

    do
    {
	ret = UI::UserInput();

	if (ret == `all)
	{
	    UI::ChangeWidget(`id(`pkg), `Items, CreateTableContents(extrapackages, nil, true));
	}
	else if (ret == `none)
	{
	    UI::ChangeWidget(`id(`pkg), `Items, CreateTableContents(extrapackages, nil, false));
	}
	else if (ret == `pkg)
	{
	    integer current = (integer) UI::QueryWidget(`id(`pkg), `CurrentItem);
	    string current_value = (string) select((term) UI::QueryWidget(`id(`pkg), `Item(current)), 1, " ");

	    if (current_value == " ")
	    {
		current_value = "X";
	    }
	    else
	    {
		current_value = " ";
	    }

	    UI::ChangeWidget(`id(`pkg), `Item(current, 0), current_value);
	}
	else if (ret == `cancel)
	{
	    ret = `abort;
	}
    }
    while (ret != `next && ret != `abort && ret != `back);

    if (ret != `abort)
    {
	integer num = size(extrapackages);
	integer i = 0;

	packagestouninstall = $[];

	while (i < num)
	{
	    string s = (string) select((term) UI::QueryWidget(`id(`pkg), `Item(i)), 1, " ");
	    string p = (string) select((term) UI::QueryWidget(`id(`pkg), `Item(i)), 2, " ");

	    if (s == "X")
	    {
		map i = extrapackages[p]:$[];
		string v = i["ver"]:"";
		packagestouninstall = add(packagestouninstall, p, $[ "ver" : v ]);
	    }
	    else if (p != nil && haskey(packagestouninstall, p))
	    {
		packagestouninstall = remove(packagestouninstall, p);
	    }

	    i = i + 1;
	}

	y2milestone("Selected packages to uninstall: %1", packagestouninstall);
    }

    lastret = (symbol) ret;
    return (symbol) ret;
}

/**
 * Start Yast2 package manager
 * @return symbol UI::UserInput() result
 */
define symbol SWsingleDialog() ``{
    list<string> install = [];
    list<string> uninstall = [];

    if (lastret == `back)
    {
	return `back;
    }

    if (size(packagestoinstall) > 0)
    {
	foreach(string k, any v, packagestoinstall, ``{install = add(install, k);});
    }

    if (size(packagestouninstall) > 0)
    {
	foreach(string k, any v, packagestouninstall, ``{uninstall = add(uninstall, k);});
    }

    y2milestone("install: %1", install);
    y2milestone("uninstall: %1", uninstall);

    if (size(install) > 0 || size(uninstall) > 0)
    {
	Package::DoInstallAndRemove(install, uninstall);

	Restore::ReadActualInstalledPackages();
    }

    return lastret;
}

/**
 * Return table widget contens - files and packages selected for restoration
 * @param restoreselection Restore settings
 * @return list Table content
 */
define list<term> CreateTableContentsRestoreSelection(map<string, map<string, any> > restoreselection) ``{
    list<term> ret = [];
    // id of item in the table
    integer num = 0;

    if (restoreselection != nil)
    {
	foreach(string p, map<string, any> m, restoreselection, ``{

		string ver = m["vers"]:"";
		string descr = m["descr"]:"";

		string seltype = m["sel_type"]:" ";

		string numfiles = "";

		if (seltype == "X")
		{
		    // all files selected for restoration
		    numfiles = _("all");
		}
		else if (seltype == " ")
		{
		    numfiles = "";
		}
		else if (seltype == "P")
		{
		    integer total = size(m["files"]:[]);
		    integer sel = size(m["sel_file"]:[]);

		    // selected %1 (number of files) of %2 (number of files)
		    numfiles = sformat(_("%1 of %2"), sel, total);
		}
		else
		{
		    y2error("Unknown selection type: %1", seltype);
		}

		if (p == "")
		{
		    // name for "no package" - files not owned by any package
		    p = _("-- No package --");
		}

		ret = add(ret, `item(`id(num), seltype, numfiles, p, ver, descr));

		num = num + 1;
	    }
	);
    }

    return ret;
}

/**
 * Ask wheter missing package should be installed and restored
 * @param package Package name
 * @param version Package version
 * @return boolean True if package should be installed
 */

define boolean InstallQuestion(string package, string version) ``{
    boolean ret = false;

    if (Mode::config () == true)
    {
	// do not ask in autoinstall config mode
	return true;
    }

    if (package != "" && !haskey(Restore::GetActualInstalledPackages(), package) && !haskey(packagestoinstall, package))
    {
	// popup question - %1 is package name
	ret = Popup::AnyQuestion("", sformat(_("Package '%1' is not installed in your system.
Install it?
"), package + "-" + version), Label::YesButton(), Label::NoButton(), `focus_yes);

	if (ret == true)
	{
	    // add package to the map of installed packages
	    packagestoinstall = add(packagestoinstall, package, $[ "ver" : version ]);
	}
    }

    return ret;
}

/**
 * Packages (and files) for restoration can be selected in this archive.
 * @return symbol UI::UserInput() result
 */
define symbol PackageSelectionRestoreDialog() ``{

    term button = `PushButton(`id(`files), `opt(`key_F7), _("S&elect files"));

    list<term> tablecontents = CreateTableContentsRestoreSelection(Restore::GetArchiveInfo());
    integer position = 0;

    // refresh previous selection
    if (restorepackagename != nil)
    {
	foreach(term t, tablecontents, ``{
		if (restorepackagename == select(t, 3, ""))
		{
		    position = select(select(t, 0, `id(0)), 0, 0);
		}
	    }
	);
    }

    map<string, any> proposedRPMrestoration = Restore::ProposeRPMdbRestoration();
    y2milestone("Proposed RPM restoration: %1", proposedRPMrestoration);

    boolean RPMoption = Restore::restoreRPMdb;

    y2warning("RPMoption: %1", RPMoption);

    if (RPMoption == nil)
    {
	RPMoption = (boolean) (proposedRPMrestoration["proposed"]:false);
    }

    y2warning("RPMoption: %1", RPMoption);

    term contents = `HBox(
	    `HSpacing(2),
	    `VBox(
		`VSpacing(1),
		// table header
		`Table(`id(`pkgtable),`opt(`notify), `header(" ", _("Files"), _("Package"), _("Version"), _("Description")), tablecontents),
		`VSpacing(0.2),
		// push button label
		`HBox(`PushButton(`id(`select), _("&Select all")), `PushButton(`id(`deselect), _("&Deselect all")), button),
		`VSpacing(1.0),
		// check box label - restore option
		`CheckBox(`id(`rpmdb), `opt(`notify), _("Restore RPM &database (if present in archive)"), RPMoption),
		`VSpacing(1.5)
	    ),
	    `HSpacing(2)
	);

    // description of symbols in the table 1/2
    string helptext = _("X = Restore all files from backup, P = Partial restore of manually selected files");

    // description of symbols in the table 2/2
    helptext = helptext + _("<P><BR>To select files to restore from the archive, press <B>Select files</B>.</P>");

    // dialog header
    Wizard::SetContents(_("Packages to restore"), contents, RestoreSelectionHelp(false), true, true);

    if (Mode::config () == true)
    {
	Wizard::SetNextButton(`next, Label::FinishButton() );
    }
    else
    {
	Wizard::SetNextButton(`next, Label::AcceptButton() );
    }

    if (Restore::RPMrestorable() == false)
    {
	// RPM DB cannot be restored (it is not contained in the archive)
	Restore::restoreRPMdb = false;

	UI::ChangeWidget(`id(`rpmdb), `Enabled, false);
	y2warning("RPM DB is not present in the archive - cannot be restored");
    }


    // set currnet item in the table
    if (size(tablecontents) > 0)
    {
	UI::ChangeWidget(`id(`pkgtable), `CurrentItem, position);
    }

    any ret = nil;


    do
    {
	ret = UI::UserInput();

	integer current = 0;
	string current_value = "";
	string current_pkgname = "";
	string current_version = "";

	if (size(tablecontents) > 0)
	{
	    current = (integer) UI::QueryWidget(`id(`pkgtable), `CurrentItem);
	    current_value = (string) select((term) UI::QueryWidget(`id(`pkgtable), `Item(current)), 1, " ");
	    current_pkgname = (string) select((term) UI::QueryWidget(`id(`pkgtable), `Item(current)), 3, "");
	    current_version = (string) select((term) UI::QueryWidget(`id(`pkgtable), `Item(current)), 4, "");
	}

	restorepackagename = current_pkgname;

	// package name "none" - files not owned by any package
	if (current_pkgname == _("-- No package --"))
	{
	    current_pkgname = "";
	}

	if (ret == `pkgtable)
	{
	    // toggle restore selection: "X" -> " ", " " -> "X", "P" -> " "
	    if (current_value == " ")
	    {
		// check if package is installed
		// TODO check versions
		if (current_pkgname != "" && !haskey(Restore::GetActualInstalledPackages(), current_pkgname) && !haskey(packagestoinstall, current_pkgname))
		{
		    current_value = InstallQuestion(current_pkgname, current_version) ? "X" : " ";
		}
		else
		{
		    current_value = "X";
		}
	    }
	    else
	    {
		current_value = " ";
	    }

	    // files are selected to restore - all
	    string selectionstring = (current_value == "X") ? _("all") : "";

	    UI::ChangeWidget(`id(`pkgtable), `Item(current, 0), current_value);
	    UI::ChangeWidget(`id(`pkgtable), `Item(current, 1), selectionstring);

	    Restore::SetRestoreSelection(current_pkgname, $["sel_type" : current_value]);
	}
	else if (ret == `files)
	{
	    // check if package is installed
	    // TODO check versions
	    if (current_value == " " && current_pkgname != "" && !haskey(Restore::GetActualInstalledPackages(), current_pkgname) && !haskey(packagestoinstall, current_pkgname))
	    {
		if (InstallQuestion(current_pkgname, current_version) == false)
		{
		    ret = `dummy;
		}
	    }

	    restorepackagename = current_pkgname;
	}
	else if ((ret == `select || ret == `deselect) && size(tablecontents) > 0)
	{
	    // set selection type
	    string sel_type = (ret == `select) ? "X" : " ";

	    if (sel_type == "X")
	    {
		// check whether some packages are missing, ask if they should be selected too
		map missing = Restore::GetMissingPackages();
		boolean selmissing = Mode::config ();	// select all packages in autoinstall config mode

		if (missing != $[] && Mode::config () == false)
		{
		    // user selected to restore all packages,
		    // but some packages are not installed
		    // ask to restore them
		    string question = _("Some packages are not installed.
Select them for restoration?
");
		    selmissing = Popup::AnyQuestion("", question, Label::YesButton(), Label::NoButton(), `focus_no);
		}

		// ask about mismatched packages
		map mismatched = Restore::GetMismatchedPackages();
		boolean selmismatch = Mode::config ();	// select all packages in autoinstall config mode

		if (mismatched != $[] && Mode::config () == false)
		{
		    // user selected to restore all packages,
		    // but some installed packages have different version than at backup
		    // ask to restore them
		    string question = _("Some installed packages have a different
version than in the backup archive.
Select them for restoration?
");
		    selmismatch = Popup::AnyQuestion("", question, Label::YesButton(), Label::NoButton(), `focus_no);
		}

		// set selection type for packages
		foreach(string p, map info, Restore::GetArchiveInfo(), ``{
			string sel = sel_type;

			if (selmissing == false && haskey(missing, p) == true)
			{
			    sel = " ";
			}
			else if (selmismatch == false && haskey(mismatched, p) == true)
			{
			    sel = " ";
			}

			Restore::SetRestoreSelection(p, $["sel_type" : sel, "sel_file" : []]);
		    }
		);
	    }
	    else
	    {
		// set selection type for all packages
		foreach(string p, map info, Restore::GetArchiveInfo(), ``{
			Restore::SetRestoreSelection(p, $["sel_type" : sel_type, "sel_file" : []]);
		    }
		);
	    }

	    // change table contents
	    UI::ChangeWidget(`id(`pkgtable), `Items, CreateTableContentsRestoreSelection(Restore::GetArchiveInfo()));

	    // set previous selection
	    if (current != nil)
	    {
		UI::ChangeWidget(`id(`pkgtable), `CurrentItem, current);
	    }
	}
	else if (ret == `rpmdb)
	{
	    // check current RPM rezstoration status with proposed
	    boolean selectedRPM = (boolean) UI::QueryWidget(`id(`rpmdb), `Value);
	    proposedRPMrestoration = Restore::ProposeRPMdbRestoration();
	    RPMoption = (boolean) (proposedRPMrestoration["proposed"]:nil);

	    if (selectedRPM != RPMoption)
	    {
		// display warning

		if (RPMoption == true)
		{
		    Popup::Warning(_("It is recommended to restore the RPM database."));
		}
		else if (RPMoption == false)
		{
		    Popup::Warning(_("It is recommended NOT to restore the RPM database."));
		}
		else
		{
		    // RPMoption is nil
		    Popup::Warning(_("There is a conflict between selected
packages and the RPM database restoration option.
Try changing the selection or the RPM database restoration status."));
		}

		Restore::restoreRPMdb = selectedRPM;
	    }
	}
	else if (ret == `cancel)
	{
	    ret = `abort;
	}
    }
    while (ret != `next && ret != `abort && ret != `back && ret != `files);

    if (ret == `next)
    {
	map<string, map<string, any> >final = Restore::GetArchiveInfo();

	final = filter(string p, map<string, any> i, final, ``{
		return (i["sel_type"]:" " != " ");
	    }
	);

	Restore::restoreRPMdb = (boolean) UI::QueryWidget(`id(`rpmdb), `Value);

	y2debug("Final restore selection: %1", final);
    }

    if (Mode::config () == true)
    {
	Wizard::RestoreNextButton();
    }

    lastret = (symbol) ret;
    return (symbol) ret;
}

/**
 * Display all files in backup archive which belong to package. User can select which files will be resored.
 * @param packagename Name of package
 * @return symbol UI::UserInput() result
 */
define symbol FileSelectionDialog(string packagename) ``{

    // create multiselection widget contents

    map pkginfo = lookup(Restore::GetArchiveInfo(), packagename, $[]);

    string sel_type = pkginfo["sel_type"]:" ";
    list<string> files = pkginfo["files"]:[];
    list sel_file = pkginfo["sel_file"]:[];

    list cont = [];

    foreach(string f, files, ``{
	    boolean selected = false;

	    if (sel_type == "X")
	    {
		selected = true;
	    }
	    else if (sel_type == " ")
	    {
		selected = false;
	    }
	    else if (sel_type == "P")
	    {
		selected = contains(sel_file, f);
	    }
	    else
	    {
		y2error("Unknown selection type %1 in package %2", sel_type, packagename);
	    }

	    cont = add(cont, `item(`id(f), f, selected));
	}
    );

    // multi selection box label
    string mlabel = _("&Files to restore");

    term contents = `HBox(
	    `HSpacing(2),
	    `VBox(
		`VSpacing(1),
		`ReplacePoint(`id(`rp), `MultiSelectionBox(`id(`mbox), mlabel, cont)),
		`VSpacing(1),
		`HBox(
		    // push button label
		    `PushButton(`id(`all), _("&Select all")),
		    // push button label
		    `PushButton(`id(`none), _("&Deselect all"))
		),
		`VSpacing(1.5)
	    ),
	    `HSpacing(2)
	);

    Wizard::SetNextButton(`next, Label::OKButton() );

    // dialog header - %1 is name of package (e.g. "aaa_base")
    Wizard::SetContents(sformat(_("File selection -- package %1"), packagename), contents, FileSelectionHelp(), true, true);

    any ret = nil;

    do
    {
	ret = UI::UserInput();

	if (ret == `all || ret == `none)
	{
	    cont = [];
	    boolean selected = (ret == `all);

	    foreach(string f, files, ``{
		    cont = add(cont, `item(`id(f), f, selected));
		}
	    );

	    UI::ReplaceWidget(`id(`rp), `MultiSelectionBox(`id(`mbox), mlabel, cont));
	}
	else if (ret == `cancel)
	{
	    ret = `abort;
	}
    }
    while(ret != `next && ret != `abort && ret != `back);

    if (ret == `next)
    {
	string sel_type_new = "";
	list sel = (list) UI::QueryWidget(`id(`mbox), `SelectedItems);

	if (size(sel) == 0)
	{
	    sel_type_new = " ";
	}
	else if (size(sel) == size(files))
	{
	    sel_type_new = "X";
	    // clear list of selected files to save memory, "X" as sel_type is enough
	    sel = [];
	}
	else
	{
	    sel_type_new = "P";
	}

	Restore::SetRestoreSelection(packagename, $["sel_type" : sel_type_new, "sel_file" : sel]);
    }

    Wizard::RestoreNextButton();

    return (symbol) ret;
}

/**
 * Restore packages from backup archive - display progress of restoring process
 * @return symbol UI::UserInput() result
 */
define symbol RestoreProgressDialog() ``{

    symbol ret = nil;
    symbol progressbar = `progress;

    integer bootloaderstep = (Restore::runbootloader == true) ? 1 : 0;

    term contents = `HBox(
	    `HSpacing(2),
	    `VBox(
		`VSpacing(1),
		`ProgressBar(`id(progressbar), " ", Restore::TotalPackagesToRestore() + bootloaderstep, 0),
		`VSpacing(1.5)
	    ),
	    `HSpacing(2)
	);

    // callback function for abort
    block<boolean> callback = ``{
	import "Label";

	any ret = UI::PollInput();
	boolean abort = false;

	if (ret == `abort || ret == `cancel)
	{
	    // abort popup question
	    abort = Popup::AnyQuestion(_("Abort confirmation"), _("Really abort restore?"), Label::YesButton(), Label::NoButton(), `focus_no);
	}

	return abort;
    };

    // dialog header
    Wizard::SetContents(_("Restoring files"), contents, RestoreProgressHelp(), false, false);

    // start restoration
    map result = Restore::Write(callback, progressbar, Restore::targetDirectory);

    // set values from restoration
    ret = (result["aborted"]:false) ? `abort : `next;

    // get lilo status
    bloaderstatus = result["bootloader"]:false;

    if (ret == `next)
    {
	 restoredfiles = result["restored"]:[];
	 failedfiles = result["failed"]:[];
	 restoredpackages = result["packages"]:0;
    }

    lastret = ret;
    return ret;
}

/**
 * Start SuSEconfig
 * @return symbol UI::UserInput() result
 */
define symbol SuSEconfigDialog() ``{

    if (Restore::runSuSEconfig == true && Mode::test () == false)
    {
	WFM::CallFunction("inst_suseconfig", []);

	// SuSE config was started
	susestatus = true;
    }

    return `next;
}


/**
 * This function should be called only once before end of client. This function
 * cleans up the system - unmounts mounted files systems.
 * @return symbol Returns symbol `next for wizard sequencer
 */

define symbol AtExit() ``{

    // unmount file system
    Restore::Umount();

    return `next;
}

/**
 * Convert programm status to string
 * @param status Status: true = OK, false = Failed, nil = "Not started"
 * @return string Status
 */

define string StatusToString(boolean status) ``{
    // program return status - program was not started
    string ret = "<I>" + _("Not started") + "</I>";

    if (status == true)
    {
	// program return status - success
	ret = _("Ok");
    }
    else if (status == false)
    {
	// program return status - failed
	ret = "<B>" + _("Failed") + "</B>";
    }

    return ret;
}


/**
 * Display summary of restoration
 * @return symbol UI::UserInput() result
 */
define symbol SummaryDialog() ``{

    // summary information texts
    string basicinfo = "<P>" + _("Number of installed packages: ") + size(packagestoinstall) + "<BR>" + _("Number of uninstalled packages: ") + size(packagestouninstall) + "</P><P>" + _("Total restored packages: ") + restoredpackages + "<BR>" + _("Total restored files: ") + ((restoredfiles != nil) ? size(restoredfiles) : 0) + "</P>";

    // display failed files if any
    if (size(failedfiles) > 0)
    {
	// summary information text - header
	 basicinfo = basicinfo + "<P><B>" + _("Failed files") + "</B><BR>" + mergestring(failedfiles, "<BR>") + "</P>";
    }

    // set lilo result string
    string lilostr = StatusToString(bloaderstatus);

    // set SuSEconfig result string
    string SuSEinfo = StatusToString(susestatus);

    string filelist = "";

    if (restoredfiles != nil && size(restoredfiles) > 0)
    {
	string prefix = Restore::targetDirectory;

	if (substring(prefix, size(prefix) - 1, 1) != "/")
	{
	    prefix = prefix + "/";
	}

	filelist = prefix + mergestring(restoredfiles, "<BR>" + prefix);
    }

    // summary information texts - details
    string extendedinfo = "<P><BR><B>" + _("Details:") + "</B></P><P>" + _("Boot Loader Configuration: ") + lilostr + "<BR>" + _("SuSEconfig status: ") + SuSEinfo + " </P><P><B>" + _("Restored files:") + "</B><BR>" + filelist + "</P>";

    // dialog header
    return DisplaySummaryDialog(basicinfo, basicinfo + extendedinfo, SummaryHelp(), _("Summary of restoration"), `finish);
}

/**
 * Whole restoration
 * @return any Returned value from Sequencer::Run() call
 */
define symbol RestoreSequence () ``{
    map aliases =
	$[
	    "archive"	:   ``( ArchiveSelectionDialog(false, false, "") ),
	    "property"	:   ``( ArchivePropertyDialog() ),
	    "marchive"	: [ ``( ArchiveSelectionDialog(true, true, "") ), true ],
	    "contents"	: [ ``( ArchiveContentsDialog() ), true ],
	    "options"	: [ ``( RestoreOptionsDialog() ), true ],
	    "install"	:   ``( SelectionInstallDialog() ),
	    "uninstall"	:   ``( SelectionUninstallDialog() ),
	    "sw_single"	:   ``( SWsingleDialog() ),
	    "select"	:   ``( PackageSelectionRestoreDialog() ),
	    "selectfile": [ ``( FileSelectionDialog(restorepackagename) ), true ],
	    "restore"	: [ ``( RestoreProgressDialog() ), true ],
	    "SuSEconfig": [ ``( SuSEconfigDialog() ), true ],
	    "atexit"	:   ``( AtExit() ),
	    "summary"	:   ``( SummaryDialog() ),
	];

    map sequence = $[
	"ws_start" :  "archive",
	"archive" :
	$[
	    `next	: "property",
	    `noconfig	: "atexit",
	    `abort	: "atexit"
	],
	"marchive" :
	$[
	    `next	: "install",
	    `abort	: "atexit"
	],
	"property" :
	$[
	    `details	: "contents",
	    `options	: "options",
	    `multi	: "marchive",
	    `next	: "install",
	    `abort	: "atexit"
	],
	"contents" :
	$[
	    `next	: "property",
	    `abort	: "atexit"
	],
	"options" :
	$[
	    `next	: "property",
	    `abort	: "atexit"
	],
	"install" :
	$[
	    `next	: "uninstall",
	    `abort	: "atexit"
	],
	"uninstall" :
	$[
	    `next	: "sw_single",
	    `abort	: "atexit"
	],
	"sw_single" :
	$[
	    `next	: "select",
	    `abort	: "atexit"
	],
	"select" :
	$[
	    `files	: "selectfile",
	    `abort	: "atexit",
	    `next	: "restore"
	],
	"restore" :
	$[
	    `next	: "SuSEconfig",
	    `abort	: "atexit"
	],
	"SuSEconfig" :
	$[
	    `next	: "summary",
	    `abort	: "atexit"
	],
	"selectfile" :
	$[
	    `next	: "select",
	    `abort	: "atexit"
	],
	"summary" :
	$[
	    `abort	: "atexit",
	    `next	: "atexit"
	],
	"atexit" :
	$[
	    `next	: `next
	]
    ];


    Wizard::CreateDialog ();
    Wizard::SetDesktopIcon("restore");

    symbol ret = Sequencer::Run(aliases, sequence);

    UI::CloseDialog ();
    return ret;
}



/**
 * Restoration without reading and writing.
 * For use with autoinstallation.
 * @return any Returned value from Sequencer::Run() call
 */
define any RestoreAutoSequence() ``{

    map aliases =
	$[
	    "archive"	:   ``( ArchiveSelectionDialog(false, false, "") ),
	    "property"	:   ``( ArchivePropertyDialog() ),
	    "marchive"	: [ ``( ArchiveSelectionDialog(true, true, "") ), true ],
	    "contents"	: [ ``( ArchiveContentsDialog() ), true ],
	    "options"	: [ ``( RestoreOptionsDialog() ), true ],
	    "select"	:   ``( PackageSelectionRestoreDialog() ),
	    "atexit"	:   ``( AtExit() ),
	    "selectfile": [ ``( FileSelectionDialog(restorepackagename) ), true ],
	];

    map sequence = $[
	"ws_start" : "archive",
	"archive" :
	$[
	    `next	: "property",
	    `noconfig	: "atexit",
	    `abort	: `abort
	],
	"marchive" :
	$[
	    `next	: "select",
	    `abort	: `abort
	],
	"property" :
	$[
	    `details	: "contents",
	    `options	: "options",
	    `multi	: "marchive",
	    `next	: "select",
	    `abort	: `abort
	],
	"contents" :
	$[
	    `next	: "property",
	    `abort	: `abort
	],
	"options" :
	$[
	    `next	: "property",
	    `abort	: `abort
	],
	"select" :
	$[
	    `files	: "selectfile",
	    `abort	: `abort,
	    `next	: `next
	],
	"selectfile" :
	$[
	    `next	: "select",
	    `abort	: `abort
	],
	"atexit" :
	$[
	    `next	: `next
	]
    ];

    Wizard::CreateDialog ();
    Wizard::SetDesktopIcon("restore");

    any ret = Sequencer::Run(aliases, sequence);

    UI::CloseDialog ();
    return ret;
}


/**
 * Return content for table widget - list of backup files
 * @param packagesinfo Map $[ "packagename" : $[ "files" : ["files in the archive"] ] ]
 * @return list Table content
 */
define list CreateArchiveContentTable(map<string, map<string, any> > packagesinfo) ``{
    list ret = [];
    integer num = 0;

    if (packagesinfo != nil)
    {
	foreach(string p, map<string, any> info, packagesinfo, ``{
		list<string> files = info["files"]:[];
		string version = info["vers"]:"";

		foreach(string file, files, ``{
			ret = add(ret, `item(`id(num), p, version, file));
			num = num + 1;
		    }
		);
	    }
	);
    }

    return ret;
}

/**
 * Select item from list
 * @param label Label in dialog
 * @param inputlist List of values
 * @param selected Default selected value
 * @return string Selected value or empty string ("") if dialog was closed
 */

define string SelectFromList(string label, list inputlist, string selected) ``{

    UI::OpenDialog(
	`HBox(
	    `VSpacing(10),
	    `VBox(
		`HSpacing(40),
		`SelectionBox(`id(`selbox), label, inputlist),
		`HBox(
		    `PushButton(`id(`ok), `opt(`default, `key_F10), Label::OKButton()),
		    `PushButton(`id(`cancel), `opt(`key_F9), Label::CancelButton())
		)
	    )
	)
    );

    if (contains(inputlist, selected))
    {
	UI::ChangeWidget(`id(`selbox), `CurrentItem, selected);
    }

    UI::SetFocus(`id(`ok));

    any uinput = nil;

    do
    {
	uinput = UI::UserInput();
    }
    while (uinput != `ok && uinput != `cancel);

    string ret = (uinput == `cancel) ? "" : (string) UI::QueryWidget(`id(`selbox), `CurrentItem);

    UI::CloseDialog();

    return ret;
}

}

