/**
 * File:
 *   include/checkmedia/ui.ycp
 *
 * Summary:
 *   User interface functions for checking media integrity
 *
 * Authors:
 *   Ladislav Slezak <lslezak@suse.cz>
 *
 * $Id:$
 *
 * All user interface functions.
 *
 */

{

textdomain "packager";

import "Wizard";
import "CheckMedia";

import "Popup";
import "Label";
import "Sequencer";
import "String";
import "Stage";

define list<term> CDdevices(string preferred) {
    list<map> cds = (list<map>)SCR::Read(.probe.cdrom);
    list<term> ret = [];

    if (cds != nil)
    {
	foreach(map cd, cds, {
		string dev = cd["dev_name"]:"";
		string model = cd["model"]:"";
		boolean deflt = (preferred == dev);

		if (dev != nil && dev != "" && model != nil)
		{
		    ret = add(ret, `item(`id(dev), model + sformat(" (%1)", dev), deflt));
		}
	    }
	);
    }

    return ret;
}

define void SetButtonState(boolean running) {
    UI::ChangeWidget(`id(`stop), `Enabled, running);
    UI::ChangeWidget(`id(`progress), `Enabled, running);
    UI::ChangeWidget(`id(`next), `Enabled, !running);
    UI::ChangeWidget(`id(`start), `Enabled, !running);
    UI::ChangeWidget(`id(`back), `Enabled, !running);
}

define list<string> TranslateInfo(list<string> info) {
    list<string> ret = [];

    if (info != nil)
    {
	foreach(string i, info, {
		list<string> parts = splitstring(i, ":");

		string key = String::CutBlanks(parts[0]:"");
		string val = String::CutBlanks(parts[1]:"");

		map<string,string> trasmap = $[
		    // %1 = CD identification
		    "app" : _("Identification: %1"),
		    // %1 medium number, e.g. CD1,CD2...
		    "media" : _("Medium: %1"),
		    // %1 = size of the medium
		    "size" : _("Size: %1"),
		    // %1 = MD5 hash value
		    "md5" : _("Expected MD5: %1"),
		    // %1 = result of the check
		    "check" : _("Result: %1"),
		    // error message
		    "not an iso" : _("The drive does not contain a medium or the ISO file system is broken.")
		];

		if (key == "check")
		{
		    // try to translate result string
		    if (val == "md5sum ok")
		    {
			// result of the check - success
			val = _("OK");
		    }
		    else
		    {
			// translate failed message
			string wrongMD5 = regexpsub(val, "md5sum wrong \\((.*)\\)", "\\1");

			if (wrongMD5 != nil)
			{
			    // result of the check - failed, %1 is the real MD5 sum
			    val = sformat(_("Failed (MD5: %1)"), wrongMD5);
			    y2milestone("computedMD5: %1", wrongMD5);
			}
		    }
		}

		string newstr = trasmap[key]:"";

		if (newstr != nil && newstr != "")
		{
		    newstr = sformat(newstr, val);

		    ret = add(ret, newstr);
		}
	    }
	);
    }

    y2milestone("Translated info: %1", ret);

    return ret;
}

/**
 * Unmount CD drives in the first installation stage
 * @return map map with unmounted drives (key: device name, value: mount point)
 */
define map<string,string> UnmountCD() {
    // key: device, value: mountpoint
    map<string,string> unmounted = $[];

    if (Stage::initial())
    {
	if (CheckMedia::cd_cache == nil)
	{
	    // the cache is not initialied, do it now
	    list<map> cds = (list<map>)SCR::Read(.probe.cdrom);

	    if (cds == nil)
	    {
		// initialize to empty list
		cds = [];
	    }

	    CheckMedia::cd_cache = cds;
	}

	y2milestone("cd_cache: %1", CheckMedia::cd_cache);

	if (CheckMedia::cd_cache != nil)
	{
	    // reload /proc/mounts file
	    SCR::UnmountAgent(.proc.mounts);

	    list<map> mounts = (list<map>)SCR::Read(.proc.mounts);
	    list<string> cddevs = maplist(map cdd, CheckMedia::cd_cache, {return cdd["dev_name"]:"";});

	    // check which CD drive is mounted
	    foreach(map mnt, mounts, {
		    string mpoint = mnt["file"]:"";
		    string device = mnt["spec"]:"";

		    if (mpoint != nil && device != nil && contains(cddevs, device))
		    {
			boolean succ = (boolean) SCR::Execute(.target.umount, mpoint);
			if (succ == true)
			{
			    unmounted = add(unmounted, device, mpoint);
			    y2milestone("Unmounted device: %1 (%2)", device, mpoint);
			}
			else
			{
			    y2warning("Could not unmount device %1 (%2)", device, mpoint);
			}
		    }
		}
	    );
	}
    }

    y2milestone("unmounted: %1", unmounted);

    return unmounted;
}

define boolean MountCD(map<string,string> mountedCDs) {
    y2milestone("mountedCDs: %1", mountedCDs);
    boolean ret = true;

    if (Stage::initial() && size(mountedCDs) > 0)
    {
	// remount all unmounted devices back
	foreach(string device, string mpoint, mountedCDs, {
		boolean succ = (boolean)SCR::Execute(.target.mount, [device, mpoint], "-o ro");
		if (succ)
		{
		    y2milestone("Remounted device %1 (%2)", device, mpoint);
		}
		else
		{
		    y2error("Mount failed: %1 (%2)", device, mpoint);
		    ret = false;
		}
	    }
	);
    }

    return ret;
}

// mount CD drive and check whether there is directory 'media.1' (the first medium) and 'boot' (bootable product CD)
define boolean InsertedCD1() {
    boolean ret = true;
    string instmode = (string)SCR::Read(.etc.install_inf.InstMode);

    if (instmode == "cd" || instmode == "dvd")
    {
	// get CD device name
	string bootcd = "/dev/" + (string)SCR::Read(.etc.install_inf.Cdrom);

	// is the device mounted?
	list<map> mounts = (list<map>)SCR::Read(.proc.mounts);
	map<string,string> mnt = listmap(map m, mounts, {return $[m["spec"]:"" : m["file"]:""];});

	string dir = "";
	boolean mounted = false;

	if (haskey(mnt, bootcd))
	{
	    dir = mnt[bootcd]:"";
	}
	else
	{
	    dir = (string)SCR::Read(.target.tmpdir) + "/YaST.mnt";
	    SCR::Execute(.target.mkdir, dir);
	    mounted = (boolean)SCR::Execute(.target.mount, [bootcd, dir], "-o ro");
	}

	// check for the first medium
	integer succ = (integer)SCR::Execute(.target.bash, sformat("test -d %1/media.1 && test -d %1/boot", dir));

	ret = (succ == 0);

	// reset to the previous state
	if (mounted)
	{
	    // unmount back
	    boolean umnt = (boolean)SCR::Execute(.target.umount, dir);
	    y2milestone("unmounted %1: %2", dir, umnt);
	}
    }

    return ret;
}

define void RequireFirstMedium() {
    while (!InsertedCD1())
    {
	// warning popup - the CD/DVD drive doesn't contain the first medium (CD1/DVD1)
	if (Popup::AnyQuestion(Popup::NoHeadline(), _("Insert the first installation medium."),
	    Label::OKButton(), Label::CancelButton(), `focus_yes) == false)
	{
	    break;
	}
    }
}

