Return a dict of the changes required for a user if the user is present,
    otherwise return False.

    Updated in 2015.8.0 to include support for windows homedrive, profile,
    logonscript, and description fields.

    Updated in 2014.7.0 to include support for shadow attributes, all
    attributes supported as integers only.

def _changes(name,
             uid=None,
             gid=None,
             groups=None,
             optional_groups=None,
             remove_groups=True,
             home=None,
             createhome=True,
             password=None,
             enforce_password=True,
             empty_password=False,
             shell=None,
             fullname='',
             roomnumber='',
             workphone='',
             homephone='',
             other='',
             loginclass=None,
             date=None,
             mindays=0,
             maxdays=999999,
             inactdays=0,
             warndays=7,
             expire=None,
             win_homedrive=None,
             win_profile=None,
             win_logonscript=None,
             win_description=None,
             allow_uid_change=False,
             allow_gid_change=False):
    '''
    Return a dict of the changes required for a user if the user is present,
    otherwise return False.

    Updated in 2015.8.0 to include support for windows homedrive, profile,
    logonscript, and description fields.

    Updated in 2014.7.0 to include support for shadow attributes, all
    attributes supported as integers only.
    '''

    if 'shadow.info' in __salt__:
        lshad = __salt__['shadow.info'](name)

    lusr = __salt__['user.info'](name)
    if not lusr:
        return False

    change = {}
    if groups is None:
        groups = lusr['groups']
    wanted_groups = sorted(set((groups or []) + (optional_groups or [])))
    if uid and lusr['uid'] != uid:
        change['uid'] = uid
    if gid is not None and lusr['gid'] not in (gid, __salt__['file.group_to_gid'](gid)):
        change['gid'] = gid
    default_grp = __salt__['file.gid_to_group'](
        gid if gid is not None else lusr['gid']
    )
    # remove the default group from the list for comparison purposes
    if default_grp in lusr['groups']:
        lusr['groups'].remove(default_grp)
    if name in lusr['groups'] and name not in wanted_groups:
        lusr['groups'].remove(name)
    # remove default group from wanted_groups, as this requirement is
    # already met
    if default_grp in wanted_groups:
        wanted_groups.remove(default_grp)
    if _group_changes(lusr['groups'], wanted_groups, remove_groups):
        change['groups'] = wanted_groups
    if home and lusr['home'] != home:
        change['home'] = home
    if createhome:
        newhome = home if home else lusr['home']
        if newhome is not None and not os.path.isdir(newhome):
            change['homeDoesNotExist'] = newhome
    if shell and lusr['shell'] != shell:
        change['shell'] = shell
    if 'shadow.info' in __salt__ and 'shadow.default_hash' in __salt__:
        if password and not empty_password:
            default_hash = __salt__['shadow.default_hash']()
            if lshad['passwd'] == default_hash \
                    or lshad['passwd'] != default_hash and enforce_password:
                if lshad['passwd'] != password:
                    change['passwd'] = password
        if empty_password and lshad['passwd'] != '':
            change['empty_password'] = True
        if date is not None and lshad['lstchg'] != date:
            change['date'] = date
        if mindays is not None and lshad['min'] != mindays:
            change['mindays'] = mindays
        if maxdays is not None and lshad['max'] != maxdays:
            change['maxdays'] = maxdays
        if inactdays is not None and lshad['inact'] != inactdays:
            change['inactdays'] = inactdays
        if warndays is not None and lshad['warn'] != warndays:
            change['warndays'] = warndays
        if expire and lshad['expire'] != expire:
            change['expire'] = expire
    elif 'shadow.info' in __salt__ and salt.utils.platform.is_windows():
        if expire and expire is not -1 and salt.utils.dateutils.strftime(lshad['expire']) != salt.utils.dateutils.strftime(expire):
            change['expire'] = expire

    # GECOS fields
    fullname = salt.utils.data.decode(fullname)
    lusr['fullname'] = salt.utils.data.decode(lusr['fullname'])
    if fullname is not None and lusr['fullname'] != fullname:
        change['fullname'] = fullname
    if win_homedrive and lusr['homedrive'] != win_homedrive:
        change['homedrive'] = win_homedrive
    if win_profile and lusr['profile'] != win_profile:
        change['profile'] = win_profile
    if win_logonscript and lusr['logonscript'] != win_logonscript:
        change['logonscript'] = win_logonscript
    if win_description and lusr['description'] != win_description:
        change['description'] = win_description

    # MacOS doesn't have full GECOS support, so check for the "ch" functions
    # and ignore these parameters if these functions do not exist.
    if 'user.chroomnumber' in __salt__ \
            and roomnumber is not None:
        roomnumber = salt.utils.data.decode(roomnumber)
        lusr['roomnumber'] = salt.utils.data.decode(lusr['roomnumber'])
        if lusr['roomnumber'] != roomnumber:
            change['roomnumber'] = roomnumber
    if 'user.chworkphone' in __salt__ \
            and workphone is not None:
        workphone = salt.utils.data.decode(workphone)
        lusr['workphone'] = salt.utils.data.decode(lusr['workphone'])
        if lusr['workphone'] != workphone:
            change['workphone'] = workphone
    if 'user.chhomephone' in __salt__ \
            and homephone is not None:
        homephone = salt.utils.data.decode(homephone)
        lusr['homephone'] = salt.utils.data.decode(lusr['homephone'])
        if lusr['homephone'] != homephone:
            change['homephone'] = homephone
    if 'user.chother' in __salt__ and other is not None:
        other = salt.utils.data.decode(other)
        lusr['other'] = salt.utils.data.decode(lusr['other'])
        if lusr['other'] != other:
            change['other'] = other
    # OpenBSD/FreeBSD login class
    if __grains__['kernel'] in ('OpenBSD', 'FreeBSD'):
        if loginclass:
            if __salt__['user.get_loginclass'](name) != loginclass:
                change['loginclass'] = loginclass

    errors = []
    if not allow_uid_change and 'uid' in change:
        errors.append(
            'Changing uid ({0} -> {1}) not permitted, set allow_uid_change to '
            'True to force this change. Note that this will not change file '
            'ownership.'.format(lusr['uid'], uid)
        )
    if not allow_gid_change and 'gid' in change:
        errors.append(
            'Changing gid ({0} -> {1}) not permitted, set allow_gid_change to '
            'True to force this change. Note that this will not change file '
            'ownership.'.format(lusr['gid'], gid)
        )
    if errors:
        raise CommandExecutionError(
            'Encountered error checking for needed changes',
            info=errors
        )

    return change