from typing import Union
from typing import List


def _filter_rows(include_rows: Union[None, int, List[int]],
                 exclude_rows: Union[None, int, List[int]],
                 rows_number: int) -> List[int]:
    """
    Filters row indices given the include and exclude lists.

    For the exceptions description please see the documentation of the
    :func`fatf.transparency.model.feature_influence.
    individual_conditional_expectation` function.

    Parameters
    ----------
    include_rows: Union[None, integer, List[integer]]
        Indices of rows that will be included. If this parameter is specified,
        only the selected rows will be included. If additionally
        ``exclude_rows`` is specified the selected rows will be a set
        difference between the two. This parameter can either be a *list* of
        indices, a single index (integer) or ``None`` -- all indices.
    exclude_rows: Union[None, integer, List[integer]]
        Indices of rows to be excluded. If this parameter is specified and
        ``include_rows`` is not, these indices will be excluded from all of the
        rows. If both include and exclude parameters are specified, the
        included rows will be a set difference of the two. This parameter can
        either be a *list* of indices, a single index (integer) or ``None`` --
        no indices.
    rows_number : integer
        The total number of rows from which these indices will be included/
        excluded.

    Returns
    -------
    filtered_rows : List[integer]
        Sorted list of row indices computed as a set difference between the
        ``include_rows`` parameter (or the whole set of indices for the array
        if the parameter is ``None``) and the ``exclude_rows`` parameters (or
        an empty set of indices if the parameter is ``None``).
    """

    def within_bounds(error_type, row_index):
        if row_index < 0 or row_index >= rows_number:
            raise ValueError('{} rows element {} is out of bounds. There are '
                             'only {} rows in the input dataset.'.format(
                                 error_type, row_index, rows_number))
        return True

    assert isinstance(rows_number, int) and rows_number > 0, \
        'Rows number must be a positive integer.'

    if include_rows is None:
        include_rows = list(range(rows_number))
    elif isinstance(include_rows, int):
        assert within_bounds('Include', include_rows), 'Index within bounds.'
        include_rows = [include_rows]
    elif isinstance(include_rows, list):
        for i in include_rows:
            if not isinstance(i, int):
                raise TypeError(
                    'Include rows element *{}* is not an integer.'.format(i))
            assert within_bounds('Include', i), 'Every index is within bounds.'
    else:
        raise TypeError('The include_rows parameters must be either None or a '
                        'list of integers indicating which rows should be '
                        'included in the computation.')

    if exclude_rows is None:
        exclude_rows = []
    elif isinstance(exclude_rows, int):
        assert within_bounds('Exclude', exclude_rows), 'Index within bounds.'
        exclude_rows = [exclude_rows]
    elif isinstance(exclude_rows, list):
        for i in exclude_rows:
            if not isinstance(i, int):
                raise TypeError(
                    'Exclude rows element *{}* is not an integer.'.format(i))
            assert within_bounds('Exclude', i), 'Every index is within bounds.'
    else:
        raise TypeError('The exclude_rows parameters must be either None or a '
                        'list of integers indicating which rows should be '
                        'excluded in the computation.')

    filtered_rows = sorted(set(include_rows).difference(exclude_rows))
    return filtered_rows