# #################################################
# GROUP INPUT MIXIN
# #################################################


class GroupInputMixin:
    """
    Mixin class for managing group input filters in a Loki database.

    IMPLEMENTED METHODS:
    - [unionInputGroups]:
        Adds a list of groups to a group filter in a specified database table.
        Filters ambiguous or unrecognized groups based on provided options,
        allowing for ignoring or adding multiple matches for ambiguous groups.
    - [intersectInputGroups]:
        Reduces a group filter in a specified database table, keeping only
        groups that match the provided list. If the `group` filter is not
        initialized, a union operation is performed instead of an intersection.
    - [unionInputGroupSearch]:
        Adds a list of groups to a filter in a specified database table, using
        text-based search to identify and insert relevant groups.
    - [intersectInputGroupSearch]:
        Reduces a group filter in a specified database table, keeping only the
        groups that match a provided list based on textual search. If the group
        filter is not initialized, performs a union instead of an intersection.
    """

    def unionInputGroups(self, db, names, errorCallback=None):
        """
        Adds a list of groups to a group filter in a specified database table.
        Filters ambiguous or unrecognized groups based on provided options,
        allowing for ignoring or adding multiple matches for ambiguous groups.

        Parameters:
        - db: The name of the database where the `group` table is located.
        - names: A list of tuples `[(namespace, name, extra), ...]`, where:
                - `namespace` is the namespace of the group,
                - `name` is the group name,
                - `extra` contains additional information.
        - errorCallback: Optional function called in case of errors with group
        input data.

        Operation:
        - Logs the start of the process for adding groups to the filter.
        - Calls `prepareTableForUpdate` to temporarily remove indexes from the
        `group` table, optimizing insertion.
        - Defines an SQL `INSERT` query to insert each group into the `group`
        table in the database.
        - Sets the maximum allowed matches for a group (`maxMatch`), depending
        on the `allow_ambiguous_groups` option.
        - Executes the SQL query for each group identifier generated by
        `generateGroupIDsByIdentifiers`.
        - Tracks counts of:
                - `numAdd`: Number of valid groups added.
                - `tally['zero']`: Number of unrecognized groups, logged as a
                warning.
                - `tally['many']`: Number of ambiguous groups, logged as a
                warning based on `allow_ambiguous_groups` setting.
        - Logs the final count of added groups.
        - Increments the group filter counter in the database.

        Returns:
        - None. The method inserts groups into the specified table and logs
        details about the insertion process.

        This function is useful for managing and adding groups to a database
        table, applying an insertion process that handles ambiguous or missing
        data based on flexible settings.
        """
        # names=[ (namespace,name,extra), ... ]
        self.logPush("adding to %s group filter ...\n" % (db,))
        cursor = self._loki._db.cursor()

        self.prepareTableForUpdate(db, "group")
        sql = (
            "INSERT INTO `%s`.`group` (label,extra,group_id) VALUES (?2,?3,?4); SELECT 1"  # noqa E501
            % db
        )
        maxMatch = None if self._options.allow_ambiguous_groups == "yes" else 1
        tally = dict()
        numAdd = 0
        for row in cursor.executemany(
            sql,
            self._loki.generateGroupIDsByIdentifiers(
                names,
                minMatch=1,
                maxMatch=maxMatch,
                tally=tally,
                errorCallback=errorCallback,
            ),
        ):
            numAdd += 1
        if tally["zero"]:
            self.warn(
                "WARNING: ignored %d unrecognized group identifier(s)\n"
                % tally["zero"]  # noqa E501
            )
        if tally["many"]:
            if self._options.allow_ambiguous_groups == "yes":
                self.warn(
                    "WARNING: added multiple results for %d ambiguous group identifier(s)\n"  # noqa E501
                    % tally["many"]
                )
            else:
                self.warn(
                    "WARNING: ignored %d ambiguous group identifier(s)\n"
                    % tally["many"]
                )
        self.logPop("... OK: added %d groups\n" % numAdd)

        self._inputFilters[db]["group"] += 1

    def intersectInputGroups(self, db, names, errorCallback=None):
        """
        Reduces a group filter in a specified database table, keeping only
        groups that match the provided list. If the `group` filter is not
        initialized, a union operation is performed instead of an intersection.

        Parameters:
        - db: The name of the database containing the `group` table.
        - names: List of tuples `[(namespace, name, extra), ...]`, where:
            - `namespace` is the group namespace,
            - `name` is the group name,
            - `extra` contains additional information.
        - errorCallback: Optional function called upon error during group
        processing.

        Operation:
        - If the `group` filter is not yet initialized
        (`_inputFilters[db]['group']` is 0), calls `unionInputGroups` to
        initialize the filter with all provided groups.
        - Otherwise:
            - Logs the start of the group filter reduction process.
            - Calls `prepareTableForQuery` to ensure the `group` table is
            ready for querying.
            - Sets all groups in the table to "not kept" (`flag = 0`).
            - Counts the number of groups before reduction (`numBefore`).
            - Updates the `group` table, setting `flag = 1` for groups that
            match the `group_id` identifiers
            generated from the provided names.
            - Deletes groups not in the provided list (`flag = 0`).
            - Logs warning information for unrecognized (`tally['zero']`) and
            ambiguous (`tally['many']`) identifiers,
            based on the `allow_ambiguous_groups` setting.
            - Logs the final count of retained groups and the number of groups
            discarded.
            - Increments the `group` filter counter for the database.

        Returns:
        - None. The method performs a set intersection of groups in the
        specified table and logs the outcome.

        This function is useful for managing a set of groups in a database
        table, performing an intersection of existing records with a provided
        list to keep only valid matches.
        """
        # names=[ (namespace,name,extra), ... ]
        if not self._inputFilters[db]["group"]:
            return self.unionInputGroups(db, names, errorCallback)
        self.logPush("reducing %s group filter ...\n" % (db,))
        cursor = self._loki._db.cursor()

        self.prepareTableForQuery(db, "group")
        cursor.execute("UPDATE `%s`.`group` SET flag = 0" % db)
        numBefore = cursor.getconnection().changes()
        maxMatch = None if self._options.allow_ambiguous_groups == "yes" else 1
        tally = dict()
        sql = "UPDATE `%s`.`group` SET flag = 1 WHERE group_id = ?4" % db
        cursor.executemany(
            sql,
            self._loki.generateGroupIDsByIdentifiers(
                names,
                minMatch=1,
                maxMatch=maxMatch,
                tally=tally,
                errorCallback=errorCallback,
            ),
        )
        cursor.execute("DELETE FROM `%s`.`group` WHERE flag = 0" % db)
        numDrop = cursor.getconnection().changes()
        if tally["zero"]:
            self.warn(
                "WARNING: ignored %d unrecognized group identifier(s)\n"
                % tally["zero"]  # noqa E501
            )
        if tally["many"]:
            if self._options.allow_ambiguous_groups == "yes":
                self.warn(
                    "WARNING: kept multiple results for %d ambiguous group identifier(s)\n"  # noqa E501
                    % tally["many"]
                )
            else:
                self.warn(
                    "WARNING: ignored %d ambiguous group identifier(s)\n"
                    % tally["many"]
                )
        self.logPop(
            "... OK: kept %d groups (%d dropped)\n"
            % (numBefore - numDrop, numDrop)  # noqa E501
        )

        self._inputFilters[db]["group"] += 1

    def unionInputGroupSearch(self, db, texts):
        """
        Adds a list of groups to a filter in a specified database table, using
        text-based search to identify and insert relevant groups.

        Parameters:
        - db: Name of the database where the `group` table is located.
        - texts: List of tuples `[(text, extra), ...]`, where:
            - `text` is the search term used to identify groups,
            - `extra` provides additional information for each search term.

        Operation:
        - Logs the beginning of the group addition process using text-based
        search.
        - Calls `prepareTableForUpdate` to optimize insertion by temporarily
        removing indexes from the `group` table.
        - Defines an SQL `INSERT` statement to insert groups found by text-
        based search into the `group` table.
        - Uses `generateGroupIDsBySearch` to generate `group_id`s based on the
        search terms provided in `texts`.
        - Inserts the groups into the `group` table with `group_id`, `label`,
        and `extra` for each matched text.
        - Counts the number of valid groups added (`numAdd`) and logs the
        final result.
        - Increments the filter counter for `group` in the database.

        Returns:
        - None. The method inserts groups into the specified table and logs
        details about the insertion process.

        This function enables the addition of groups to a filter based on a
        text-based search, facilitating the integration of relevant data into
        the database table and applying an optimized insertion process for
        handling large data volumes.
        """
        # texts=[ (text,extra), ... ]
        self.logPush("adding to %s group filter by text search ...\n" % (db,))
        cursor = self._loki._db.cursor()

        self.prepareTableForUpdate(db, "group")
        sql = (
            "INSERT INTO `%s`.`group` (extra,label,group_id) VALUES (?1,?2,?3); SELECT 1"  # noqa E501
            % db
        )
        numAdd = 0
        for row in cursor.executemany(
            sql, self._loki.generateGroupIDsBySearch(texts)
        ):  # noqa E501
            numAdd += 1
        self.logPop("... OK: added %d groups\n" % numAdd)

        self._inputFilters[db]["group"] += 1

    def intersectInputGroupSearch(self, db, texts):
        """
        Reduces a group filter in a specified database table, keeping only the
        groups that match a provided list based on textual search. If the group
        filter is not initialized, performs a union instead of an intersection.

        Parameters:
        - db: Name of the database where the `group` table is located.
        - texts: List of tuples `[(text, extra), ...]`, where:
            - `text` is the search term for matching groups,
            - `extra` contains additional information for each search term.

        Operation:
        - If the `group` filter is not yet initialized
        (`_inputFilters[db]['group']` is 0), calls `unionInputGroupSearch` to
        create the filter with all groups found in the text search.
        - Otherwise:
            - Logs the start of the group filter reduction process.
            - Calls `prepareTableForQuery` to ensure the `group` table is
            ready for querying.
            - Sets all groups in the table as "not kept" (`flag = 0`).
            - Counts the number of groups before reduction (`numBefore`).
            - Uses `generateGroupIDsBySearch` to retrieve `group_id`s matching
            the provided texts.
            - Updates `flag = 1` only for groups with a `group_id` matching
            the search results.
            - Deletes groups that do not match the search criteria (flag = 0).
            - Counts the number of groups removed (`numDrop`).
            - Logs the final number of retained groups and the count of
            discarded groups.
            - Increments the group filter counter for the database.

        Returns:
        - None. This method performs an intersection of groups in the
        specified table based on a list of search terms and logs the results.

        This function is helpful for managing and reducing a set of groups in a
        database table by intersecting the existing records based on a list of
        search terms, retaining only valid matches.
        """
        # texts=[ (text,extra), ... ]
        if not self._inputFilters[db]["group"]:
            return self.unionInputGroupSearch(db, texts)
        self.logPush("reducing %s group filter by text search ...\n" % (db,))
        cursor = self._loki._db.cursor()

        self.prepareTableForQuery(db, "group")
        cursor.execute("UPDATE `%s`.`group` SET flag = 0" % db)
        numBefore = cursor.getconnection().changes()
        sql = "UPDATE `%s`.`group` SET flag = 1 WHERE group_id = ?3" % db
        cursor.executemany(sql, self._loki.generateGroupIDsBySearch(texts))
        cursor.execute("DELETE FROM `%s`.`group` WHERE flag = 0" % db)
        numDrop = cursor.getconnection().changes()
        self.logPop(
            "... OK: kept %d groups (%d dropped)\n"
            % (numBefore - numDrop, numDrop)  # noqa E501
        )

        self._inputFilters[db]["group"] += 1