/**
 * Main dialog
 * @return symbol Result from UserInput()
 */
define symbol MainDialog () {

    // set wizard buttons at first
    if (!CheckMedia::forced_start)
    {
	Wizard::SetNextButton(`next, Label::CloseButton());
    }

    // set buttons according to mode
    if (!Stage::initial())
    {
	// remove Back button - workflow has only one dialog
	Wizard::HideBackButton();
	// remove Abort button - it's useless
	Wizard::HideAbortButton();
    }

    // umount CD drives (only in the first stage)
    map<string,string> unmountedCD = UnmountCD();

    // dialog header
    string caption = _("Media Check");

    // help text - media check (header) 1/7
    string help = _("<P><B>Media Check</B></P>") +
    // help text - media check 2/7
_("<P>When you have a problem with
installation and are using a CD or DVD installation medium, you should check
whether the medium could be broken.</P>
") +

    // help text - media check 3/7
_("<P>Select a drive, insert a medium into the drive, and press <B>Start Check</B>
to perform the check. The check can take several minutes depending on
speed of the drive and size of the medium. The check verifies the MD5 checksum.</P>
") +

    // help text - media check 4/7
_("<P>If the check of the medium fails, you should not continue the installation.
It may fail or you may lose your data. You should replace the broken
medium.</P>
") +

    // help text - media check 5/7
_("After the check you can insert the next medium and start the procedure again.
The order of the media is irrelevant.") +

    // help text - media check 6/7
_("<P><B>Note:</B> You cannot change the medium if it is used by the system. If
it happens during installation, start manual installation and select the media
verification item in the menu.</P> 
") +

    // help text - media check 7/6
_("<P>If you burn the media yourself, use the <B>pad</B> option in your recording software. It avoids read errors at the end of media during the check.</P>
");

// advice check of the media
string label = _("It is recommended to check all installation media to avoid installation problems.");

    term contents = `VBox(
	// combobox label
	CheckMedia::forced_start ? `VBox(`Left(`Label(label)), `VSpacing(0.6)) : `Empty(),
	// combo box
	`Left(`ComboBox(`id(`cddevices), _("&CD or DVD Drive"), CDdevices(CheckMedia::preferred_drive))),
	`VSpacing(0.4),
	// widget label
	`LogView(`id(`log), _("S&tatus Information"), 12, 0),
	`VSpacing(0.4),
	// progress bar label
	`ProgressBar(`id(`progress), _("Progress")),
	`VSpacing(1),
	`HBox(
	    // push button label
	    `PushButton(`id(`start), _("&Start Check")),
	    `HSpacing(2),
	    // push button label
	    `PushButton(`id(`stop), _("S&top"))
	)//,
//	`VSpacing(1)
    );

    Wizard::SetContents(caption,
	contents,
	help,
	true, true
    );

    symbol ret = nil;
    while (true)
    {
	// update state of the buttons (enabled/disabled)
	SetButtonState(false);

	ret = (symbol)UI::UserInput();

	y2milestone("ui: %1", ret);

	if (ret == `next || ret == `back)
	{
	    // avoid reproposing of the installation -  always return `back in
	    // the initial mode when the module start wasn't forced (after
	    // language selection)
	    if (Stage::initial() && !CheckMedia::forced_start)
	    {
		ret = `back;
	    }
	    break;
	}
	else if (ret == `cancel)
	{
	    ret = `abort;
	    break;
	}
	else if (ret == `abort)
	{
	    if (Popup::ConfirmAbort(`painless))
	    {
		ret = `abort;
		break;
	    }
	}
	else if (ret == `start)
	{
	    string selecteddrive = (string)UI::QueryWidget(`id(`cddevices), `Value);

	    if (selecteddrive != nil && selecteddrive != "")
	    {
		SetButtonState(true);

		y2milestone("starting media check at drive %1", selecteddrive);
		// progress message, %1 is CD device name (e.g. /dev/hdc)
		UI::ChangeWidget(`id(`log), `LastLine, sformat(_("Check started (%1)...\n"), selecteddrive));

		// try to read one byte from the medium
		integer res = (integer)SCR::Execute(.target.bash, sformat("/usr/bin/head -c 1 %1 > /dev/null", selecteddrive));
		if (res != 0)
		{
		    // error message: the medium cannot be read or no medium in the drive; %1 = drive, e.g. /dev/hdc
		    UI::ChangeWidget(`id(`log), `LastLine, sformat(_("Cannot read medium in the drive %1."), selecteddrive));
		}
		else
		{
		    CheckMedia::Start(selecteddrive);

		    boolean loop = true;
		    boolean aborted = false;
		    while(loop)
		    {
			CheckMedia::Process();

			integer progress = CheckMedia::Progress();
			list<string> data = CheckMedia::Info();

			if (data != nil && size(data) > 0)
			{
			    data = TranslateInfo(data);

			    // add new output to the log view
			    string info = mergestring(data, "\n") + "\n";
			    UI::ChangeWidget(`id(`log), `LastLine, info);
			}

			if (progress > 0)
			{
			    UI::ChangeWidget(`id(`progress), `Value, progress);
			}

			symbol ui = (symbol)UI::PollInput();

			loop = CheckMedia::Running();

			if (ui == `stop || ui == `cancel)
			{
			    CheckMedia::Stop();
			    loop = false;
			    aborted = true;
			}
			else if (ui == `abort)
			{
			    if (Popup::ConfirmAbort(`painless))
			    {
				CheckMedia::Stop();

				// remount umounted CD drives back
				MountCD(unmountedCD);

				return `abort;
			    }
			}

			// sleep for a while
			sleep(200);
		    }

		    SetButtonState(false);

		    if (aborted)
		    {
			// the check was aborted
			UI::ChangeWidget(`id(`log), `LastLine, _("Aborted"));
		    }
		}

		// add empty line between outputs
		UI::ChangeWidget(`id(`log), `LastLine, "\n\n\n");
		// set zero progress
		UI::ChangeWidget(`id(`progress), `Value, 0);
	    }
	}
	else
	{
	    y2warning("unknown UserInput: %1", ret);
	}
    };

    if (Stage::initial())
    {
	// is the first medium in drive?
	RequireFirstMedium();
    }

    // remount umounted CD drives back
    MountCD(unmountedCD);

    return ret;
}


/**
 * Main workflow of the idedma configuration
 * @return any Result from WizardSequencer() function
 */
define any MainSequence () ``{
    map aliases =
	$[
	    "checkmedia" : ``(MainDialog()),
	];

    map sequence = $[
	"ws_start" : "checkmedia",
	"checkmedia" :
	$[
	    `abort	: `abort,
	    `next	: `next
	]
    ];

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

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

    UI::CloseDialog();
    return ret;
}


}
