/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
//         www.projectforge.org
//
// Copyright (C) 2001-2014 Kai Reinhard ([email protected])
//
// ProjectForge is dual-licensed.
//
// This community edition 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; version 3 of the License.
//
// This community edition 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/.
//
/////////////////////////////////////////////////////////////////////////////

package org.projectforge.web.user;

import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.PasswordTextField;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.form.validation.IFormValidator;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.ResourceModel;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.apache.wicket.validation.IValidatable;
import org.apache.wicket.validation.ValidationError;
import org.apache.wicket.validation.validator.AbstractValidator;
import org.projectforge.access.AccessChecker;
import org.projectforge.common.StringHelper;
import org.projectforge.common.TimeNotation;
import org.projectforge.core.ConfigXml;
import org.projectforge.core.Configuration;
import org.projectforge.ldap.LdapPosixAccountsUtils;
import org.projectforge.ldap.LdapSambaAccountsConfig;
import org.projectforge.ldap.LdapSambaAccountsUtils;
import org.projectforge.ldap.LdapUserDao;
import org.projectforge.ldap.LdapUserValues;
import org.projectforge.ldap.PFUserDOConverter;
import org.projectforge.user.GroupDO;
import org.projectforge.user.Login;
import org.projectforge.user.PFUserContext;
import org.projectforge.user.PFUserDO;
import org.projectforge.user.UserDao;
import org.projectforge.user.UserGroupCache;
import org.projectforge.user.UserRight;
import org.projectforge.user.UserRightDao;
import org.projectforge.user.UserRightVO;
import org.projectforge.user.UserRightValue;
import org.projectforge.web.I18nCore;
import org.projectforge.web.calendar.DateTimeFormatter;
import org.projectforge.web.common.MultiChoiceListHelper;
import org.projectforge.web.wicket.AbstractEditForm;
import org.projectforge.web.wicket.WebConstants;
import org.projectforge.web.wicket.WicketUtils;
import org.projectforge.web.wicket.bootstrap.GridBuilder;
import org.projectforge.web.wicket.bootstrap.GridSize;
import org.projectforge.web.wicket.components.LabelValueChoiceRenderer;
import org.projectforge.web.wicket.components.MaxLengthTextArea;
import org.projectforge.web.wicket.components.MaxLengthTextField;
import org.projectforge.web.wicket.components.MinMaxNumberField;
import org.projectforge.web.wicket.components.RequiredMaxLengthTextField;
import org.projectforge.web.wicket.components.SingleButtonPanel;
import org.projectforge.web.wicket.components.TimeZoneField;
import org.projectforge.web.wicket.flowlayout.DivTextPanel;
import org.projectforge.web.wicket.flowlayout.FieldsetPanel;

import com.vaynberg.wicket.select2.Select2MultiChoice;

public class UserEditForm extends AbstractEditForm<PFUserDO, UserEditPage>
{
  private static final long serialVersionUID = 7872294377838461659L;

  private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(UserEditForm.class);

  private static final String MAGIC_PASSWORD = "******";

  @SpringBean(name = "accessChecker")
  private AccessChecker accessChecker;

  @SpringBean(name = "userRightDao")
  private UserRightDao userRightDao;

  @SpringBean(name = "userGroupCache")
  private UserGroupCache userGroupCache;

  protected UserRightsEditData rightsData;

  private String password;

  @SuppressWarnings("unused")
  private String passwordRepeat;

  private PFUserDO passwordUser;

  boolean invalidateAllStayLoggedInSessions;

  MultiChoiceListHelper<GroupDO> assignListHelper;

  LdapUserValues ldapUserValues;

  private TextField< ? > usernameTextField;

  private MinMaxNumberField<Integer> uidNumberField;

  private MinMaxNumberField<Integer> gidNumberField;

  private MaxLengthTextField homeDirectoryField;

  private MaxLengthTextField loginShellField;

  private MinMaxNumberField<Integer> sambaSIDNumberField;

  private MinMaxNumberField<Integer> sambaPrimaryGroupSIDNumberField;

  public UserEditForm(final UserEditPage parentPage, final PFUserDO data)
  {
    super(parentPage, data);
  }

  public static void createFirstName(final GridBuilder gridBuilder, final PFUserDO user)
  {
    // First name
    final FieldsetPanel fs = gridBuilder.newFieldset(gridBuilder.getString("firstName"));
    final RequiredMaxLengthTextField firstName = new RequiredMaxLengthTextField(fs.getTextFieldId(), new PropertyModel<String>(user,
        "firstname"));
    WicketUtils.setStrong(firstName);
    fs.add(firstName);
  }

  public static void createLastName(final GridBuilder gridBuilder, final PFUserDO user)
  {
    // Last name
    final FieldsetPanel fs = gridBuilder.newFieldset(gridBuilder.getString("name"));
    final RequiredMaxLengthTextField name = new RequiredMaxLengthTextField(fs.getTextFieldId(), new PropertyModel<String>(user, "lastname"));
    WicketUtils.setStrong(name);
    fs.add(name);
  }

