# Path: src/cutadapt/filters.py
# class Predicate(ABC):
#     @abstractmethod
#     def test(self, read, info: ModificationInfo) -> bool:
#         """
#         Return True if the filtering criterion matches.
#         """
# 
#     @classmethod
#     def descriptive_identifier(cls) -> str:
#         """
#         Return a short name for this predicate based on the class name such as "too_long",
#         "too_many_expected_errors".
#         This is used as identifier in the JSON report.
#         """
#         return "".join(
#             ("_" + ch.lower() if ch.isupper() else ch) for ch in cls.__name__
#         )[1:]
# 
# Path: src/cutadapt/modifiers.py
# class ModificationInfo:
#     """
#     An object of this class is created for each read that passes through the pipeline.
#     Any information (except the read itself) that needs to be passed from one modifier
#     to one later in the pipeline or from one modifier to the filters is recorded here.
#     """
# 
#     __slots__ = ["matches", "original_read", "cut_prefix", "cut_suffix", "is_rc"]
# 
#     def __init__(self, read):
#         self.matches = []  # type: List[Match]
#         self.original_read = read
#         self.cut_prefix = None
#         self.cut_suffix = None
#         self.is_rc = None
# 
#     def __repr__(self):
#         return (
#             "ModificationInfo("
#             f"matches={self.matches!r}, "
#             f"original_read={self.original_read}, "
#             f"cut_prefix={self.cut_prefix}, "
#             f"cut_suffix={self.cut_suffix}, "
#             f"is_rc={self.is_rc})"
#         )
# 
# Path: src/cutadapt/statistics.py
# class ReadLengthStatistics:
#     """
#     Keep track of the lengths of written reads or read pairs
#     """
# 
#     def __init__(self) -> None:
#         # It would be more natural to use a Counter, but a
#         # defaultdict is much faster
#         self._written_lengths1: DefaultDict[int, int] = defaultdict(int)
#         self._written_lengths2: DefaultDict[int, int] = defaultdict(int)
# 
#     def update(self, read) -> None:
#         """Add a single-end read to the statistics"""
#         self._written_lengths1[len(read)] += 1
# 
#     def update2(self, read1, read2) -> None:
#         """Add a paired-end read to the statistics"""
#         self._written_lengths1[len(read1)] += 1
#         self._written_lengths2[len(read2)] += 1
# 
#     def written_reads(self) -> int:
#         """Return number of written reads or read pairs"""
#         return sum(self._written_lengths1.values())
# 
#     def written_bp(self) -> Tuple[int, int]:
#         return (
#             self._compute_total_bp(self._written_lengths1),
#             self._compute_total_bp(self._written_lengths2),
#         )
# 
#     def written_lengths(self) -> Tuple[Counter, Counter]:
#         return (Counter(self._written_lengths1), Counter(self._written_lengths2))
# 
#     @staticmethod
#     def _compute_total_bp(counts: DefaultDict[int, int]) -> int:
#         return sum(length * count for length, count in counts.items())
# 
#     def __iadd__(self, other):
#         written_lengths1, written_lengths2 = other.written_lengths()
#         for length, count in written_lengths1.items():
#             self._written_lengths1[length] += count
#         for length, count in written_lengths2.items():
#             self._written_lengths2[length] += count
#         return self
# 
# Path: src/cutadapt/utils.py
# def reverse_complemented_sequence(sequence: dnaio.Sequence):
#     if sequence.qualities is None:
#         qualities = None
#     else:
#         qualities = sequence.qualities[::-1]
#     return dnaio.Sequence(
#         sequence.name, reverse_complement(sequence.sequence), qualities
#     )


from abc import ABC, abstractmethod
from typing import Tuple, Dict, Optional, Any
from .filters import Predicate
from .modifiers import ModificationInfo
from .statistics import ReadLengthStatistics
from .utils import reverse_complemented_sequence

1. First RestFileWriter, InfoFileWriter, WildcardFileWriter because
   they should see all reads before filtering.
2. Filters come next. These are implemented as SingleEndFilter or PairedEndFilter
   instances with an appropriate Predicate. Filters can optionally send each
   consumed/filtered read to an output file.
3. The last pipeline step should be one of the "Sinks", which consume all reads.
   Demultiplexers are sinks, for example.
