import getpass


def query_user(prompt, var_type, default=None, hide=False):
    """
    Query user for a certain value and let them try again, if they fail entering a valid value.

    A default value can be given, that is returned when the user enters an empty string. If no default value is given,
    an empty input may still be valid if the given type allows construction from empty string. The default value – if
    given – is automatically appended to the prompt (in brackets).

    Booleans must be entered with 'y' or 'n' by the user.

    :param prompt: The prompt string to be presented to the user
    :type prompt: str
    :param var_type: The type to convert the value to. If it fails with ValueError the user is queried again.
    :type var_type: type
    :param default: The default value. Must be of the var_type or None.
    :param hide: If True, getpass() is used for the query to hide the input in the terminal (for password inputs etc.)
    :type hide: bool
    :return: The entered value, converted to the given var_type
    :rtype: var_type
    """
    if var_type is bool:
        prompt += " [{}/{}]".format('Y' if default else 'y', 'n' if default or default is None else 'N')
    elif var_type is str:
        if default:
            prompt += " [{}]".format(default)
    else:
        prompt += " [{}]".format(default)

    while True:
        result = getpass.getpass(prompt + " ") if hide else input(prompt + " ")
        if not result and default is not None:
            return default
        if var_type is bool:
            if result.lower() in "yn":
                return result.lower() == 'y'
            else:
                print("Invalid input. Must be 'y' or 'n'.")
        else:
            try:
                return var_type(result)
            except ValueError:
                print("Not a valid {}. Please try again.".format(var_type.__name__))