Writes sample and library information to a set of files for a given
        set of lanes.

        **BARCODE PARAMETERS FILES**: Store information regarding the sample
        index sequences, sample index names, and, optionally, the library name.
        These files are used by Picard's `CollectIlluminaBasecallingMetrics`
        and Picard's `ExtractIlluminaBarcodes`. The output tab-seperated files
        are formatted as:

            ``<directory>/barcode_params.<lane>.txt``

        **LIBRARY PARAMETERS FILES**: Store information regarding the sample
        index sequences, sample index names, and optionally sample library and
        descriptions. A path to the resulting demultiplexed BAM file is also
        stored which is used by Picard's `IlluminaBasecallsToSam`. The output
        tab-seperated files are formatted as:

            ``<directory>/library_params.<lane>.txt``

        The format of the BAM file output paths in the library parameter files
        are formatted as:

            ``<bam_prefix>/<Sample_Name>.<Sample_Library>/<Sample_Name>.<index><index2>.<lane>.bam``

        Two files will be written to ``directory`` for all ``lanes`` specified.
        If the path to ``directory`` does not exist, it will be created.

        Args:
            directory: File path to the directory to write the parameter files.
            bam_prefix: Where the demultiplexed BAMs should be written.
            lanes: The lanes to write basecalling parameters for.

def to_picard_basecalling_params(
        self,
        directory: Union[str, Path],
        bam_prefix: Union[str, Path],
        lanes: Union[int, List[int]],
    ) -> None:
        """Writes sample and library information to a set of files for a given
        set of lanes.

        **BARCODE PARAMETERS FILES**: Store information regarding the sample
        index sequences, sample index names, and, optionally, the library name.
        These files are used by Picard's `CollectIlluminaBasecallingMetrics`
        and Picard's `ExtractIlluminaBarcodes`. The output tab-seperated files
        are formatted as:

            ``<directory>/barcode_params.<lane>.txt``

        **LIBRARY PARAMETERS FILES**: Store information regarding the sample
        index sequences, sample index names, and optionally sample library and
        descriptions. A path to the resulting demultiplexed BAM file is also
        stored which is used by Picard's `IlluminaBasecallsToSam`. The output
        tab-seperated files are formatted as:

            ``<directory>/library_params.<lane>.txt``

        The format of the BAM file output paths in the library parameter files
        are formatted as:

            ``<bam_prefix>/<Sample_Name>.<Sample_Library>/<Sample_Name>.<index><index2>.<lane>.bam``

        Two files will be written to ``directory`` for all ``lanes`` specified.
        If the path to ``directory`` does not exist, it will be created.

        Args:
            directory: File path to the directory to write the parameter files.
            bam_prefix: Where the demultiplexed BAMs should be written.
            lanes: The lanes to write basecalling parameters for.

        """
        if len(self.samples) == 0:
            raise ValueError('No samples in sample sheet')
        if not (
            isinstance(lanes, int)
            or isinstance(lanes, (list, tuple))
            and len(lanes) > 0
            and all(isinstance(lane, int) for lane in lanes)
        ):
            raise ValueError(f'Lanes must be an int or list of ints: {lanes}')
        if len(set(len(sample.index or '') for sample in self.samples)) != 1:
            raise ValueError('I7 indexes have differing lengths.')
        if len(set(len(sample.index2 or '') for sample in self.samples)) != 1:
            raise ValueError('I5 indexes have differing lengths.')
        for attr in ('Sample_Name', 'Library_ID', 'index'):
            if any(getattr(sample, attr) is None for sample in self.samples):
                raise ValueError(
                    'Samples must have at least `Sample_Name`, '
                    '`Sample_Library`, and `index` attributes'
                )

        # Make lanes iterable if only an int was provided.
        lanes = [lanes] if isinstance(lanes, int) else lanes

        # Resolve path to basecalling parameter files.
        prefix = Path(directory).expanduser().resolve()
        prefix.mkdir(exist_ok=True, parents=True)

        # Promote bam_prefix to Path object.
        bam_prefix = Path(bam_prefix).expanduser().resolve()

        # Both headers are one column larger if an ``index2`` attribute is
        # present on all samples. Use list splatting to unpack the options.
        barcode_header = [
            *(
                ['barcode_sequence_1']
                if not self.samples_have_index2
                else ['barcode_sequence_1', 'barcode_sequence_2']
            ),
            'barcode_name',
            'library_name',
        ]
        # TODO: Remove description if none is provided on all samples.
        library_header = [
            *(
                ['BARCODE_1']
                if not self.samples_have_index2
                else ['BARCODE_1', 'BARCODE_2']
            ),
            'OUTPUT',
            'SAMPLE_ALIAS',
            'LIBRARY_NAME',
            'DS',
        ]

        for lane in lanes:
            barcode_out = prefix / f'barcode_params.{lane}.txt'
            library_out = prefix / f'library_params.{lane}.txt'

            # Enter into a writing context for both library and barcode params.
            with ExitStack() as stack:
                barcode_writer = csv.writer(
                    stack.enter_context(barcode_out.open('w')), delimiter='\t'
                )
                library_writer = csv.writer(
                    stack.enter_context(library_out.open('w')), delimiter='\t'
                )

                barcode_writer.writerow(barcode_header)
                library_writer.writerow(library_header)

                for sample in self.samples:
                    # The long name of a sample is a combination of the sample
                    # ID and the sample library.
                    long_name = '.'.join(
                        [sample.Sample_Name, sample.Library_ID]
                    )

                    # The barcode name is all sample indexes concatenated.
                    barcode_name = sample.index + (sample.index2 or '')
                    library_name = sample.Library_ID or ''

                    # Assemble the path to the future BAM file.
                    bam_file = (
                        bam_prefix
                        / long_name
                        / f'{sample.Sample_Name}.{barcode_name}.{lane}.bam'
                    )

                    # Use list splatting to build the contents of the library
                    # and barcodes parameter files.
                    barcode_line = [
                        *(
                            [sample.index]
                            if not self.samples_have_index2
                            else [sample.index, sample.index2]
                        ),
                        barcode_name,
                        library_name,
                    ]

                    library_line = [
                        *(
                            [sample.index]
                            if not self.samples_have_index2
                            else [sample.index, sample.index2]
                        ),
                        bam_file,
                        sample.Sample_Name,
                        sample.Library_ID,
                        sample.Description or '',
                    ]

                    barcode_writer.writerow(map(str, barcode_line))
                    library_writer.writerow(map(str, library_line))

                # Dempultiplexing relys on an umatched file so append that,
                # but only to the library parameters file.
                unmatched_file = bam_prefix / f'unmatched.{lane}.bam'
                library_line = [
                    *(['N'] if not self.samples_have_index2 else ['N', 'N']),
                    unmatched_file,
                    'unmatched',
                    'unmatchedunmatched',
                    '',
                ]
                library_writer.writerow(map(str, library_line))