  public static void createOrganization(final GridBuilder gridBuilder, final PFUserDO user)
  {
    // Organization
    final FieldsetPanel fs = gridBuilder.newFieldset(gridBuilder.getString("organization"));
    fs.add(new MaxLengthTextField(fs.getTextFieldId(), new PropertyModel<String>(user, "organization")));
  }

  public static void createEMail(final GridBuilder gridBuilder, final PFUserDO user)
  {
    // E-Mail
    final FieldsetPanel fs = gridBuilder.newFieldset(gridBuilder.getString("email"));
    fs.add(new MaxLengthTextField(fs.getTextFieldId(), new PropertyModel<String>(user, "email")));
  }

  @SuppressWarnings("serial")
  public static void createAuthenticationToken(final GridBuilder gridBuilder, final PFUserDO user, final UserDao userDao,
      final Form< ? > form)
  {
    // Authentication token
    final FieldsetPanel fs = gridBuilder.newFieldset(gridBuilder.getString("user.authenticationToken")).suppressLabelForWarning();
    fs.add(new DivTextPanel(fs.newChildId(), new Model<String>() {
      @Override
      public String getObject()
      {
        if (PFUserContext.getUserId().equals(user.getId()) == true) {
          return userDao.getAuthenticationToken(user.getId());
        } else {
          // Administrators shouldn't see the token.
          return "*****";
        }
      }
    }));
    fs.addHelpIcon(gridBuilder.getString("user.authenticationToken.tooltip"));
    final Button button = new Button(SingleButtonPanel.WICKET_ID, new Model<String>("renewAuthenticationKey")) {
      @Override
      public final void onSubmit()
      {
        userDao.renewAuthenticationToken(user.getId());
        form.error(getString("user.authenticationToken.renew.successful"));
      }
    };
    button.add(WicketUtils.javaScriptConfirmDialogOnClick(form.getString("user.authenticationToken.renew.securityQuestion")));
    fs.add(new SingleButtonPanel(fs.newChildId(), button, gridBuilder.getString("user.authenticationToken.renew"), SingleButtonPanel.DANGER));
    WicketUtils.addTooltip(button, gridBuilder.getString("user.authenticationToken.renew.tooltip"));
  }

  public static void createJIRAUsername(final GridBuilder gridBuilder, final PFUserDO user)
  {
    // JIRA user name
    final FieldsetPanel fs = gridBuilder.newFieldset(gridBuilder.getString("user.jiraUsername"));
    fs.add(new MaxLengthTextField(fs.getTextFieldId(), new PropertyModel<String>(user, "jiraUsername")));
    fs.addHelpIcon(gridBuilder.getString("user.jiraUsername.tooltip"));
  }

  public static void createLastLoginAndDeleteAllStayLogins(final GridBuilder gridBuilder, final PFUserDO user, final UserDao userDao,
      final Form< ? > form)
  {
    // Last login and deleteAllStayLoggedInSessions
    final FieldsetPanel fs = gridBuilder.newFieldset(gridBuilder.getString("login.lastLogin")).suppressLabelForWarning();
    fs.add(new DivTextPanel(fs.newChildId(), DateTimeFormatter.instance().getFormattedDateTime(user.getLastLogin())));
    @SuppressWarnings("serial")
    final Button button = new Button(SingleButtonPanel.WICKET_ID, new Model<String>("invalidateStayLoggedInSessions")) {
      @Override
      public final void onSubmit()
      {
        userDao.renewStayLoggedInKey(user.getId());
        form.error(getString("login.stayLoggedIn.invalidateAllStayLoggedInSessions.successfullDeleted"));
      }
    };
    fs.add(new SingleButtonPanel(fs.newChildId(), button, gridBuilder.getString("login.stayLoggedIn.invalidateAllStayLoggedInSessions"),
        SingleButtonPanel.DANGER));
    WicketUtils.addTooltip(button, gridBuilder.getString("login.stayLoggedIn.invalidateAllStayLoggedInSessions.tooltip"));
  }

  public static void createLocale(final GridBuilder gridBuilder, final PFUserDO user)
  {
    // Locale
    final FieldsetPanel fs = gridBuilder.newFieldset(gridBuilder.getString("user.locale"));
    final LabelValueChoiceRenderer<Locale> localeChoiceRenderer = new LabelValueChoiceRenderer<Locale>();
    localeChoiceRenderer.addValue(null, gridBuilder.getString("user.defaultLocale"));
    for (final String str : I18nCore.LOCALIZATIONS) {
      localeChoiceRenderer.addValue(new Locale(str), gridBuilder.getString("locale." + str));
    }
    @SuppressWarnings("serial")
    final DropDownChoice<Locale> localeChoice = new DropDownChoice<Locale>(fs.getDropDownChoiceId(), new PropertyModel<Locale>(user,
        "locale"), localeChoiceRenderer.getValues(), localeChoiceRenderer) {
      /**
       * @see org.apache.wicket.markup.html.form.AbstractSingleSelectChoice#getDefaultChoice(java.lang.String)
       */
      @Override
      protected CharSequence getDefaultChoice(final String selectedValue)
      {
        return "";
      }

      @Override
      protected Locale convertChoiceIdToChoice(final String id)
      {
        if (StringHelper.isIn(id, I18nCore.LOCALIZATIONS) == true) {
          return new Locale(id);
        } else {
          return null;
        }
      }
    };
    fs.add(localeChoice);
  }

