#
# text.py - text mode frontend to anaconda
#
# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006  Red Hat, Inc.
# All rights reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Author(s): Erik Troan <ewt@redhat.com>
#            Matt Wilson <msw@redhat.com>
#

from snack import *
import sys
import os
import isys
import iutil
import time
import signal
import parted
import product
import string
from language import expandLangs
from flags import flags
from constants_text import *
from constants import *
from network import hasActiveNetDev
from installinterfacebase import InstallInterfaceBase
import imputil

import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
P_ = lambda x, y, z: gettext.ldngettext("anaconda", x, y, z)

import logging
log = logging.getLogger("anaconda")

stepToClasses = {
    "language" : ("language_text", "LanguageWindow"),
    "keyboard" : ("keyboard_text", "KeyboardWindow"),
    "welcome" : ("welcome_text", "WelcomeWindow"),
    "parttype" : ("partition_text", "PartitionTypeWindow"),
    "addswap" : ("upgrade_text", "UpgradeSwapWindow"),
    "upgrademigratefs" : ("upgrade_text", "UpgradeMigrateFSWindow"),
    "zfcpconfig": ("zfcp_text", ("ZFCPWindow")),
    "findinstall" : ("upgrade_text", ("UpgradeExamineWindow")),
    "upgbootloader": ("upgrade_bootloader_text", "UpgradeBootloaderWindow"),
    "network" : ("network_text", ("HostnameWindow")),
    "timezone" : ("timezone_text", "TimezoneWindow"),
    "accounts" : ("userauth_text", "RootPasswordWindow"),
    "useraccounts" : ("userauth_text", "UserPasswordWindow"),
    "tasksel": ("task_text", "TaskWindow"),
    "install" : ("progress_text", "setupForInstall"),
    "complete" : ("complete_text", "FinishedWindow"),
}

if iutil.isS390():
    stepToClasses["bootloader"] = ("zipl_text", ( "ZiplWindow"))

class InstallWindow:
    def __call__ (self, screen):
        raise RuntimeError, "Unimplemented screen"

class WaitWindow:
    def pop(self):
	self.screen.popWindow()
	self.screen.refresh()

    def refresh(self):
        pass

    def __init__(self, screen, title, text):
	self.screen = screen
	width = 40
	if (len(text) < width): width = len(text)

	t = TextboxReflowed(width, text)

	g = GridForm(self.screen, title, 1, 1)
	g.add(t, 0, 0)
	g.draw()
	self.screen.refresh()

class OkCancelWindow:
    def getrc(self):
	return self.rc

    def __init__(self, screen, title, text):
	rc = ButtonChoiceWindow(screen, title, text,
			        buttons=[TEXT_OK_BUTTON, _("Cancel")])
	if rc == string.lower(_("Cancel")):
	    self.rc = 1
	else:
	    self.rc = 0

class ProgressWindow:
    def pop(self):
	self.screen.popWindow()
	self.screen.refresh()
        del self.scale
        self.scale = None

    def pulse(self):
        pass

    def set(self, amount):
        self.scale.set(int(float(amount) * self.multiplier))
        self.screen.refresh()

    def refresh(self):
        pass

    def __init__(self, screen, title, text, total, updpct = 0.05, pulse = False):
        self.multiplier = 1
        if total == 1.0:
            self.multiplier = 100
	self.screen = screen
	width = 55
	if (len(text) > width): width = len(text)

	t = TextboxReflowed(width, text)

	g = GridForm(self.screen, title, 1, 2)
	g.add(t, 0, 0, (0, 0, 0, 1), anchorLeft=1)

        self.scale = Scale(int(width), int(float(total) * self.multiplier))
        if not pulse:
            g.add(self.scale, 0, 1)
                
	g.draw()
	self.screen.refresh()

