class UserApiMixin:
    user_account_default_props = ['acc_id', 'acc_user_id', 'acc_email', 'acc_type']
    user_account_list_props = ['acc_can_login', 'acc_display_name', 'acc_email', 'acc_fs_id', 'acc_full_name', 'acc_id',
                               'acc_last_ip', 'acc_last_login', 'acc_last_ua', 'acc_login_queue',
                               'acc_logins', 'acc_privs', 'acc_pw_change', 'acc_session_time_started', 'acc_sort_name',
                               'acc_sys', 'acc_time_created', 'acc_time_last_access', 'acc_type', 'acc_user_id',
                               'acc_uuid', 'com_id', 'tz_id']
    user_account_get_props = user_account_list_props + ['com_full_name', 'tz_iana', 'tz_name']

    user_account_privs = ['COMWIDE', 'SYSWIDE', 'POD_DESIGNER', 'LAB_DESIGNER']

    community_default_props = ['com_id', 'com_full_name']
    community_props = ['com_alt_banner', 'com_alt_banner_height', 'com_alt_banner_width', 'com_enabled',
                       'com_full_name', 'com_id', 'com_max_slots_per_res', 'com_min_hours_btw_res',
                       'com_mynetlab_news', 'com_mynetlab_welcome']

    def user_account_list(self, *, com_id=None, acc_type=None, properties="default", **kwargs):
        """
        Query a list of the user accounts in the NETLAB system. The list method
        is only restricted by community id and account types. The properties
        only identify those properties or fields that will be returned via
        the underlying query.

        :param int com_id: Community ID
        :param str acc_type: User account type **(S, I, None)**
        :param properties: Properties consist of any combination of properties listed below under Properties.
                           You may also use **"all"** to include all properties, or **"default"** for a smaller subset
                           of properties.
        :type properties: str or list

        :returns: A list of dictionaries (a set of records) where the dictionaries contain requested properties
            per user.

        **Properties**

        acc_can_login
            User allowed to login.
        acc_display_name
            Account name people will see this name in discussions, messages and comments.
        acc_email
            Account email.
        acc_fs_id
            Filesystem identifier of the account root folder.
        acc_full_name
            Account full name.
        acc_id
            Account identifier.
        acc_last_ip
            Last IP address of user.
        acc_last_login
            Datetime of last account login.
        acc_last_ua
            User agent string of the browser when logging in from the UI.
        acc_logins
            Number of account logins.
        acc_privs
            Supplemental account privileges for instructors only.
            **('COMWIDE', 'SYSWIDE', 'POD_DESIGNER', 'LAB_DESIGNER')**
        acc_pw_change
            Force user to change password.
        acc_session_time_started
            Datetime of session start time.
        acc_sort_name
            Account name as it appears in sorted lists.
        acc_sys
            Mark as system account.
        acc_time_created
            Datetime account was created.
        acc_time_last_access
            Datetime of last access.
        acc_type
            Account type. 'S' (student), 'I' (instructor), 'Z' (administrator)
        acc_user_id
            Unique system-wide login identifier.
        acc_uuid
            Globally unique account ID.
        com_id
            Unique community identifier.
        tz_id
            Time zone identifier.
        """

        method = 'user.account.list'

        if acc_type:
            acc_type = acc_type.upper()
        if properties == 'all':
            properties = self.user_account_list_props
        if properties == 'default':
            properties = self.user_account_default_props
        return self.call(method, com_id=com_id, acc_type=acc_type, properties=properties, **kwargs)

    def user_account_filter(self, *, com_id=None, filter_params, **kwargs):
        """
        Gets all users from server and then performs filtering locally. Any property from
        :meth:`~netlab.api.UserApiMixin.user_account_list` can be filtered upon. Multiple
        ``filter_params`` will be combined using AND. Returns zero or more user accounts that match filter query.

        :param int com_id: Only return users from this Community ID, defaults to all.
        :param dict filter_params: Dict where each key is a property and the value to filter by.

        :returns: A list of user accounts that match ``filter_params``
        """

        # only request the properties that are needed to filter by and always get acc_id
        properties = list(filter_params) + ['acc_id']

        # TODO Replace with server side filtering when available. client side could be very slow
        def filter_by_params(account):
            filter_checks = []
            for param, value in filter_params.items():
                # TODO: add filter_param__options like case insensitive, date range, etc
                filter_checks.append(account[param] == value)
            return all(filter_checks)

        users = self.call('user.account.list', com_id=com_id, properties=properties, **kwargs)
        results = list(filter(filter_by_params, users))
        return results

    def user_account_get(self, *, acc_id, properties="default", **kwargs):
        """
        Retrieves a user account on the NETLAB system.

        :param int acc_id: User Account ID.
        :param properties: Properties consist of any combination of properties listed below under Properties.
                           You may also use **"all"** to include all properties, or **"default"** for a smaller subset
                           of properties.
        :type properties: str or list

        :returns: Account data for the requested acc_id with the requested properties.

        **Properties**

        acc_can_login
            User allowed to login.
        acc_display_name
            Account name people will see this name in discussions, messages and comments.
        acc_email
            Account email.
        acc_fs_id
            Filesystem identifier of the account root folder.
        acc_full_name
            Account full name.
        acc_id
            Account identifier.
        acc_last_ip
            Last IP address of user.
        acc_last_login
            Datetime of last account login.
        acc_last_ua
            User agent string of the browser when logging in from the UI.
        acc_logins
            Number of account logins.
        acc_privs
            Supplemental account privileges for instructors only.
            **('COMWIDE', 'SYSWIDE', 'POD_DESIGNER', 'LAB_DESIGNER')**
        acc_pw_change
            Force user to change password.
        acc_session_time_started
            Datetime of session start time.
        acc_sort_name
            Account name as it appears in sorted lists.
        acc_sys
            Mark as system account.
        acc_time_created
            Datetime account was created.
        acc_time_last_access
            Datetime of last access.
        acc_type
            Account type. 'S' (student), 'I' (instructor), 'Z' (administrator)
        acc_user_id
            Unique system-wide login identifier.
        acc_uuid
            Globally unique account ID.
        com_full_name
            Community name.
        com_id
            Unique community identifier.
        tz_iana
            Timezone IANA database identifier.
        tz_id
            Time zone identifier.
        tz_name
            Time zone name.
        """
        method = 'user.account.get'

        if properties == 'all':
            properties = self.user_account_get_props
        if properties == 'default':
            properties = self.user_account_default_props

        return self.call(method, acc_id=acc_id, properties=properties, **kwargs)

    def user_account_add(self, *, com_id, acc_user_id, acc_password, acc_full_name, acc_type="S", acc_privs=None,
                         acc_display_name=None, acc_sort_name=None, acc_email=None, acc_can_login=True,
                         acc_pw_change=True, cls_id=None, tz_id=11, **kwargs):
        """
        Add a user account on the NETLAB system.

        :param int com_id: Community ID.
        :param str acc_user_id: User Account ID.
        :param str acc_password: Unencrypted account password.
        :param str acc_full_name: Account full name.

        :param str acc_type: Account type Student = 'S' or Instructor = 'I'. Defaults to 'S'.
        :param bool acc_can_login: User can login. Defaults to True.
        :param bool acc_pw_change: Upon first login, force user to change password. Defaults to True.
        :param str acc_privs: Supplemental account privileges for instructors in list or tuple format.
                              **('COMWIDE', 'SYSWIDE', 'POD_DESIGNER', 'LAB_DESIGNER')**
        :param str acc_display_name: Account name, people will see this name in discussions, messages, and comments.
        :param str acc_sort_name: Account name as it appears in sorted lists.
        :param str acc_email: Account email address.
        :param int tz_id: Timezone identifier. Defaults to 11.
        :param int cls_id: Class ID. (If you want to add user directly to a class during account creation.)

        :returns: On success, returns acc_id.
        :rtype: int
        """

        method = "user.account.add"
        params = {
            'com_id': com_id,
            'acc_user_id': acc_user_id,
            'acc_password': acc_password,
            'acc_full_name': acc_full_name,
            'acc_type': acc_type.upper(),
            'acc_can_login': acc_can_login,
            'acc_pw_change': acc_pw_change,
            'tz_id': tz_id,
        }

        if acc_privs:
            if isinstance(acc_privs, (list, tuple)):
                acc_privs = ",".join(acc_privs)
            params['acc_privs'] = acc_privs.upper()
        if acc_display_name:
            params['acc_display_name'] = acc_display_name
        if acc_sort_name:
            params['acc_sort_name'] = acc_sort_name
        if acc_email:
            params['acc_email'] = acc_email
        if cls_id:
            params['cls_id'] = cls_id

        params.update(kwargs)
        return int(self.call(method, **params))

    def user_account_password_set(self, *, acc_id, new_password, force_reset=False, **kwargs):
        """
        Sets the user account password.

        :param int acc_id: Account ID.
        :param str new_password: New account password.
        :param bool force_reset: Force user to reset password. Defaults to False.

        :returns: Acknowledgment of "OK" or "Error"
        """

        method = "user.account.password.set"
        return self.call(method, acc_id=acc_id, new_password=new_password, force_reset=force_reset, **kwargs)

    def user_account_remove(self, *, acc_id, **kwargs):
        """
        Removes the specified user account.

        :param int acc_id: User Account ID

        :returns: Ok or Error
        """

        method = "user.account.remove"
        return self.call(method, acc_id=acc_id, **kwargs)

    def user_account_update(self, *, acc_id, acc_user_id=None, acc_type=None, acc_can_login=None,
                            acc_pw_change=None, acc_password=None, acc_privs=None, acc_full_name=None,
                            acc_display_name=None, acc_sort_name=None, acc_email=None,
                            tz_id=None, **kwargs):
        """
        Update the user account.

        :param int acc_id: Account ID

        :param str acc_user_id: Unique system-wide login identifier.
        :param str acc_type: Account type. (Student='S', Instructor='I')
        :param bool acc_can_login: User allowed to login.
        :param bool acc_pw_change: Force user to change password.
        :param str acc_password: Account password
        :param list acc_privs: Supplemental account privileges for instructors in list or tuple format.
                               **('COMWIDE', 'SYSWIDE', 'POD_DESIGNER', 'LAB_DESIGNER')**
        :param str acc_full_name: Account full name.
        :param str acc_display_name: Account name people will see this name in discussions, messages and comments.
        :param str acc_sort_name: Account name as it appears in sorted lists.
        :param str acc_email: Account e-mail address.
        :param int tz_id: Timezone identifier.

        :returns: On success, returns 'OK'
        """
        method = "user.account.update"
        params = {'acc_id': acc_id}

        if acc_user_id:
            params['acc_user_id'] = acc_user_id
        if acc_type:
            params['acc_type'] = acc_type
        if acc_can_login is not None:
            params['acc_can_login'] = acc_can_login
        if acc_pw_change is not None:
            params['acc_pw_change'] = acc_pw_change
        if acc_password:
            params['acc_password'] = acc_password
        if acc_privs:
            if isinstance(acc_privs, (list, tuple)):
                acc_privs = ",".join(acc_privs)
            params['acc_privs'] = acc_privs.upper()
        if acc_full_name:
            params['acc_full_name'] = acc_full_name
        if acc_display_name:
            params['acc_display_name'] = acc_display_name
        if acc_sort_name:
            params['acc_sort_name'] = acc_sort_name
        if acc_email:
            params['acc_email'] = acc_email
        if tz_id:
            params['tz_id'] = tz_id

        params.update(kwargs)
        return self.call(method, **params)

    def user_community_add(self, *, com_full_name, com_id, com_enabled=True,
                           com_max_slots_per_res=None, com_min_hours_btw_res=None,
                           com_mynetlab_news=None, com_mynetlab_welcome=None):
        """
        This method allows you to add a community to the NETLAB+ system.

        :param str com_full_name: Community full name.
        :param int com_id: Community identifier.
        :param bool com_enabled: Is community enabled.
        :param int com_max_slots_per_res: Max amount of slots per reservation.
        :param int com_min_hours_btw_res: Minimum hours between reservations.
        :param str com_mynetlab_news: Community MyNETLAB+ news entry.
        :param str com_mynetlab_welcome: Community MyNETLAB+ welcome message.

        :returns: Community ID of the newly created community.
        """

        assert com_id < 100, "Community id out of range"  # TODO document and confirm
        params = {
            'com_full_name': com_full_name,
            'com_id': com_id,
            'com_enabled': com_enabled,
        }

        if com_max_slots_per_res is not None:
            params['com_max_slots_per_res'] = com_max_slots_per_res
        if com_min_hours_btw_res is not None:
            params['com_min_hours_btw_res'] = com_min_hours_btw_res
        if com_mynetlab_news is not None:
            params['com_mynetlab_news'] = com_mynetlab_news
        if com_mynetlab_welcome is not None:
            params['com_mynetlab_welcome'] = com_mynetlab_welcome

        return self.call('user.community.add', **params)

    def user_community_remove(self, *, com_id):
        """
        This method allows you to remove a community.

        :param int com_id: Community identifier.

        :returns: Success or Failure
        """

        return self.call('user.community.remove', com_id=com_id)

    def user_community_find(self, *, com_full_name, **kwargs):
        """
        This method allows you to find a community based on ``com_full_name``.

        :param str com_full_name: Community full name.

        :returns: The Community ID on success.
        :rtype: int
        """

        method = "user.community.find"
        return int(self.call(method, com_full_name=com_full_name, **kwargs))

    def user_community_get(self, *, com_id, properties="default", **kwargs):
        """
        This method allows you to retrieve a community and its properties.

        :param int com_id: Community ID.
        :param properties: Properties to return. Accepts string or list.
                        See below for all available properties. You may also use **"all"** to include all properties.
        :type properties: list or str

        :returns: A dictionary with the properties requested

        **Properties**

        com_alt_banner
            Alternate community banner.
        com_alt_banner_height
            Alternate banner height.
        com_alt_banner_width
            Alternate banner width.
        com_enabled
            Is community enabled.
        com_full_name
            Community full name.
        com_id
            Community identifier.
        com_max_slots_per_res
            Max amount of slots per reservation.
        com_min_hours_btw_res
            Minimum hours between reservations.
        com_mynetlab_news
            Community MyNETLAB+ news entry.
        com_mynetlab_welcome
            Community MyNETLAB+ welcome message.
        """

        method = "user.community.get"
        if properties == 'all':
            properties = self.community_props
        if properties == 'default':
            properties = self.community_default_props

        return self.call(method, com_id=com_id, properties=properties, **kwargs)

    def user_community_list(self, *, properties="default", sort_property="com_id", **kwargs):
        """
        Retrieve a list of communities.

        :param properties: Properties to return. Accepts string or list.
                        See below for all available properties. You may also use **"all"** to include all properties.
        :type properties: list or str
        :param str sort_property: Property to sort list by. Only ``com_id`` (default) and ``com_full_name`` are
                   valid options.

        :returns: A dictionary with the properties requested

        **Properties**

        com_alt_banner
            Alternate community banner.
        com_alt_banner_height
            Alternate banner height.
        com_alt_banner_width
            Alternate banner width.
        com_enabled
            Is community enabled.
        com_full_name
            Community full name.
        com_id
            Community identifier.
        com_max_slots_per_res
            Max amount of slots per reservation.
        com_min_hours_btw_res
            Minimum hours between reservations.
        com_mynetlab_news
            Community MyNETLAB+ news entry.
        com_mynetlab_welcome
            Community MyNETLAB+ welcome message.
        """

        method = "user.community.list"
        if properties == 'all':
            properties = self.community_props
        if properties == 'default':
            properties = self.community_default_props

        return self.call(method, properties=properties, sort_property=sort_property, **kwargs)

    def user_logins_system_get(self, **kwargs):
        """
        This method allows you to query the User Logins status and number of active logins.

        :returns: A dictionary with the following properties

        active
            Number of active users logged in.
        enabled
            User logins status.
        """
        method = "user.logins.system.get"
        return self.call(method, **kwargs)

    def user_logins_system_set(self, *, enabled, logout=False, **kwargs):
        """
        This method allows you to enable and disable user logins.

        :param bool enabled: Enable or disable user logins.
        :param bool logout: Logout users that are already logged in.

        :returns: A dictionary with the following properties

        changed
            Whether user login status was changed.
        logouts
            Number of users logged out.
        """
        method = "user.logins.system.set"
        return self.call(method, enabled=enabled, logout=logout, **kwargs)