  public static void createLanguage(final GridBuilder gridBuilder, final PFUserDO user)
  {
  }

  public static void createDateFormat(final GridBuilder gridBuilder, final PFUserDO user)
  {
    addDateFormatCombobox(gridBuilder, user, "dateFormat", "dateFormat", Configuration.getInstance().getDateFormats(), false);
  }

  public static void createExcelDateFormat(final GridBuilder gridBuilder, final PFUserDO user)
  {
    addDateFormatCombobox(gridBuilder, user, "dateFormat.xls", "excelDateFormat", Configuration.getInstance().getExcelDateFormats(), true);
  }

  public static void createTimeNotation(final GridBuilder gridBuilder, final PFUserDO user)
  {
    // Time notation
    final FieldsetPanel fs = gridBuilder.newFieldset(gridBuilder.getString("timeNotation"));
    final LabelValueChoiceRenderer<TimeNotation> timeNotationChoiceRenderer = new LabelValueChoiceRenderer<TimeNotation>();
    timeNotationChoiceRenderer.addValue(TimeNotation.H12, gridBuilder.getString("timeNotation.12"));
    timeNotationChoiceRenderer.addValue(TimeNotation.H24, gridBuilder.getString("timeNotation.24"));
    final DropDownChoice<TimeNotation> timeNotationChoice = new DropDownChoice<TimeNotation>(fs.getDropDownChoiceId(),
        new PropertyModel<TimeNotation>(user, "timeNotation"), timeNotationChoiceRenderer.getValues(), timeNotationChoiceRenderer);
    timeNotationChoice.setNullValid(true);
    fs.add(timeNotationChoice);
  }

  public static void createTimeZone(final GridBuilder gridBuilder, final PFUserDO user)
  {
    // Time zone
    final FieldsetPanel fs = gridBuilder.newFieldset(gridBuilder.getString("timezone"));
    final TimeZoneField timeZone = new TimeZoneField(fs.getTextFieldId(), new PropertyModel<TimeZone>(user, "timeZoneObject"));
    fs.addKeyboardHelpIcon(gridBuilder.getString("tooltip.autocomplete.timeZone"));
    fs.add(timeZone);
  }

  /**
   * If no telephone system url is set in config.xml nothing will be done.
   * @param gridBuilder
   * @param user
   */
  public static void createPhoneIds(final GridBuilder gridBuilder, final PFUserDO user)
  {
    if (StringUtils.isNotEmpty(ConfigXml.getInstance().getTelephoneSystemUrl()) == true) {
      // Personal phone identifiers
      final FieldsetPanel fs = gridBuilder.newFieldset(gridBuilder.getString("user.personalPhoneIdentifiers"));
      fs.add(new MaxLengthTextField(fs.getTextFieldId(), new PropertyModel<String>(user, "personalPhoneIdentifiers")));
      fs.addHelpIcon(new ResourceModel("user.personalPhoneIdentifiers.tooltip.title"), new ResourceModel(
          "user.personalPhoneIdentifiers.tooltip.content"));
    }
  }

  /**
   * If no MEB is configured in config.xml nothing will be done.
   * @param gridBuilder
   * @param user
   */
  public static void createMEBPhoneNumbers(final GridBuilder gridBuilder, final PFUserDO user)
  {
    if (Configuration.getInstance().isMebConfigured() == true) {
      // MEB mobile phone numbers
      final FieldsetPanel fs = gridBuilder.newFieldset(gridBuilder.getString("user.personalMebMobileNumbers"));
      fs.add(new MaxLengthTextField(fs.getTextFieldId(), new PropertyModel<String>(user, "personalMebMobileNumbers")));
      fs.addHelpIcon(
          new ResourceModel("user.personalMebMobileNumbers.tooltip.title"),
          Model.of(gridBuilder.getString("user.personalMebMobileNumbers.tooltip.content")
              + " "
              + gridBuilder.getString("user.personalMebMobileNumbers.format")));
    }
  }

  public static void createDescription(final GridBuilder gridBuilder, final PFUserDO user)
  {
    // Description
    final FieldsetPanel fs = gridBuilder.newFieldset(gridBuilder.getString("description"));
    fs.add(new MaxLengthTextArea(fs.getTextAreaId(), new PropertyModel<String>(user, "description")));
  }