class LuksPassphraseWindow:
    def __init__(self, screen, passphrase = "", preexist = False):
        self.screen = screen
        self.passphrase = passphrase
        self.minLength = 8
        self.preexist = preexist
        self.txt = _("Choose a passphrase for the encrypted devices. You "
                     "will be prompted for this passphrase during system boot.")
        self.rc = None

    def run(self):
        toplevel = GridForm(self.screen, _("Passphrase for encrypted device"),
                            1, 5)

        txt = TextboxReflowed(65, self.txt)
        toplevel.add(txt, 0, 0)

        passphraseentry = Entry(60, password = 1)
        toplevel.add(passphraseentry, 0, 1, (0,0,0,1))

        confirmentry = Entry(60, password = 1)
        toplevel.add(confirmentry, 0, 2, (0,0,0,1))

        if self.preexist:
            globalcheckbox = Checkbox(_("Also add this passphrase to all existing encrypted devices"), isOn = True)
            toplevel.add(globalcheckbox, 0, 3)

        buttons = ButtonBar(self.screen, [TEXT_OK_BUTTON, TEXT_CANCEL_BUTTON])
        toplevel.add(buttons, 0, 4, growx=1)

        passphraseentry.set(self.passphrase)
        confirmentry.set(self.passphrase)

        while True:
            rc = toplevel.run()
            res = buttons.buttonPressed(rc)

            passphrase = None
            if res == TEXT_OK_CHECK or rc == "F12":
                passphrase = passphraseentry.value()
                confirm = confirmentry.value()

                if passphrase != confirm:
                    ButtonChoiceWindow(self.screen,
                                       _("Error with passphrase"),
                                       _("The passphrases you entered were "
                                         "different.  Please try again."),
                                       buttons=[TEXT_OK_BUTTON])
                    passphraseentry.set("")
                    confirmentry.set("")
                    continue

                if len(passphrase) < self.minLength:
                    ButtonChoiceWindow(self.screen,
                                       _("Error with passphrase"),
                                       P_("The passphrase must be at least "
                                          "%d character long.",
                                          "The passphrase must be at least "
                                          "%d characters long.",
                                          self.minLength)
                                         % (self.minLength,),
                                       buttons=[TEXT_OK_BUTTON])
                    passphraseentry.set("")
                    confirmentry.set("")
                    continue
            else:
                passphrase = self.passphrase
                passphraseentry.set(self.passphrase)
                confirmentry.set(self.passphrase)

            retrofit = False
            if self.preexist:
                retrofit = globalcheckbox.selected()
            self.rc = passphrase
            return (self.rc, retrofit)

    def pop(self):
        self.screen.popWindow()

class PassphraseEntryWindow:
    def __init__(self, screen, device):
        self.screen = screen
        self.txt = _("Device %s is encrypted. In order to "
                     "access the device's contents during "
                     "installation you must enter the device's "
                     "passphrase below.") % (device,)
        self.rc = None

    def run(self):
        toplevel = GridForm(self.screen, _("Passphrase"), 1, 4)

        txt = TextboxReflowed(65, self.txt)
        toplevel.add(txt, 0, 0)

        passphraseentry = Entry(60, password = 1)
        toplevel.add(passphraseentry, 0, 1, (0,0,0,1))

        globalcheckbox = Checkbox(_("This is a global passphrase"))
        toplevel.add(globalcheckbox, 0, 2)

        buttons = ButtonBar(self.screen, [TEXT_OK_BUTTON, TEXT_CANCEL_BUTTON])
        toplevel.add(buttons, 0, 3, growx=1)

        rc = toplevel.run()
        res = buttons.buttonPressed(rc)

        passphrase = None
        isglobal = False
        if res == TEXT_OK_CHECK:
            passphrase = passphraseentry.value().strip()
            isglobal = globalcheckbox.selected()

        self.rc = (passphrase, isglobal)
        return self.rc

    def pop(self):
        self.screen.popWindow()

