/**
 * File:	modules/Require.ycp
 * Package:	Packager
 * Summary:	Provide installation on demand
 * Authors:	Martin Vidner <mvidner@suse.cz>
 *
 * $Id: Require.ycp 21259 2005-02-07 05:17:14Z nashif $
 *
 * This module is intended to replace the include file require.ycp,
 * but that one still extsts for compatibility.
 * If you change something here, check if also the other file needs changing.
 */

{

    module "Require";
    textdomain "base";

    import "Label";
    import "Popup";
    import "PackageAI";
    import "PackageCallbacksInit";
    import "Wizard";	// some modules call us without any dialog open
    import "Mode";
    import "Stage";

    // The constructor has a popup and calls Pkg::SourceStartCache
    // which is time consuming and annoying if not needed. (#20486)
    // So we import it inside the functions.
    //import "PackageCallbacks";

    // --- some internal bookkeeping to simplify the external interface ---

    /**
     * Has Pkg::TargetInit run?
     */
    boolean target_initialized = false;

    /**
     * Has Pkg::SourceStartCache run?
     */
    boolean source_initialized = false;

    /**
     * Was last operation canceled?
     * Used to enhance the exit status to distinguish between package
     * installation fail and installation canceled by user, as in the second
     * case doesn't make much sense to display any error
     * Is set to true when user canceled package installation, from
     * RequireAndConflict* functions
     */
    boolean last_op_canceled = false;

    // --- Public interface, functions to initialize pkg target or source



    /**
     * Ensure that Pkg:: calls work.
     * This may become superfluous.
     */
    global define void EnsureTargetInit () ``{
	PackageCallbacksInit::InitPackageCallbacks();
	target_initialized = target_initialized || Pkg::TargetInit ("/", false);
    }

    /**
     * Ensure that Pkg:: calls working with the installation sources work
     */
    global define void EnsureSourceInit () ``{
	if (!source_initialized)
	{
	    PackageCallbacksInit::InitPackageCallbacks();
	    // popup when initializing the package manager
	    UI::OpenDialog(`opt(`decorated ),`Label(_("Reading package information; One moment please...")));
	    list sources = Pkg::SourceStartCache (true);
	    UI::CloseDialog();
	    if (size (sources) > 0)
	    {
		source_initialized = true;
	    }
	    else
	    {
		import "Popup";
		// error popup, no packages sources
		// FIXME: translation
		Popup::Error (_("No package source defined."));
	    }
	}
    }

    // --- Public interface, from low-level functions to high-level ones ---

    /**
     * Run SuSE config, create new wizard dialog before it, close after it is
     * finished
     */
    global define void RunSuSEconfig () ``{
	Wizard::CreateDialog ();
	// inst_suseconfig returns `auto or `next
	// (update mode or Args(2) is true) => no error checking
	WFM::CallFunction("inst_suseconfig", [$["enable_back":false, "enable_next":false]]);
	Wizard::CloseDialog ();
    }

    /**
     * Read-only interface for @ref last_op_canceled
     * Use immediately after calling some RequireAndTarget* function
     * @return true if it was
     */
    global define boolean LastOperationCanceled () ``{
	return last_op_canceled;
    }

    /**
     * Is a package available for installation?
     * @param	package package name, without version or .rpm suffix
     * @return true/false
     */
    global define boolean IsPackageAvailable (string package) ``{
	EnsureSourceInit ();
	return Pkg::IsAvailable (package);
    }

    /**
     * <small>This is not a real function, just a docs placeholder</small>
     * <p>
     * Most functions have two public variants: Foo and FooTarget. In
     * normal mode they behave the same and act on the currently
     * running system. In autoyast mode (Mode::config ()), FooTarget acts
     * on the autoyast package list only and Foo acts both on the
     * running system and the autoyast package list.
     *
     *<pre>
     *              |Normal AI
     * -------------+----------
     * Foo          |Pkg    AI, Pkg
     * FooTarget    |Pkg    AI
     *</pre>
     * Implementation: Foo and FooTarget call (private) FooPkg and FooAI
     * according to the above table.
     * <p>
     * We need to access AutoinstSoftware, which will not be typcally
     * installed.  So we import it only in FooAI which are called if
     * Mode::config ().
     */
    global define void FooTarget () ``{
	y2internal ("Do not call");
    }

    /**
     * Is a package installed?
     * Works on the current system.
     * @see FooTarget
     */
    define boolean IsPackageInstalledPkg (string package) ``{
	// This is a most commonly called function and so it's
	// important that it's fast, especially in the common
	// case, where all dependencies are satisfied.
	// Unfortunately, initializing Pkg reads the RPM database...
	// so we must avoid it.
	return 0 == SCR::Execute (.target.bash, "rpm -q " + package);
	// return Pkg::IsProvided (package);
    }

    /**
     * Is a package installed?
     * Works on the target system.
     * @see FooTarget
     */
    define boolean IsPackageInstalledAI (string package) ``{
	return PackageAI::Installed(package);
    }

    // ---------- AreAllPackagesInstalled* ----------
    /**
     * Are all of these packages installed?
     * Works on the current system.
     * @see FooTarget
     * @param packages list of packages
     * @return true/false
     */
    define boolean AreAllPackagesInstalledPkg (list<string> packages) ``{
	string which = find(string p, packages, ``{ return ! IsPackageInstalledPkg (p); });
	return which == nil;
    }

    /**
     * Are all of these packages installed?
     * Works on the target system.
     * @see FooTarget
     * @param packages list of packages
     * @return true/false
     */
    define boolean AreAllPackagesInstalledAI (list<string> packages) ``{
	return PackageAI::InstalledAll(packages);
    }

    /**
     * Are all of these packages installed?
     * Use for required packages that you want installed.
     * In both normal and autoyast modes, works on the current system.
     * In autoyast mode, also works on the target system.
     * @see FooTarget
     * @param packages list of packages
     * @return true/false
     */
    global define boolean AreAllPackagesInstalled (list<string> packages) ``{
	boolean result = true;
	if (Mode::config ())
	{
	    result = result && AreAllPackagesInstalledAI (packages);
	}
	result = result && AreAllPackagesInstalledPkg (packages);
	return result;
    }

    /**
     * Are all of these packages installed?
     * Use for required packages that you want installed.
     * In normal mode, works on the current system.
     * In autoyast mode, works on the target system.
     * @see FooTarget
     * @param packages list of packages
     * @return true/false
     */
    global define boolean AreAllPackagesInstalledTarget (list<string> packages) ``{
	return Mode::config ()?
	    AreAllPackagesInstalledAI (packages):
	    AreAllPackagesInstalledPkg (packages);
    }

    // ---------- IsPackageInstalled* ----------

    /**
     * Obsolete!
     * If required, use AreAllPackagesInstalled,
     * if conflicting, use IsAnyPackageInstalled.
     * @deprecated AreAllPackagesInstalled or IsAnyPackageInstalled
     */
    global define boolean IsPackageInstalled (string package) ``{
	y2warning (1, "IsPackageInstalled is obsolete");
	return AreAllPackagesInstalled ([package]);
    }

    /**
     * Is a package installed?
     * In normal mode, works on the current system.
     * In autoyast mode, works on the target system.
     * @see FooTarget
     */
    global define boolean IsPackageInstalledTarget (string package) ``{
	return Mode::config ()?
	    IsPackageInstalledAI (package):
	    IsPackageInstalledPkg (package);
    }

    // ---------- IsAnyPackageInstalled* ----------
    /**
     * Is any of these packages installed?
     * Works on the target system.
     * @see FooTarget
     * @param packages list of packages
     * @return true/false
     */
    define boolean IsAnyPackageInstalledAI (list<string> packages) ``{
	return PackageAI::InstalledAny(packages);
    }

    /**
     * Is any of these packages installed?
     * Works on the current system.
     * @see FooTarget
     * @param packages list of packages
     * @return true/false
     */
    define boolean IsAnyPackageInstalledPkg (list<string> packages) ``{
	string which = find(string p, packages, ``{ return IsPackageInstalledPkg (p); });
	return which != nil;
    }

    /**
     * Is any of these packages installed?
     * Use for conflicting packages that you want deleted.
     * In both normal and autoyast modes, works on the current system.
     * In autoyast mode, also works on the target system.
     * @see FooTarget
     * @param packages list of packages
     * @return true/false
     */
    global define boolean IsAnyPackageInstalled (list<string> packages) ``{
	boolean result = false;
	if (Mode::config ())
	{
	    result = result || IsAnyPackageInstalledAI (packages);
	}
	result = result || IsAnyPackageInstalledPkg (packages);
	return result;
    }

    /**
     * Is any of these packages installed?
     * Use for conflicting packages that you want deleted.
     * In normal mode, works on the current system.
     * In autoyast mode, works on the target system.
     * @see FooTarget
     * @param packages list of packages
     * @return true/false
     */
    global define boolean IsAnyPackageInstalledTarget (list<string> packages) ``{
	return Mode::config ()?
	    IsAnyPackageInstalledAI (packages):
	    IsAnyPackageInstalledPkg (packages);
    }


    // ---------- DoInstallAndRemove* ----------

    /**
     * Install required packages and remove conflicting packages.
     * Works on the current system.
     * @see FooTarget
     * @param toinstall packages to install
     * @param toremove pacakges to remove
     * @return success?
     */
    define boolean DoInstallAndRemovePkg (list<string> toinstall, list<string> toremove) ``{
	y2debug ("toinstall: %1, toremove: %2", toinstall, toremove);
	EnsureSourceInit ();
	EnsureTargetInit ();
	boolean ok = true;

	// licenses: #35250
	map<string, string> licenses = Pkg::PkgGetLicensesToConfirm (toinstall);
	if (size (licenses) > 0)
	{
	    list<string> rt_licenses_l = maplist (
		string p, string l, licenses,
		``( sformat ("<p><b>%1</b></p>\n%2", p, l) )
		);
	    if (! Popup::AnyQuestionRichText (
		    // popup heading, with rich text widget and Yes/No buttons
		    _("Do you accept this license agreement?",
		      "Do you accept these license agreements?",
		      size (licenses)),
		    mergestring (rt_licenses_l, "\n"),
		    70, 20,
		    Label::YesButton (), Label::NoButton (),
		    `focus_none))
	    {
		last_op_canceled = true;
		return false;
	    }
	    foreach (string p, string l, licenses, {
		Pkg::PkgMarkLicenseConfirmed (p);
	    });
	    last_op_canceled = false;
	}

	// check if the database os consistent before packages were added
	boolean packages_consistent = Pkg::PkgSolveCheckTargetOnly();

	foreach (string p, toinstall, ``{
	    if(ok == true)
		if(Pkg::PkgInstall(p) != true) {
		    y2error("Package install: %1", p);
		    ok = false;
		}
	});
	if(ok != true) return false;

	foreach (string p, toremove, ``{
	    if(ok == true)
		if(Pkg::PkgDelete(p) != true) {
		    y2error("Package delete: %1", p);
		    ok = false;
		}
	});
	if(ok != true) return false;

	if(Pkg::PkgSolve (false) != true) {
	    y2error("Package solve failed");
	    if (packages_consistent)
	    {
		return false;
	    }
	}

	//[int successful, list failed, list remaining, list srcremaining]
	list result = Pkg::PkgCommit (0);
	y2debug ("PkgCommit: %1", result);
	if(result[1]:[] != []) {
	    y2error("Package commit: %1", result[1]:[]);
	    return false;
	}

	foreach (string remaining, (list<string>) (result[2]:[]), ``{
	    if(ok == true)
		if(contains(toinstall, remaining)) {
		    y2error("Package remain: %1", remaining);
		    ok = false;
		}
	});
	if(ok != true) return false;

	// Only run SuSEconfig if any packages were installed?
	// No, deleted packages are not covered by this.
	// if (true || result[0]:-1 > 0)

	// But omit it during installation, one is run at its end.
	// #25071
	import "Mode";
	if (!Stage::initial () && !Stage::cont ())
	{
	    RunSuSEconfig ();
	}

	return true;
    }

    /**
     * Install required packages and remove conflicting packages.
     * Works on the target system.
     * @see FooTarget
     * @param toinstall packages to install
     * @param toremove pacakges to remove
     * @return success?
     */
    define boolean DoInstallAndRemoveAI (list<string> toinstall, list<string> toremove) ``{
	return PackageAI::DoInstallAndRemove(toinstall, toremove);
    }

    /**
     * Install required packages and remove conflicting packages.
     * In both normal and autoyast modes, works on the current system.
     * In autoyast mode, also works on the target system.
     * @see FooTarget
     * @param toinstall packages to install
     * @param toremove pacakges to remove
     * @return success?
     */
    global define boolean DoInstallAndRemove (list<string> toinstall, list<string> toremove) ``{
	boolean ok = true;
	if (Mode::config ())
	{
	    ok = DoInstallAndRemoveAI (toinstall, toremove) && ok;
	}
	ok = DoInstallAndRemovePkg (toinstall, toremove) && ok;
	return ok;
    }

    /**
     * Install required packages and remove conflicting packages.
     * In normal mode, works on the current system.
     * In autoyast mode, works on the target system.
     * @see FooTarget
     * @param toinstall packages to install
     * @param toremove pacakges to remove
     * @return success?
     */
    global define boolean DoInstallAndRemoveTarget (list<string> toinstall, list<string> toremove) ``{
	boolean ok = true;
	if (Mode::config ())
	{
	    ok = DoInstallAndRemoveAI (toinstall, toremove) && ok;
	}
	else
	{
	    ok = DoInstallAndRemovePkg (toinstall, toremove) && ok;
	}
	return ok;
    }

    // ---------- RequireAndConflict* ----------

    /**
     * Ensure that all required packages are installed and no
     * conflicting packages are installed.
     * Works on the current system
     * and the user is presented an
     * explanation and asked to confirm the changes.
     * @see FooTarget
     * @param require Required packages
     * @param conflict Conflicting packages
     * @param help Explanation (rich text). %1 and %2 represent a comma separated list of packages to be installed/removed
     * @return installation successful?
     */
    define boolean RequireAndConflictPkg (
	list<string> require, list<string> conflict, string help,
	string continue_button, string cancel_button ) ``{

	list<string> toinstall = filter (string p, require, ``(!IsPackageInstalledPkg (p)));
	list<string> toremove = filter (string p, conflict, ``(IsPackageInstalledPkg (p)));
	if (toinstall == [] && toremove == [])
	{
	    return true;
	}
	string message = sformat (help,
				  mergestring (toinstall, ", "),
				  mergestring (toremove, ", ")
				  );
	boolean doit = Popup::AnyQuestionRichText (
	    "", message, 40, 10,
	    continue_button, cancel_button,
	    `focus_yes
	    );
	if (doit)
	{
	    last_op_canceled = false;
	    return DoInstallAndRemovePkg (toinstall, toremove);
	}
	last_op_canceled = true;
	return false;
    }

    /**
     * Ensure that all required packages are installed and no
     * conflicting packages are installed.
     * Works on the target system
     * and nothing is displayed.
     * @see FooTarget
     * @param require Required packages
     * @param conflict Conflicting packages
     * @param help Explanation (rich text). %1 and %2 represent a comma separated list of packages to be installed/removed
     * @return installation successful?
     */
    define boolean RequireAndConflictAI ( list<string> require,
					  list<string> conflict)
    ``{
	last_op_canceled = false;
	list<string> toinstall = filter (string p, require, ``(!IsPackageInstalledAI (p)));
	list<string> toremove = filter (string p, conflict, ``(IsPackageInstalledAI (p)));
	if (toinstall == [] && toremove == [])
	{
	    return true;
	}
	else
	{
	    return DoInstallAndRemoveAI (toinstall, toremove);
	}
    }
    /**
     * Like RequireAndConflict but you can specify button texts
     * in place of the default Continue and Cancel
     * @see RequireAndConflict
     * @param continue_button Text which is going to be on the left-hand button
     * @param cancel_button Text which is going to be on the right-hand button
     * @return installation successful?
     */
    global define boolean RequireAndConflictButtons (
	list<string> require, list<string> conflict, string help,
	string continue_button, string cancel_button ) ``{

	boolean ok = true;
	if (Mode::config ())
	{
	    ok = RequireAndConflictAI (require, conflict) && ok;
	}
	ok = RequireAndConflictPkg (require, conflict, help,
				    continue_button, cancel_button) && ok;
	return ok;
    }

    /**
     * Like RequireAndConflictTarget but you can specify button texts
     * in place of the default Continue and Cancel
     * @see RequireAndConflictTarget
     * @param continue_button Text which is going to be on the left-hand button
     * @param cancel_button Text which is going to be on the right-hand button
     * @return installation successful?
     */
    global define boolean RequireAndConflictTargetButtons (
	list<string> require, list<string> conflict, string help,
	string continue_button, string cancel_button ) ``{

	boolean ok = true;
	if (Mode::config ())
	{
	    ok = RequireAndConflictAI (require, conflict) && ok;
	}
	else
	{
	    ok = RequireAndConflictPkg (require, conflict, help,
					continue_button, cancel_button) && ok;
	}
	return ok;
    }

    /**
     * Ensure that all required packages are installed and no
     * conflicting packages are installed.
     * In both normal and autoyast modes, works on the current system.
     * In autoyast mode, also works on the target system.
     * If the current system is affected, the user is presented an
     * explanation and asked to confirm the changes.
     * For the target system, nothing is displayed.
     * @see FooTarget
     * @param require Required packages
     * @param conflict Conflicting packages
     * @param help Explanation (rich text). %1 and %2 represent a comma separated list of packages to be installed/removed
     * @return installation successful?
     */
    global define boolean RequireAndConflict (list<string> require, list<string> conflict, string help) ``{
	return RequireAndConflictButtons (
	    require, conflict, help,
	    Label::ContinueButton (), Label::CancelButton ());
    }

    /**
     * Ensure that all required packages are installed and no
     * conflicting packages are installed.
     * In normal mode, works on the current system
     * and the user is presented an explanation and asked to confirm
     * the changes.
     * In autoyast mode, works on the target system
     * and nothing is displayed.
     * @see FooTarget
     * @param require Required packages
     * @param conflict Conflicting packages
     * @param help Explanation (rich text). %1 and %2 represent a comma separated list of packages to be installed/removed
     * @return installation successful?
     */
    global define boolean RequireAndConflictTarget (list<string> require, list<string> conflict, string help) ``{
	return RequireAndConflictTargetButtons (
	    require, conflict, help,
	    Label::ContinueButton (), Label::CancelButton ());
    }

}