  public static void createSshPublicKey(final GridBuilder gridBuilder, final PFUserDO user)
  {
    // SSH public key
    final FieldsetPanel fs = gridBuilder.newFieldset(gridBuilder.getString("user.sshPublicKey"));
    fs.add(new MaxLengthTextArea(fs.getTextAreaId(), new PropertyModel<String>(user, "sshPublicKey")));
  }

  @SuppressWarnings("serial")
  @Override
  protected void init()
  {
    super.init();
    if (isNew() == true && Login.getInstance().hasExternalUsermanagementSystem() == false) {
      getData().setLocalUser(true);
    }
    ldapUserValues = PFUserDOConverter.readLdapUserValues(data.getLdapValues());
    if (ldapUserValues == null) {
      ldapUserValues = new LdapUserValues();
    }
    final boolean adminAccess = accessChecker.isLoggedInUserMemberOfAdminGroup();
    gridBuilder.newSplitPanel(GridSize.COL50);
    {
      // User
      final FieldsetPanel fs = gridBuilder.newFieldset(getString("user"));
      if (adminAccess == true) {
        usernameTextField = new RequiredMaxLengthTextField(fs.getTextFieldId(), new PropertyModel<String>(data, "username"));
        WicketUtils.setStrong(usernameTextField);
        fs.add(usernameTextField);
        usernameTextField.add(new AbstractValidator<String>() {

          @Override
          protected void onValidate(final IValidatable<String> validatable)
          {
            data.setUsername(validatable.getValue());
            if (StringUtils.isNotEmpty(data.getUsername()) == true && ((UserDao) getBaseDao()).doesUsernameAlreadyExist(data) == true) {
              validatable.error(new ValidationError().addMessageKey("user.error.usernameAlreadyExists"));
            }
          }
        });
      } else {
        fs.add(new DivTextPanel(fs.newChildId(), data.getUsername()));
      }
    }
    createFirstName(gridBuilder, data);
    createLastName(gridBuilder, data);
    createOrganization(gridBuilder, data);
    createEMail(gridBuilder, data);
    createAuthenticationToken(gridBuilder, data, (UserDao) getBaseDao(), this);
    createJIRAUsername(gridBuilder, data);
    if (adminAccess == true) {
      gridBuilder.newFieldset(getString("user.hrPlanningEnabled")).addCheckBox(new PropertyModel<Boolean>(data, "hrPlanning"), null)
      .setTooltip(getString("user.hrPlanningEnabled.tooltip"));
      gridBuilder.newFieldset(getString("user.activated")).addCheckBox(new Model<Boolean>() {
        @Override
        public Boolean getObject()
        {
          return data.isDeactivated() == false;
        };

        @Override
        public void setObject(final Boolean activated)
        {
          data.setDeactivated(!activated);
        };
      }, null).setTooltip(getString("user.activated.tooltip"));
      addPassswordFields();
    }

    gridBuilder.newSplitPanel(GridSize.COL50);
    createLastLoginAndDeleteAllStayLogins(gridBuilder, data, (UserDao) getBaseDao(), this);
    createLocale(gridBuilder, data);
    createDateFormat(gridBuilder, data);
    createExcelDateFormat(gridBuilder, data);
    createTimeNotation(gridBuilder, data);
    createTimeZone(gridBuilder, data);
    createPhoneIds(gridBuilder, data);
    createMEBPhoneNumbers(gridBuilder, data);
    createSshPublicKey(gridBuilder, data);

    gridBuilder.newGridPanel();
    addAssignedGroups(adminAccess);
    if (adminAccess == true && Login.getInstance().hasExternalUsermanagementSystem() == true) {
      addLdapStuff();
    }
    if (adminAccess == true) {
      addRights();
    }

    gridBuilder.newGridPanel();
    createDescription(gridBuilder, data);
  }