class InstallInterface(InstallInterfaceBase):
    def progressWindow(self, title, text, total, updpct = 0.05, pulse = False):
        return ProgressWindow(self.screen, title, text, total, updpct, pulse)

    def setInstallProgressClass(self, c):
        self.instProgress = c

    def exitWindow(self, title, text):
        return self.messageWindow(title, text, type="custom",
                                  custom_buttons=[_("Exit installer")])

    def messageWindow(self, title, text, type="ok", default = None,
		      custom_icon=None, custom_buttons=[]):
	if type == "ok":
	    ButtonChoiceWindow(self.screen, title, text,
			       buttons=[TEXT_OK_BUTTON])
        elif type == "yesno":
            if default and default == "no":
                btnlist = [TEXT_NO_BUTTON, TEXT_YES_BUTTON]
            else:
                btnlist = [TEXT_YES_BUTTON, TEXT_NO_BUTTON]
	    rc = ButtonChoiceWindow(self.screen, title, text,
			       buttons=btnlist)
            if rc == "yes":
                return 1
            else:
                return 0
	elif type == "custom":
	    tmpbut = []
	    for but in custom_buttons:
		tmpbut.append(string.replace(but,"_",""))

	    rc = ButtonChoiceWindow(self.screen, title, text, width=60,
				    buttons=tmpbut)

	    idx = 0
	    for b in tmpbut:
		if string.lower(b) == rc:
		    return idx
		idx = idx + 1
	    return 0
	else:
	    return OkCancelWindow(self.screen, title, text)

    def detailedMessageWindow(self, title, text, longText=None, type="ok",
                              default=None, custom_icon=None,
                              custom_buttons=[]):
        t = TextboxReflowed(60, text, maxHeight=8)
        lt = Textbox(60, 6, longText, scroll=1, wrap=1)
        g = GridFormHelp(self.screen, title, help, 1, 3)
        g.add(t, 0, 0)
        g.add(lt, 0, 1, padding = (0, 1, 0, 1))

        if type == "ok":
            bb = ButtonBar(self.screen, [TEXT_OK_BUTTON])
            g.add(bb, 0, 2, growx = 1)
            return bb.buttonPressed(g.runOnce(None, None))
        elif type == "yesno":
            if default and default == "no":
                buttons = [TEXT_NO_BUTTON, TEXT_YES_BUTTON]
            else:
                buttons = [TEXT_YES_BUTTON, TEXT_NO_BUTTON]

            bb = ButtonBar(self.screen, buttons)
            g.add(bb, 0, 2, growx = 1)
            rc = bb.buttonPressed(g.runOnce(None, None))

            if rc == "yes":
                return 1
            else:
                return 0
        elif type == "custom":
            buttons = []
            idx = 0

            for button in custom_buttons:
                buttons.append(string.replace(button, "_", ""))

            bb = ButtonBar(self.screen, buttons)
            g.add(bb, 0, 2, growx = 1)
            rc = bb.buttonPressed(g.runOnce(None, None))

            for b in buttons:
                if string.lower(b) == rc:
                    return idx
                idx += 1

            return 0
        else:
            return self.messageWindow(title, text, type, default, custom_icon,
                                      custom_buttons)

    def createRepoWindow(self):
        self.messageWindow(_("Error"),
                           _("Repository editing is not available in text mode."))

    def editRepoWindow(self, repoObj):
        self.messageWindow(_("Error"),
                           _("Repository editing is not available in text mode."))

    def entryWindow(self, title, text, prompt, entrylength = None):
        (res, value) = EntryWindow(self.screen, title, text, [prompt])
        if res == "cancel":
            return None
        r = value[0]
        r.strip()
        return r

    def getLuksPassphrase(self, passphrase = "", preexist = False):
        w = LuksPassphraseWindow(self.screen, passphrase = passphrase,
                                 preexist = preexist)
        rc = w.run()
        w.pop()
        return rc

    def passphraseEntryWindow(self, device):
        w = PassphraseEntryWindow(self.screen, device)
        (passphrase, isglobal) = w.run()
        w.pop()
        return (passphrase, isglobal)

    def enableNetwork(self):
        if len(self.anaconda.network.netdevices) == 0:
            return False
        from netconfig_text import NetworkConfiguratorText
        w = NetworkConfiguratorText(self.screen, self.anaconda)
        ret = w.run()
        return ret != INSTALL_BACK

    def kickstartErrorWindow(self, text):
        s = _("The following error was found while parsing the "
              "kickstart configuration file:\n\n%s") %(text,)
        self.messageWindow(_("Error Parsing Kickstart Config"),
                           s,
                           type = "custom",
                           custom_buttons = [("_Reboot")],
                           custom_icon="error")
                           
    def mainExceptionWindow(self, shortText, longTextFile):
        from meh.ui.text import MainExceptionWindow
        log.critical(shortText)
        exnWin = MainExceptionWindow(shortText, longTextFile, screen=self.screen)
        return exnWin

    def saveExceptionWindow(self, accountManager, signature, *args, **kwargs):
        from meh.ui.text import SaveExceptionWindow
        win = SaveExceptionWindow (accountManager, signature, screen=self.screen,
                                   *args, **kwargs)
        win.run()

    def waitWindow(self, title, text):
	return WaitWindow(self.screen, title, text)

    def beep(self):
        # no-op.  could call newtBell() if it was bound
        pass

    def drawFrame(self):
        self.screen.drawRootText (0, 0, self.screen.width * " ")
        if productArch:
          self.screen.drawRootText (0, 0, _("Welcome to %(productName)s for %(productArch)s") % {'productName': productName, 'productArch': productArch})
        else:
          self.screen.drawRootText (0, 0, _("Welcome to %s") % productName)

        self.screen.pushHelpLine(_("  <Tab>/<Alt-Tab> between elements   |  <Space> selects   |  <F12> next screen"))

    def setScreen(self, screen):
        self.screen = screen

    def shutdown(self):
	self.screen.finish()
	self.screen = None

    def suspend(self):
        self.screen.suspend()

    def resume(self):
        self.screen.resume()

    def __init__(self):
	InstallInterfaceBase.__init__(self)
	signal.signal(signal.SIGINT, signal.SIG_IGN)
	signal.signal(signal.SIGTSTP, signal.SIG_IGN)
	self.screen = SnackScreen()
        self.instProgress = None
        self._initLabelAnswers = {}
        self._inconsistentLVMAnswers = {}

    def __del__(self):
	if self.screen:
	    self.screen.finish()

    def isRealConsole(self):
        """Returns True if this is a _real_ console that can do things, False
        for non-real consoles such as serial, i/p virtual consoles or xen."""
        if flags.serial or flags.virtpconsole:
            return False
        if isys.isPseudoTTY(0):
            return False
        if isys.isVioConsole():
            return False
        return True

    def resetInitializeDiskQuestion(self):
        self._initLabelAnswers = {}

    def questionInitializeDisk(self, path, description, size, details=""):

        retVal = False # The less destructive default

        if not path:
            return retVal

        # we are caching answers so that we don't
        # ask in each storage.reset() again
        if path in self._initLabelAnswers:
            log.info("UI not asking about disk initialization, "
                     "using cached answer: %s" % self._initLabelAnswers[path])
            return self._initLabelAnswers[path]
        elif "all" in self._initLabelAnswers:
            log.info("UI not asking about disk initialization, "
                     "using cached answer: %s" % self._initLabelAnswers["all"])
            return self._initLabelAnswers["all"]

        rc = self.messageWindow(_("Warning"),
                _("Error processing drive:\n\n"
                  "%(path)s\n%(size)-0.fMB\n%(description)s\n\n"
                  "This device may need to be reinitialized.\n\n"
                  "REINITIALIZING WILL CAUSE ALL DATA TO BE LOST!\n\n"
                  "This action may also be applied to all other disks "
                  "needing reinitialization.%(details)s")
                % {'path': path, 'size': size,
                   'description': description, 'details': details},
                type="custom",
                custom_buttons = [ _("_Ignore"),
                                   _("Ignore _all"),
                                   _("_Re-initialize"),
                                   _("Re-ini_tialize all") ],
                custom_icon="question")
        if rc == 0:
            retVal = False
        elif rc == 1:
            path = "all"
            retVal = False
        elif rc == 2:
            retVal = True
        elif rc == 3:
            path = "all"
            retVal = True

        self._initLabelAnswers[path] = retVal
        return retVal

    def resetReinitInconsistentLVMQuestion(self):
        self._inconsistentLVMAnswers = {}

    def questionReinitInconsistentLVM(self, pv_names=None, lv_name=None, vg_name=None):

        retVal = False # The less destructive default
        allSet = frozenset(["all"])

        if not pv_names or (lv_name is None and vg_name is None):
            return retVal

        # We are caching answers so that we don't ask for ignoring
        # in each storage.reset() again (note that reinitialization is
        # done right after confirmation in dialog, not as a planned
        # action).
        key = frozenset(pv_names)
        if key in self._inconsistentLVMAnswers:
            log.info("UI not asking about disk initialization, "
                     "using cached answer: %s" % self._inconsistentLVMAnswers[key])
            return self._inconsistentLVMAnswers[key]
        elif allSet in self._inconsistentLVMAnswers:
            log.info("UI not asking about disk initialization, "
                     "using cached answer: %s" % self._inconsistentLVMAnswers[allSet])
            return self._inconsistentLVMAnswers[allSet]

        if vg_name is not None:
            message = "Volume Group %s" % vg_name
        elif lv_name is not None:
            message = "Logical Volume %s" % lv_name

        na = {'msg': message, 'pvs': ", ".join(pv_names)}
        rc = self.messageWindow(_("Warning"),
                  _("Error processing LVM.\n"
                    "There is inconsistent LVM data on %(msg)s.  You can "
                    "reinitialize all related PVs (%(pvs)s) which will erase "
                    "the LVM metadata, or ignore which will preserve the "
                    "contents.  This action may also be applied to all other "
                    "PVs with inconsistent metadata.") % na,
                type="custom",
                custom_buttons = [ _("_Ignore"),
                                   _("Ignore _all"),
                                   _("_Re-initialize"),
                                   _("Re-ini_tialize all") ],
                custom_icon="question")
        if rc == 0:
            retVal = False
        elif rc == 1:
            key = allSet
            retVal = False
        elif rc == 2:
            retVal = True
        elif rc == 3:
            key = allSet
            retval = True

        self._inconsistentLVMAnswers[key] = retVal
        return retVal

    def run(self, anaconda):
        self.anaconda = anaconda
        instLang = anaconda.instLanguage

        if instLang.getFontFile(instLang.instLang) == "none":
            if not anaconda.ksdata:
                ButtonChoiceWindow(self.screen, "Language Unavailable",
                                   "%s display is unavailable in text mode.  "
                                   "The installation will continue in "
                                   "English." % (instLang.instLang,),
                                   buttons=[TEXT_OK_BUTTON])

	if not self.isRealConsole():
	    self.screen.suspendCallback(spawnShell, self.screen)

        # drop into the python debugger on ctrl-z if we're running in test mode
        if flags.debug:
            self.screen.suspendCallback(debugSelf, self.screen)

        # draw the frame after setting up the fallback
        self.drawFrame()

	lastrc = INSTALL_OK
	(step, instance) = anaconda.dispatch.currentStep()
	while step:
	    (file, classNames) = stepToClasses[step]

	    if type(classNames) != type(()):
		classNames = (classNames,)

	    if lastrc == INSTALL_OK:
		step = 0
	    else:
		step = len(classNames) - 1

	    while step >= 0 and step < len(classNames):
                # reget the args.  they could change (especially direction)
                (foo, args) = anaconda.dispatch.currentStep()
                nextWindow = None

                while 1:
                    try:
                        found = imputil.imp.find_module(file)
                        loaded = imputil.imp.load_module(classNames[step],
                                                         found[0], found[1],
                                                         found[2])
                        nextWindow = loaded.__dict__[classNames[step]]
                        break
                    except ImportError, e:
                        rc = ButtonChoiceWindow(self.screen, _("Error!"),
                                          _("An error occurred when attempting "
                                            "to load an installer interface "
                                            "component.\n\nclassName = %s")
                                          % (classNames[step],),
                                          buttons=[_("Exit"), _("Retry")])

                        if rc == string.lower(_("Exit")):
                            sys.exit(0)

		win = nextWindow()

		#log.info("TUI running step %s (class %s, file %s)" % 
			 #(step, file, classNames))

                rc = win(self.screen, instance)

		if rc == INSTALL_NOOP:
		    rc = lastrc

		if rc == INSTALL_BACK:
		    step = step - 1
                    anaconda.dispatch.dir = DISPATCH_BACK
		elif rc == INSTALL_OK:
		    step = step + 1
                    anaconda.dispatch.dir = DISPATCH_FORWARD

		lastrc = rc

	    if step == -1:
                if not anaconda.dispatch.canGoBack():
                    ButtonChoiceWindow(self.screen, _("Cancelled"),
                                       _("I can't go to the previous step "
                                         "from here. You will have to try "
                                         "again."),
                                       buttons=[_("OK")])
		anaconda.dispatch.gotoPrev()
	    else:
		anaconda.dispatch.gotoNext()

	    (step, args) = anaconda.dispatch.currentStep()

        self.screen.finish()

    def setSteps(self, anaconda):
        anaconda.dispatch.skipStep("filtertype", permanent=1)
        anaconda.dispatch.skipStep("filter", permanent=1)
        anaconda.dispatch.skipStep("cleardiskssel", permanent=1)
        anaconda.dispatch.skipStep("group-selection", permanent=1)

def killSelf(screen):
    screen.finish()
    os._exit(0)

def debugSelf(screen):
    screen.suspend()
    import pdb
    try:
        pdb.set_trace()
    except:
        sys.exit(-1)
    screen.resume()

def spawnShell(screen):
    screen.suspend()
    print("\n\nType <exit> to return to the install program.\n")
    if os.path.exists("/bin/sh"):
        iutil.execConsole()
    else:
        print("Unable to find /bin/sh to execute!  Not starting shell")
    time.sleep(5)
    screen.resume()