"""



# Constants used when returning from a step’s __call__ method to improve
# readability (it is unintuitive that "return True" means "discard the read").
DISCARD = True
KEEP = False


class SingleEndStep(ABC):
    @abstractmethod
    def __call__(self, read, info: ModificationInfo) -> bool:
        """
        Process a single read. Return True if the read has been consumed
        (and should thus not be passed on to subsequent steps).
        """


class PairedEndStep(ABC):
    @abstractmethod
    def __call__(

# Path: src/cutadapt/filters.py
# class Predicate(ABC):
#     @abstractmethod
#     def test(self, read, info: ModificationInfo) -> bool:
#         """
#         Return True if the filtering criterion matches.
#         """
# 
#     @classmethod
#     def descriptive_identifier(cls) -> str:
#         """
#         Return a short name for this predicate based on the class name such as "too_long",
#         "too_many_expected_errors".
#         This is used as identifier in the JSON report.
#         """
#         return "".join(
#             ("_" + ch.lower() if ch.isupper() else ch) for ch in cls.__name__
#         )[1:]
# 
# Path: src/cutadapt/modifiers.py
# class ModificationInfo:
#     """
#     An object of this class is created for each read that passes through the pipeline.
#     Any information (except the read itself) that needs to be passed from one modifier
#     to one later in the pipeline or from one modifier to the filters is recorded here.
#     """
# 
#     __slots__ = ["matches", "original_read", "cut_prefix", "cut_suffix", "is_rc"]
# 
#     def __init__(self, read):
#         self.matches = []  # type: List[Match]
#         self.original_read = read
#         self.cut_prefix = None
#         self.cut_suffix = None
#         self.is_rc = None
# 
#     def __repr__(self):
#         return (
#             "ModificationInfo("
#             f"matches={self.matches!r}, "
#             f"original_read={self.original_read}, "
#             f"cut_prefix={self.cut_prefix}, "
#             f"cut_suffix={self.cut_suffix}, "
#             f"is_rc={self.is_rc})"
#         )
# 
# Path: src/cutadapt/statistics.py
# class ReadLengthStatistics:
#     """
#     Keep track of the lengths of written reads or read pairs
#     """
# 
#     def __init__(self) -> None:
#         # It would be more natural to use a Counter, but a
#         # defaultdict is much faster
#         self._written_lengths1: DefaultDict[int, int] = defaultdict(int)
#         self._written_lengths2: DefaultDict[int, int] = defaultdict(int)
# 
#     def update(self, read) -> None:
#         """Add a single-end read to the statistics"""
#         self._written_lengths1[len(read)] += 1
# 
#     def update2(self, read1, read2) -> None:
#         """Add a paired-end read to the statistics"""
#         self._written_lengths1[len(read1)] += 1
#         self._written_lengths2[len(read2)] += 1
# 
#     def written_reads(self) -> int:
#         """Return number of written reads or read pairs"""
#         return sum(self._written_lengths1.values())
# 
#     def written_bp(self) -> Tuple[int, int]:
#         return (
#             self._compute_total_bp(self._written_lengths1),
#             self._compute_total_bp(self._written_lengths2),
#         )
# 
#     def written_lengths(self) -> Tuple[Counter, Counter]:
#         return (Counter(self._written_lengths1), Counter(self._written_lengths2))
# 
#     @staticmethod
#     def _compute_total_bp(counts: DefaultDict[int, int]) -> int:
#         return sum(length * count for length, count in counts.items())
# 
#     def __iadd__(self, other):
#         written_lengths1, written_lengths2 = other.written_lengths()
#         for length, count in written_lengths1.items():
#             self._written_lengths1[length] += count
#         for length, count in written_lengths2.items():
#             self._written_lengths2[length] += count
#         return self
# 
# Path: src/cutadapt/utils.py
# def reverse_complemented_sequence(sequence: dnaio.Sequence):
#     if sequence.qualities is None:
#         qualities = None
#     else:
#         qualities = sequence.qualities[::-1]
#     return dnaio.Sequence(
#         sequence.name, reverse_complement(sequence.sequence), qualities
#     )


# Path: src/cutadapt/steps.py
from abc import ABC, abstractmethod
from typing import Tuple, Dict, Optional, Any
from .filters import Predicate
from .modifiers import ModificationInfo
from .statistics import ReadLengthStatistics
from .utils import reverse_complemented_sequence


1. First RestFileWriter, InfoFileWriter, WildcardFileWriter because
   they should see all reads before filtering.
2. Filters come next. These are implemented as SingleEndFilter or PairedEndFilter
   instances with an appropriate Predicate. Filters can optionally send each
   consumed/filtered read to an output file.
3. The last pipeline step should be one of the "Sinks", which consume all reads.
   Demultiplexers are sinks, for example.
"""



# Constants used when returning from a step’s __call__ method to improve
# readability (it is unintuitive that "return True" means "discard the read").
DISCARD = True
KEEP = False


class SingleEndStep(ABC):
    @abstractmethod
    def __call__(self, read, info: ModificationInfo) -> bool:
        """
        Process a single read. Return True if the read has been consumed
        (and should thus not be passed on to subsequent steps).
        """


class PairedEndStep(ABC):
    @abstractmethod
    def __call__(

        self, read1, read2, info1: ModificationInfo, info2: ModificationInfo