  @SuppressWarnings("serial")
  private void addLdapStuff()
  {
    gridBuilder.newGridPanel();
    gridBuilder.newFormHeading(getString("ldap"));
    gridBuilder.newSplitPanel(GridSize.COL50);
    gridBuilder.newFieldset(getString("user.localUser")).addCheckBox(new PropertyModel<Boolean>(data, "localUser"), null)
    .setTooltip(getString("user.localUser.tooltip"));
    final boolean posixConfigured = LdapUserDao.isPosixAccountsConfigured();
    final boolean sambaConfigured = LdapUserDao.isSambaAccountsConfigured();
    if (posixConfigured == false && sambaConfigured == false) {
      return;
    }
    final List<FormComponent< ? >> dependentLdapPosixFormComponentsList = new LinkedList<FormComponent< ? >>();
    final List<FormComponent< ? >> dependentLdapSambaFormComponentsList = new LinkedList<FormComponent< ? >>();
    if (posixConfigured == true) {
      {
        final FieldsetPanel fs = gridBuilder.newFieldset(getString("ldap.uidNumber"), getString("ldap.posixAccount"));
        uidNumberField = new MinMaxNumberField<Integer>(fs.getTextFieldId(), new PropertyModel<Integer>(ldapUserValues, "uidNumber"), 1,
            65535);
        WicketUtils.setSize(uidNumberField, 6);
        fs.add(uidNumberField);
        fs.addHelpIcon(gridBuilder.getString("ldap.uidNumber.tooltip"));
        dependentLdapPosixFormComponentsList.add(uidNumberField);
        if (ldapUserValues.isPosixValuesEmpty() == true) {
          final Button createButton = newCreateButton(dependentLdapPosixFormComponentsList, dependentLdapSambaFormComponentsList, true,
              sambaConfigured);
          fs.add(new SingleButtonPanel(fs.newChildId(), createButton, gridBuilder.getString("create"), SingleButtonPanel.NORMAL));
          WicketUtils.addTooltip(createButton, gridBuilder.getString("ldap.uidNumber.createDefault.tooltip"));
        }
      }
      {
        final FieldsetPanel fs = gridBuilder.newFieldset(getString("ldap.gidNumber"), getString("ldap.posixAccount"));
        gidNumberField = new MinMaxNumberField<Integer>(fs.getTextFieldId(), new PropertyModel<Integer>(ldapUserValues, "gidNumber"), 1,
            65535);
        WicketUtils.setSize(gidNumberField, 6);
        fs.add(gidNumberField);
        dependentLdapPosixFormComponentsList.add(gidNumberField);
      }
    }
    final LdapSambaAccountsConfig ldapSambaAccountsConfig = ConfigXml.getInstance().getLdapConfig().getSambaAccountsConfig();
    if (sambaConfigured == true) {
      {
        final FieldsetPanel fs = gridBuilder.newFieldset(getString("ldap.sambaSID"));
        final DivTextPanel textPanel = new DivTextPanel(fs.newChildId(), ldapSambaAccountsConfig.getSambaSIDPrefix() + "-");
        fs.add(textPanel);
        sambaSIDNumberField = new MinMaxNumberField<Integer>(fs.getTextFieldId(), new PropertyModel<Integer>(ldapUserValues,
            "sambaSIDNumber"), 1, 65535);
        fs.add(sambaSIDNumberField);
        sambaSIDNumberField.setOutputMarkupId(true);
        WicketUtils.setSize(sambaSIDNumberField, 5);
        fs.addHelpIcon(getString("ldap.sambaSID.tooltip"));
        dependentLdapSambaFormComponentsList.add(sambaSIDNumberField);
        if (ldapUserValues.getSambaSIDNumber() == null) {
          final Button createButton = newCreateButton(dependentLdapPosixFormComponentsList, dependentLdapSambaFormComponentsList, false,
              true);
          fs.add(new SingleButtonPanel(fs.newChildId(), createButton, gridBuilder.getString("create"), SingleButtonPanel.NORMAL));
          WicketUtils.addTooltip(createButton, gridBuilder.getString("ldap.sambaSID.createDefault.tooltip"));
        }
      }
      {
        final FieldsetPanel fs = gridBuilder.newFieldset(getString("ldap.sambaPrimaryGroupSID"), getString("ldap.sambaAccount"));
        final DivTextPanel textPanel = new DivTextPanel(fs.newChildId(), ldapSambaAccountsConfig.getSambaSIDPrefix() + "-");
        fs.add(textPanel);
        sambaPrimaryGroupSIDNumberField = new MinMaxNumberField<Integer>(fs.getTextFieldId(), new PropertyModel<Integer>(ldapUserValues,
            "sambaPrimaryGroupSIDNumber"), 1, 65535);
        fs.add(sambaPrimaryGroupSIDNumberField);
        sambaPrimaryGroupSIDNumberField.setOutputMarkupId(true);
        WicketUtils.setSize(sambaPrimaryGroupSIDNumberField, 5);
        fs.addHelpIcon(getString("ldap.sambaPrimaryGroupSID.tooltip"));
        dependentLdapSambaFormComponentsList.add(sambaPrimaryGroupSIDNumberField);
      }
    }
    gridBuilder.newSplitPanel(GridSize.COL50);
    gridBuilder.newFieldset(getString("user.restrictedUser")).addCheckBox(new PropertyModel<Boolean>(data, "restrictedUser"), null)
    .setTooltip(getString("user.restrictedUser.tooltip"));
    if (posixConfigured == true) {
      {
        final FieldsetPanel fs = gridBuilder.newFieldset(getString("ldap.homeDirectory"), getString("ldap.posixAccount"));
        homeDirectoryField = new MaxLengthTextField(fs.getTextFieldId(), new PropertyModel<String>(ldapUserValues, "homeDirectory"), 255);
        fs.add(homeDirectoryField);
        dependentLdapPosixFormComponentsList.add(homeDirectoryField);
      }
      {
        final FieldsetPanel fs = gridBuilder.newFieldset(getString("ldap.loginShell"), getString("ldap.posixAccount"));
        loginShellField = new MaxLengthTextField(fs.getTextFieldId(), new PropertyModel<String>(ldapUserValues, "loginShell"), 100);
        fs.add(loginShellField);
        dependentLdapPosixFormComponentsList.add(loginShellField);
      }
      if (ldapUserValues.isPosixValuesEmpty() == true) {
        for (final FormComponent< ? > component : dependentLdapPosixFormComponentsList) {
          component.setEnabled(false);
        }
      }
    }
    if (sambaConfigured == true) {
      final FieldsetPanel fs = gridBuilder.newFieldset(getString("ldap.sambaNTPassword"), getString("ldap.sambaNTPassword.subtitle"))
          .suppressLabelForWarning();
      final DivTextPanel sambaNTPassword = new DivTextPanel(fs.newChildId(), "*****");
      fs.add(sambaNTPassword);
      fs.addHelpIcon(getString("ldap.sambaNTPassword.tooltip"));
      if (ldapUserValues.isSambaValuesEmpty() == true) {
        for (final FormComponent< ? > component : dependentLdapSambaFormComponentsList) {
          component.setEnabled(false);
        }
      }
    }
    if (posixConfigured == true) {
      add(new IFormValidator() {
        @Override
        public FormComponent< ? >[] getDependentFormComponents()
        {
          return dependentLdapPosixFormComponentsList.toArray(new FormComponent[0]);
        }

        @Override
        public void validate(final Form< ? > form)
        {
          final LdapUserValues values = new LdapUserValues();
          values.setUidNumber(uidNumberField.getConvertedInput());
          values.setGidNumber(gidNumberField.getConvertedInput());
          values.setHomeDirectory(homeDirectoryField.getConvertedInput());
          values.setLoginShell(loginShellField.getConvertedInput());
          if (StringUtils.isBlank(data.getLdapValues()) == true && values.isPosixValuesEmpty() == true) {
            // Nothing to validate: all fields are zero and posix account wasn't set for this user before.
            return;
          }
          if (values.getUidNumber() == null) {
            uidNumberField.error(getLocalizedMessage(WebConstants.I18N_KEY_FIELD_REQUIRED, getString("ldap.uidNumber")));
          } else {
            if (LdapPosixAccountsUtils.isGivenNumberFree(data, values.getUidNumber()) == false) {
              uidNumberField.error(getLocalizedMessage("ldap.uidNumber.alreadyInUse", LdapPosixAccountsUtils.getNextFreeUidNumber()));
            }
          }
          if (values.getGidNumber() == null) {
            gidNumberField.error(getLocalizedMessage(WebConstants.I18N_KEY_FIELD_REQUIRED, getString("ldap.gidNumber")));
          }
          if (StringUtils.isBlank(values.getHomeDirectory()) == true) {
            homeDirectoryField.error(getLocalizedMessage(WebConstants.I18N_KEY_FIELD_REQUIRED, getString("ldap.homeDirectory")));
          }
          if (StringUtils.isBlank(values.getLoginShell()) == true) {
            loginShellField.error(getLocalizedMessage(WebConstants.I18N_KEY_FIELD_REQUIRED, getString("ldap.loginShell")));
          }
        }
      });
    }
    if (sambaConfigured == true) {
      add(new IFormValidator() {
        @Override
        public FormComponent< ? >[] getDependentFormComponents()
        {
          return dependentLdapSambaFormComponentsList.toArray(new FormComponent[0]);
        }

        @Override
        public void validate(final Form< ? > form)
        {
          final LdapUserValues values = new LdapUserValues();
          values.setSambaSIDNumber(sambaSIDNumberField.getConvertedInput());
          values.setSambaPrimaryGroupSIDNumber(sambaPrimaryGroupSIDNumberField.getConvertedInput());
          if (StringUtils.isBlank(data.getLdapValues()) == true && values.isSambaValuesEmpty() == true) {
            // Nothing to validate: all fields are zero and posix account wasn't set for this user before.
            return;
          }
          if (values.getSambaSIDNumber() == null) {
            sambaSIDNumberField.error(getLocalizedMessage(WebConstants.I18N_KEY_FIELD_REQUIRED, getString("ldap.sambaSID")));
          } else {
            if (LdapSambaAccountsUtils.isGivenNumberFree(data, values.getSambaSIDNumber()) == false) {
              sambaSIDNumberField.error(getLocalizedMessage("ldap.sambaSID.alreadyInUse",
                  LdapSambaAccountsUtils.getNextFreeSambaSIDNumber()));
            }
          }
          if (values.getSambaPrimaryGroupSIDNumber() != null && values.getSambaSIDNumber() == null) {
            sambaSIDNumberField.error(getLocalizedMessage(WebConstants.I18N_KEY_FIELD_REQUIRED, getString("ldap.sambaSID")));
          }
        }
      });
    }
  }

