# User creation text spoke
#
# Copyright (C) 2013-2014  Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
from pyanaconda.core.configuration.anaconda import conf
from pyanaconda.core.constants import FIRSTBOOT_ENVIRON, PASSWORD_SET, PASSWORD_POLICY_USER
from pyanaconda.flags import flags
from pyanaconda.core.i18n import N_, _
from pyanaconda.core.regexes import GECOS_VALID
from pyanaconda.modules.common.constants.services import USERS
from pyanaconda.modules.common.util import is_module_available
from pyanaconda.ui.categories.user_settings import UserSettingsCategory
from pyanaconda.ui.common import FirstbootSpokeMixIn
from pyanaconda.ui.tui.spokes import NormalTUISpoke
from pyanaconda.ui.tui.tuiobject import Dialog, PasswordDialog, report_if_failed, report_check_func
from pyanaconda.ui.lib.users import get_user_list, set_user_list
from pyanaconda.core.users import guess_username, check_username, check_grouplist

from simpleline.render.screen import InputState
from simpleline.render.containers import ListColumnContainer
from simpleline.render.widgets import CheckboxWidget, EntryWidget

__all__ = ["UserSpoke"]


FULLNAME_ERROR_MSG = N_("Full name can't contain the ':' character")


class UserSpoke(FirstbootSpokeMixIn, NormalTUISpoke):
    """
       .. inheritance-diagram:: UserSpoke
          :parts: 3
    """
    category = UserSettingsCategory

    @staticmethod
    def get_screen_id():
        """Return a unique id of this UI screen."""
        return "user-configuration"

    @classmethod
    def should_run(cls, environment, data):
        """Should the spoke run?"""
        if not is_module_available(USERS):
            return False

        if FirstbootSpokeMixIn.should_run(environment, data):
            return True

        # the user spoke should run always in the anaconda and in firstboot only
        # when doing reconfig or if no user has been created in the installation
        users_module = USERS.get_proxy()
        user_list = get_user_list(users_module)
        if environment == FIRSTBOOT_ENVIRON and data and not user_list:
            return True

        return False

    def __init__(self, data, storage, payload):
        FirstbootSpokeMixIn.__init__(self)
        NormalTUISpoke.__init__(self, data, storage, payload)

        self.initialize_start()

        # connect to the Users DBus module
        self._users_module = USERS.get_proxy()

        self.title = N_("User creation")
        self._container = None

        # was user creation requested by the Users DBus module
        # - at the moment this basically means user creation was
        #   requested via kickstart
        # - note that this does not currently update when user
        #   list is changed via DBus
        self._user_requested = False
        self._user_cleared = False

        # should a user be created ?
        self._create_user = False

        self._user_list = get_user_list(self._users_module, add_default=True)
        # if user has a name, it's an actual user that has been requested,
        # rather than a default user added by us
        if self.user.name:
            self._user_requested = True
            self._create_user = True

        self._use_password = self.user.is_crypted or self.user.password
        self._groups = ""
        self._is_admin = False
        self.errors = []

        self._users_module = USERS.get_proxy()

        self.initialize_done()

    @property
    def user(self):
        """The user that is manipulated by the User spoke.

        This user is always the first one in the user list.

        :return: a UserData instance
        """
        return self._user_list[0]

    def refresh(self, args=None):
        NormalTUISpoke.refresh(self, args)

        # refresh the user list
        self._user_list = get_user_list(self._users_module, add_default=True, add_if_not_empty=self._user_cleared)

        self._is_admin = self.user.has_admin_priviledges()
        self._groups = ", ".join(self.user.groups)

        self._container = ListColumnContainer(1)

        w = CheckboxWidget(title=_("Create user"), completed=self._create_user)
        self._container.add(w, self._set_create_user)

        if self._create_user:
            dialog = Dialog(title=_("Full name"), conditions=[self._check_fullname])
            self._container.add(EntryWidget(dialog.title, self.user.gecos), self._set_fullname, dialog)

            dialog = Dialog(title=_("User name"), conditions=[self._check_username])
            self._container.add(EntryWidget(dialog.title, self.user.name), self._set_username, dialog)

            w = CheckboxWidget(title=_("Use password"), completed=self._use_password)
            self._container.add(w, self._set_use_password)

            if self._use_password:
                password_dialog = PasswordDialog(
                    title=_("Password"),
                    policy_name=PASSWORD_POLICY_USER
                )
                password_dialog.username = self.user.name
                if self.user.password:
                    entry = EntryWidget(password_dialog.title, _(PASSWORD_SET))
                else:
                    entry = EntryWidget(password_dialog.title)

                self._container.add(entry, self._set_password, password_dialog)

            msg = _("Administrator")
            w = CheckboxWidget(title=msg, completed=self._is_admin)
            self._container.add(w, self._set_administrator)

            dialog = Dialog(title=_("Groups"), conditions=[self._check_groups])
            self._container.add(EntryWidget(dialog.title, self._groups), self._set_groups, dialog)

        self.window.add_with_separator(self._container)

    @report_if_failed(message=FULLNAME_ERROR_MSG)
    def _check_fullname(self, user_input, report_func):
        return GECOS_VALID.match(user_input) is not None

    @report_check_func()
    def _check_username(self, user_input, report_func):
        return check_username(user_input)

    @report_check_func()
    def _check_groups(self, user_input, report_func):
        return check_grouplist(user_input)

    def _set_create_user(self, args):
        self._create_user = not self._create_user

    def _set_fullname(self, dialog):
        self.user.gecos = dialog.run()

    def _set_username(self, dialog):
        self.user.name = dialog.run()

    def _set_use_password(self, args):
        self._use_password = not self._use_password

    def _set_password(self, password_dialog):
        password = password_dialog.run()

        while password is None:
            password = password_dialog.run()

        self.user.password = password

    def _set_administrator(self, args):
        self._is_admin = not self._is_admin

    def _set_groups(self, dialog):
        self._groups = dialog.run()

    def show_all(self):
        NormalTUISpoke.show_all(self)
        # if we have any errors, display them
        while self.errors:
            print(self.errors.pop())

    @property
    def completed(self):
        """ Verify a user is created; verify pw is set if option checked. """
        user_list = get_user_list(self._users_module)
        if user_list:
            if self._use_password and not bool(self.user.password or self.user.is_crypted):
                return False
            else:
                return True
        else:
            return False

    @property
    def showable(self):
        return not (self.completed and flags.automatedInstall
                    and self._user_requested and not conf.ui.can_change_users)

    @property
    def mandatory(self):
        """The spoke is mandatory only if some input is missing.

        Possible reasons to be mandatory:
        - No admin user has been created
        - Password has been requested but not entered
        """
        return (not self._users_module.CheckAdminUserExists() or
                (self._use_password and not bool(self.user.password or
                                                 self.user.is_crypted)))

    @property
    def status(self):
        user_list = get_user_list(self._users_module)
        if not user_list:
            return _("No user will be created")
        elif self._use_password and not bool(self.user.password or self.user.is_crypted):
            return _("You must set a password")
        elif user_list[0].has_admin_priviledges():
            return _("Administrator %s will be created") % user_list[0].name
        else:
            return _("User %s will be created") % user_list[0].name

    def input(self, args, key):
        if self._container.process_user_input(key):
            self.apply()
            return InputState.PROCESSED_AND_REDRAW

        return super().input(args, key)

    def apply(self):
        if self.user.gecos and not self.user.name:
            username = guess_username(self.user.gecos)
            valid, msg = check_username(username)
            if not valid:
                self.errors.append(_("Invalid user name: %(name)s.\n%(error_message)s")
                                   % {"name": username, "error_message": msg})
            else:
                self.user.name = guess_username(self.user.gecos)

        self.user.groups = [g.strip() for g in self._groups.split(",") if g]

        # Add or remove user admin status
        self.user.set_admin_priviledges(self._is_admin)

        # encrypt and store password only if user entered anything; this should
        # preserve passwords set via kickstart
        if self._use_password and self.user.password and len(self.user.password) > 0:
            self.user.password = self.user.password
            self.user.is_crypted = True
        # clear pw when user unselects to use pw
        else:
            self.user.password = ""
            self.user.is_crypted = False

        # Turning user creation off clears any already configured user,
        # regardless of origin (kickstart, user, DBus).
        if not self._create_user and self.user.name:
            self.user.name = ""
            self._user_cleared = True
        # An the other hand, if we have a user with name set,
        # it is valid and should be used if the spoke is re-visited.
        if self.user.name:
            self._user_cleared = False
        # Set the user list while removing any unset users, where unset
        # means the user has nema == "".
        set_user_list(self._users_module, self._user_list, remove_unset=True)