  @SuppressWarnings("serial")
  private Button newCreateButton(final List<FormComponent< ? >> dependentPosixLdapFormComponentsList,
      final List<FormComponent< ? >> dependentSambaLdapFormComponentsList, final boolean updatePosixAccount,
      final boolean updateSambaAccount)
  {
    final AjaxButton createButton = new AjaxButton(SingleButtonPanel.WICKET_ID, this) {
      @Override
      protected void onSubmit(final AjaxRequestTarget target, final Form< ? > form)
      {
        data.setUsername(usernameTextField.getRawInput());
        if (updatePosixAccount == true) {
          LdapPosixAccountsUtils.setDefaultValues(ldapUserValues, data);
          if (updateSambaAccount == true) {
            LdapSambaAccountsUtils.setDefaultValues(ldapUserValues, data);
            sambaSIDNumberField.modelChanged();
            sambaPrimaryGroupSIDNumberField.modelChanged();
            target.add(sambaSIDNumberField, sambaPrimaryGroupSIDNumberField);
          }
        } else if (updateSambaAccount == true) {
          LdapSambaAccountsUtils.setDefaultValues(ldapUserValues, data);
          sambaSIDNumberField.modelChanged();
          sambaPrimaryGroupSIDNumberField.modelChanged();
          target.add(sambaSIDNumberField, sambaPrimaryGroupSIDNumberField);
        }
        if (updatePosixAccount == true) {
          for (final FormComponent< ? > component : dependentPosixLdapFormComponentsList) {
            component.modelChanged();
            component.setEnabled(true);
          }
        }
        if (updateSambaAccount == true) {
          for (final FormComponent< ? > component : dependentSambaLdapFormComponentsList) {
            component.modelChanged();
            component.setEnabled(true);
          }
        }
        this.setVisible(false);
        for (final FormComponent< ? > comp : dependentPosixLdapFormComponentsList) {
          target.add(comp);
        }
        for (final FormComponent< ? > comp : dependentSambaLdapFormComponentsList) {
          target.add(comp);
        }
        target.add(this, UserEditForm.this.feedbackPanel);
        target.appendJavaScript("hideAllTooltips();"); // Otherwise a tooltip is left as zombie.
      }

      @Override
      protected void onError(final AjaxRequestTarget target, final Form< ? > form)
      {
        target.add(UserEditForm.this.feedbackPanel);
      }
    };
    createButton.setDefaultFormProcessing(false);
    return createButton;
  }

  @SuppressWarnings("serial")
  private void addPassswordFields()
  {
    // Password
    final FieldsetPanel fs = gridBuilder.newFieldset(getString("password"), getString("passwordRepeat"));
    final PasswordTextField passwordField = new PasswordTextField(fs.getTextFieldId(), new PropertyModel<String>(this, "password")) {
      @Override
      protected void onComponentTag(final ComponentTag tag)
      {
        super.onComponentTag(tag);
        if (passwordUser == null) {
          tag.put("value", "");
        } else if (StringUtils.isEmpty(getConvertedInput()) == false) {
          tag.put("value", MAGIC_PASSWORD);
        }
      }
    };
    passwordField.setResetPassword(false).setRequired(isNew());
    final PasswordTextField passwordRepeatField = new PasswordTextField(fs.getTextFieldId(), new PropertyModel<String>(this,
        "passwordRepeat")) {
      @Override
      protected void onComponentTag(final ComponentTag tag)
      {
        super.onComponentTag(tag);
        if (passwordUser == null) {
          tag.put("value", "");
        } else if (StringUtils.isEmpty(getConvertedInput()) == false) {
          tag.put("value", MAGIC_PASSWORD);
        }
      }
    };
    passwordRepeatField.setResetPassword(false).setRequired(false);
    passwordRepeatField.add(new AbstractValidator<String>() {
      @Override
      protected void onValidate(final IValidatable<String> validatable)
      {
        final String passwordRepeatInput = validatable.getValue();
        passwordField.validate();
        final String passwordInput = passwordField.getConvertedInput();
        if (StringUtils.isEmpty(passwordInput) == true && StringUtils.isEmpty(passwordRepeatInput) == true) {
          return;
        }
        if (StringUtils.equals(passwordInput, passwordRepeatInput) == false) {
          passwordUser = null;
          validatable.error(new ValidationError().addMessageKey("user.error.passwordAndRepeatDoesNotMatch"));
          return;
        }
        if (MAGIC_PASSWORD.equals(passwordInput) == false || passwordUser == null) {
          final String errorMsgKey = ((UserDao) getBaseDao()).checkPasswordQuality(passwordInput);
          if (errorMsgKey != null) {
            passwordUser = null;
            validatable.error(new ValidationError().addMessageKey(errorMsgKey));
          } else {
            passwordUser = new PFUserDO();
            ((UserDao) getBaseDao()).createEncryptedPassword(passwordUser, passwordInput);
          }
        }
      }

      /**
       * @see org.apache.wicket.validation.validator.AbstractValidator#validateOnNullValue()
       */
      @Override
      public boolean validateOnNullValue()
      {
        // Should be validated (e. g. if password field is given but password repeat field not).
        return true;
      }
    });
    WicketUtils.setPercentSize(passwordField, 50);
    WicketUtils.setPercentSize(passwordRepeatField, 50);
    fs.add(passwordField);
    fs.add(passwordRepeatField);
    fs.addHelpIcon(getString(UserDao.MESSAGE_KEY_PASSWORD_QUALITY_CHECK));
  }

  private static void addDateFormatCombobox(final GridBuilder gridBuilder, final PFUserDO user, final String labelKey,
      final String property, final String[] dateFormats, final boolean convertExcelFormat)
  {
    final FieldsetPanel fs = gridBuilder.newFieldset(gridBuilder.getString(labelKey));
    final LabelValueChoiceRenderer<String> dateFormatChoiceRenderer = new LabelValueChoiceRenderer<String>();
    for (final String str : dateFormats) {
      String dateString = "???";
      final String pattern = convertExcelFormat == true ? str.replace('Y', 'y').replace('D', 'd') : str;
      try {
        final SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
        dateString = dateFormat.format(new Date());
      } catch (final Exception ex) {
        log.warn("Invalid date format in config.xml: " + pattern);
      }
      dateFormatChoiceRenderer.addValue(str, str + ": " + dateString);
    }
    final DropDownChoice<String> dateFormatChoice = new DropDownChoice<String>(fs.getDropDownChoiceId(), new PropertyModel<String>(user,
        property), dateFormatChoiceRenderer.getValues(), dateFormatChoiceRenderer);
    dateFormatChoice.setNullValid(true);
    fs.add(dateFormatChoice);
  }

  private void addRights()
  {
    final List<UserRightVO> userRights = userRightDao.getUserRights(data);
    boolean first = true;
    boolean odd = true;
    for (final UserRightVO rightVO : userRights) {
      final UserRight right = rightVO.getRight();
      final UserRightValue[] availableValues = right.getAvailableValues(((UserDao) getBaseDao()).getUserGroupCache(), data);
      if (right.isConfigurable(((UserDao) getBaseDao()).getUserGroupCache(), data) == false) {
        continue;
      }
      if (first == true) {
        gridBuilder.newGridPanel();
        gridBuilder.newFormHeading(getString("access.rights"));
        rightsData = new UserRightsEditData();
        first = false;
      }
      if (odd == true) {
        // gridBuilder.newNestedRowPanel();
      }
      odd = !odd;
      gridBuilder.newSplitPanel(GridSize.COL50);
      rightsData.addRight(rightVO);
      final String label = getString(right.getId().getI18nKey());
      final FieldsetPanel fs = gridBuilder.newFieldset(label);
      if (right.isBooleanType() == true) {
        fs.addCheckBox(new PropertyModel<Boolean>(rightVO, "booleanValue"), null);
      } else {
        final LabelValueChoiceRenderer<UserRightValue> valueChoiceRenderer = new LabelValueChoiceRenderer<UserRightValue>(fs,
            availableValues);
        final DropDownChoice<UserRightValue> valueChoice = new DropDownChoice<UserRightValue>(fs.getDropDownChoiceId(),
            new PropertyModel<UserRightValue>(rightVO, "value"), valueChoiceRenderer.getValues(), valueChoiceRenderer);
        valueChoice.setNullValid(true);
        fs.add(valueChoice);
      }
    }
  }

  private void addAssignedGroups(final boolean adminAccess)
  {
    final FieldsetPanel fs = gridBuilder.newFieldset(getString("user.assignedGroups")).setLabelSide(false);
    final Collection<Integer> set = ((UserDao) getBaseDao()).getAssignedGroups(data);
    final GroupsProvider groupsProvider = new GroupsProvider();
    assignListHelper = new MultiChoiceListHelper<GroupDO>().setComparator(new GroupsComparator()).setFullList(
        groupsProvider.getSortedGroups());
    if (set != null) {
      for (final Integer groupId : set) {
        final GroupDO group = userGroupCache.getGroup(groupId);
        if (group != null) {
          assignListHelper.addOriginalAssignedItem(group).assignItem(group);
        }
      }
    }
    final Select2MultiChoice<GroupDO> groups = new Select2MultiChoice<GroupDO>(fs.getSelect2MultiChoiceId(),
        new PropertyModel<Collection<GroupDO>>(this.assignListHelper, "assignedItems"), groupsProvider);
    fs.add(groups);
  }

  /**
   * @return the passwordUser
   */
  PFUserDO getPasswordUser()
  {
    return passwordUser;
  }

  /**
   * @return The clear text password (if given). Please check {@link #getPasswordUser()} first.
   */
  String getPassword()
  {
    return password;
  }

  @Override
  protected Logger getLogger()
  {
    return log;
  }
}