// -----------------------------------------------------------------------------------------------------
// Copyright (c) 2006-2022, Knut Reinert & Freie Universität Berlin
// Copyright (c) 2016-2022, Knut Reinert & MPI für molekulare Genetik
// This file may be used, modified and/or redistributed under the terms of the 3-clause BSD-License
// shipped with this file and also available at: https://github.com/seqan/seqan3/blob/master/LICENSE.md
// -----------------------------------------------------------------------------------------------------

#include <gtest/gtest.h>

#include <algorithm>
#include <iterator>
#include <ranges>
#include <sstream>

#include <seqan3/alphabet/detail/debug_stream_alphabet.hpp>
#include <seqan3/alphabet/quality/phred42.hpp>
#include <seqan3/io/sam_file/input.hpp>
#include <seqan3/test/expect_range_eq.hpp>
#include <seqan3/test/tmp_directory.hpp>
#include <seqan3/utility/views/convert.hpp>

using seqan3::operator""_dna4;
using seqan3::operator""_dna5;
using seqan3::operator""_phred42;

using default_fields = seqan3::fields<seqan3::field::seq, seqan3::field::id, seqan3::field::qual>;

TEST(sam_file_input_iterator, concepts)
{
    using it_t = typename seqan3::sam_file_input<>::iterator;
    using sen_t = typename seqan3::sam_file_input<>::sentinel;

    EXPECT_TRUE((std::input_iterator<it_t>));
    EXPECT_TRUE((std::sentinel_for<sen_t, it_t>));
}

struct sam_file_input_f : public ::testing::Test
{
    std::string input =
        R"(@HD	VN:1.6	SO:unknown	GO:none
@SQ	SN:ref	LN:34
@PG	ID:prog1	PN:cool_program
@CO	This is a comment.
read1	41	ref	1	61	1S1M1D2M	ref	10	300	ACGT	!##$	AS:i:2	NM:i:7
read2	42	ref	2	62	7M1D1M1S	ref	10	300	AGGCTGNAG	!##$&'()*	xy:B:S,3,4,5
read3	43	ref	3	63	1S1M1D4M1D1M1S	ref	10	300	GGAGTATA	!!*+,-./
)";

    std::vector<seqan3::dna5_vector> seq_comp{"ACGT"_dna5, "AGGCTGNAG"_dna5, "GGAGTATA"_dna5};

    std::vector<std::string> id_comp{"read1", "read2", "read3"};

    std::vector<std::vector<seqan3::phred42>> qual_comp{
        {"!##$"_phred42},
        {"!##$&'()*"_phred42},
        {"!!*+,-./"_phred42},
    };
};

TEST_F(sam_file_input_f, concepts)
{
    using t = seqan3::sam_file_input<>;
    EXPECT_TRUE((std::ranges::input_range<t>));

    using ct = seqan3::sam_file_input<> const;
    // not const-iterable
    EXPECT_FALSE((std::ranges::input_range<ct>));
}

TEST_F(sam_file_input_f, construct_by_filename)
{
    /* just the filename */
    {
        seqan3::test::tmp_directory tmp{};
        auto filename = tmp.path() / "sam_file_input_constructor.sam";

        {
            std::ofstream filecreator{filename, std::ios::out | std::ios::binary};
        }

        EXPECT_NO_THROW(seqan3::sam_file_input<>{filename});
    }

    // correct format check is done by tests of that format

    /* wrong extension */
    {
        seqan3::test::tmp_directory tmp{};
        auto filename = tmp.path() / "sam_file_input_constructor.xyz";
        std::ofstream filecreator{filename, std::ios::out | std::ios::binary};
        EXPECT_THROW(seqan3::sam_file_input<>{filename}, seqan3::unhandled_extension_error);
    }

    /* non-existent file*/
    {
        EXPECT_THROW(seqan3::sam_file_input<>{"/dev/nonexistent/foobarOOO"}, seqan3::file_open_error);
    }
    /* non-existent file with reference information*/
    {
        std::vector<std::string> ref_ids{"ref1", "ref2"};
        std::vector<seqan3::dna4_vector> ref_seqs{"ACTG"_dna4, "ACTG"_dna4};
        EXPECT_THROW((seqan3::sam_file_input{"/dev/nonexistent/foobarOOO", ref_ids, ref_seqs}),
                     seqan3::file_open_error);
    }

    /* filename + fields */
    {
        seqan3::test::tmp_directory tmp{};
        auto filename = tmp.path() / "sam_file_input_constructor.sam";

        {
            std::ofstream filecreator{filename, std::ios::out | std::ios::binary};
        }

        using fields_seq = seqan3::fields<seqan3::field::seq>;

        EXPECT_NO_THROW((seqan3::sam_file_input<seqan3::sam_file_input_default_traits<>,
                                                fields_seq,
                                                seqan3::type_list<seqan3::format_sam>>{filename, fields_seq{}}));
    }
}

TEST_F(sam_file_input_f, construct_from_stream)
{
    /* stream + format_tag */
    EXPECT_NO_THROW((seqan3::sam_file_input<seqan3::sam_file_input_default_traits<>,
                                            default_fields,
                                            seqan3::type_list<seqan3::format_sam>>{std::istringstream{input},
                                                                                   seqan3::format_sam{}}));

    /* stream + format_tag + fields */
    EXPECT_NO_THROW((seqan3::sam_file_input<seqan3::sam_file_input_default_traits<>,
                                            default_fields,
                                            seqan3::type_list<seqan3::format_sam>>{std::istringstream{input},
                                                                                   seqan3::format_sam{},
                                                                                   default_fields{}}));
}

TEST_F(sam_file_input_f, default_template_args_and_deduction_guides)
{
    using comp0 = seqan3::sam_file_input_default_traits<>;
    using comp1 = seqan3::fields<seqan3::field::seq,
                                 seqan3::field::id,
                                 seqan3::field::ref_id,
                                 seqan3::field::ref_offset,
                                 seqan3::field::cigar,
                                 seqan3::field::mapq,
                                 seqan3::field::qual,
                                 seqan3::field::flag,
                                 seqan3::field::mate,
                                 seqan3::field::tags,
                                 seqan3::field::header_ptr>;
    using comp2 = seqan3::type_list<seqan3::format_sam, seqan3::format_bam>;
    using comp3 = char;

    /* default template args */
    {
        using t = seqan3::sam_file_input<>;
        EXPECT_TRUE((std::is_same_v<typename t::traits_type, comp0>));
        EXPECT_TRUE((std::is_same_v<typename t::selected_field_ids, comp1>));
        EXPECT_TRUE((std::is_same_v<typename t::valid_formats, comp2>));
        EXPECT_TRUE((std::is_same_v<typename t::stream_char_type, comp3>));
    }

    /* guided filename constructor */
    {
        seqan3::test::tmp_directory tmp{};
        auto filename = tmp.path() / "sam_file_input_constructor.sam";

        {
            std::ofstream filecreator{filename, std::ios::out | std::ios::binary};
        }

        seqan3::sam_file_input fin{filename};

        using t = decltype(fin);
        EXPECT_TRUE((std::is_same_v<typename t::traits_type, comp0>));
        EXPECT_TRUE((std::is_same_v<typename t::selected_field_ids, comp1>));
        EXPECT_TRUE((std::is_same_v<typename t::valid_formats, comp2>));
        EXPECT_TRUE((std::is_same_v<typename t::stream_char_type, comp3>));
    }

    /* guided filename constructor + custom fields */
    {
        seqan3::test::tmp_directory tmp{};
        auto filename = tmp.path() / "sam_file_input_constructor.sam";

        {
            std::ofstream filecreator{filename, std::ios::out | std::ios::binary};
        }

        seqan3::sam_file_input fin{filename, seqan3::fields<seqan3::field::seq>{}};

        using t = decltype(fin);
        EXPECT_TRUE((std::is_same_v<typename t::traits_type, comp0>));
        EXPECT_TRUE((std::is_same_v<typename t::selected_field_ids, seqan3::fields<seqan3::field::seq>>));
        EXPECT_TRUE((std::is_same_v<typename t::valid_formats, comp2>));
        EXPECT_TRUE((std::is_same_v<typename t::stream_char_type, comp3>));
    }

    /* guided stream constructor */
    {
        std::istringstream ext{input};
        seqan3::sam_file_input fin{ext, seqan3::format_sam{}};

        using t = decltype(fin);
        EXPECT_TRUE((std::is_same_v<typename t::traits_type, comp0>));
        EXPECT_TRUE((std::is_same_v<typename t::selected_field_ids, comp1>));
        EXPECT_TRUE((std::is_same_v<typename t::valid_formats, seqan3::type_list<seqan3::format_sam>>));
        EXPECT_TRUE((std::is_same_v<typename t::stream_char_type, comp3>));
    }

    /* guided stream temporary constructor */
    {
        seqan3::sam_file_input fin{std::istringstream{input}, seqan3::format_sam{}};

        using t = decltype(fin);
        EXPECT_TRUE((std::is_same_v<typename t::traits_type, comp0>));
        EXPECT_TRUE((std::is_same_v<typename t::selected_field_ids, comp1>));
        EXPECT_TRUE((std::is_same_v<typename t::valid_formats, seqan3::type_list<seqan3::format_sam>>));
        EXPECT_TRUE((std::is_same_v<typename t::stream_char_type, comp3>));
    }
}

TEST_F(sam_file_input_f, empty_file)
{
    seqan3::test::tmp_directory tmp{};
    auto filename = tmp.path() / "empty.sam";

    std::ofstream filecreator{filename, std::ios::out | std::ios::binary};

    seqan3::sam_file_input fin{filename};

    EXPECT_EQ(fin.begin(), fin.end());
}

TEST_F(sam_file_input_f, empty_stream)
{
    seqan3::sam_file_input fin{std::istringstream{std::string{}}, seqan3::format_sam{}};

    EXPECT_EQ(fin.begin(), fin.end());
}

TEST_F(sam_file_input_f, record_reading)
{
    /* record based reading */
    seqan3::sam_file_input fin{std::istringstream{input}, seqan3::format_sam{}};

    size_t counter = 0;
    for (auto & rec : fin)
    {
        EXPECT_RANGE_EQ(rec.sequence(), seq_comp[counter]);
        EXPECT_RANGE_EQ(rec.id(), id_comp[counter]);
        EXPECT_RANGE_EQ(rec.base_qualities(), qual_comp[counter]);

        ++counter;
    }

    EXPECT_EQ(counter, 3u);
}

TEST_F(sam_file_input_f, record_reading_custom_fields)
{
    /* record based reading */
    seqan3::sam_file_input fin{std::istringstream{input},
                               seqan3::format_sam{},
                               seqan3::fields<seqan3::field::id, seqan3::field::seq>{}};

    size_t counter = 0;
    for (auto & [id, seq] : fin)
    {
        EXPECT_RANGE_EQ(seq, seq_comp[counter]);
        EXPECT_RANGE_EQ(id, id_comp[counter]);

        ++counter;
    }

    EXPECT_EQ(counter, 3u);
}

TEST_F(sam_file_input_f, file_view)
{
    seqan3::sam_file_input fin{std::istringstream{input}, seqan3::format_sam{}};

    auto minimum_length_filter = std::views::filter(
        [](auto const & rec)
        {
            return size(rec.sequence()) >= 5;
        });

    size_t counter = 1; // the first record will be filtered out
    for (auto & rec : fin | minimum_length_filter)
    {
        EXPECT_RANGE_EQ(rec.sequence(), seq_comp[counter]);
        EXPECT_RANGE_EQ(rec.id(), id_comp[counter]);
        EXPECT_RANGE_EQ(rec.base_qualities(), qual_comp[counter]);

        ++counter;
    }

    EXPECT_EQ(counter, 3u);
}

// ----------------------------------------------------------------------------
// decompression
// ----------------------------------------------------------------------------

template <typename fixture_t, typename input_file_t>
void decompression_impl(fixture_t & fix, input_file_t & fin)
{
    size_t counter = 0;
    for (auto & rec : fin)
    {
        EXPECT_RANGE_EQ(rec.sequence(), fix.seq_comp[counter]);
        EXPECT_RANGE_EQ(rec.id(), fix.id_comp[counter]);
        EXPECT_RANGE_EQ(rec.base_qualities(), fix.qual_comp[counter]);

        ++counter;
    }

    EXPECT_EQ(counter, 3u);
}

#if defined(SEQAN3_HAS_ZLIB)

std::string input_gz{
    '\x1F', '\x8B', '\x08', '\x08', '\x9D', '\x5B', '\x38', '\x5C', '\x00', '\x03', '\x74', '\x65', '\x73', '\x74',
    '\x2E', '\x73', '\x61', '\x6D', '\x00', '\x6D', '\xCE', '\xBF', '\x0A', '\xC2', '\x30', '\x10', '\xC7', '\xF1',
    '\xF9', '\xD7', '\xB7', '\x08', '\x15', '\xFF', '\xD4', '\xA8', '\xB9', '\x24', '\xB6', '\x90', '\x2D', '\x56',
    '\xB8', '\x29', '\x5D', '\x92', '\x17', '\x28', '\x58', '\xC1', '\x35', '\x93', '\xBE', '\xBD', '\x21', '\xBA',
    '\x08', '\x4E', '\x07', '\xC7', '\x7D', '\xBE', '\x5C', '\x5E', '\xE6', '\x1B', '\xC1', '\x12', '\xF2', '\x72',
    '\x07', '\xA1', '\x27', '\x50', '\xA4', '\x40', '\x57', '\x1D', '\x3E', '\x1B', '\x05', '\xA3', '\x14', '\xFC',
    '\xC8', '\x09', '\xA2', '\x6D', '\x57', '\xF0', '\xD1', '\x3D', '\x9C', '\xC6', '\x14', '\xCA', '\x18', '\x9A',
    '\x5C', '\xB4', '\x86', '\xD5', '\xF5', '\x56', '\xA3', '\xD7', '\x18', '\x8A', '\x2D', '\x3E', '\xFE', '\x68',
    '\xE6', '\x31', '\xF1', '\xE4', '\xB9', '\x26', '\xD6', '\x9B', '\xED', '\xAE', '\xC3', '\xF3', '\xE5', '\x2E',
    '\x2E', '\x4A', '\x23', '\xAD', '\x3C', '\xD7', '\x8C', '\x81', '\x35', '\x15', '\x19', '\xF4', '\xE6', '\xFB',
    '\x84', '\xFD', '\x13', '\x63', '\xF6', '\x9C', '\x7C', '\xF2', '\x10', '\xA2', '\xDB', '\xCB', '\xC3', '\xF1',
    '\xD4', '\xBC', '\x01', '\xDB', '\x85', '\xA3', '\xD3', '\xC3', '\x00', '\x00', '\x00'};

TEST_F(sam_file_input_f, decompression_by_filename_gz)
{
    seqan3::test::tmp_directory tmp{};
    auto filename = tmp.path() / "sam_file_output_test.sam.gz";

    {
        std::ofstream of{filename, std::ios::binary};

        std::copy(input_gz.begin(), input_gz.end(), std::ostreambuf_iterator<char>{of});
    }

    seqan3::sam_file_input fin{filename};

    decompression_impl(*this, fin);
}

TEST_F(sam_file_input_f, decompression_by_stream_gz)
{
    seqan3::sam_file_input fin{std::istringstream{input_gz}, seqan3::format_sam{}};

    decompression_impl(*this, fin);
}

TEST_F(sam_file_input_f, read_empty_gz_file)
{
    std::string empty_zipped_file{'\x1f', '\x8b', '\x08', '\x08', '\x5a', '\x07', '\x98', '\x5c',
                                  '\x00', '\x03', '\x66', '\x6f', '\x6f', '\x00', '\x03', '\x00',
                                  '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'};
    seqan3::sam_file_input fin{std::istringstream{empty_zipped_file}, seqan3::format_sam{}};

    EXPECT_TRUE(fin.begin() == fin.end());
}

std::string input_bgzf{
    '\x1F', '\x8B', '\x08', '\x04', '\x00', '\x00', '\x00', '\x00', '\x00', '\xFF', '\x06', '\x00', '\x42', '\x43',
    '\x02', '\x00', '\xF2', '\x00', '\x6D', '\x8E', '\x5D', '\x4B', '\xC3', '\x40', '\x10', '\x45', '\x9F', '\x6F',
    '\x7E', '\x45', '\x43', '\xC5', '\x6A', '\x8D', '\x31', '\xFB', '\x61', '\x0A', '\xF3', '\x94', '\x35', '\x81',
    '\x55', '\x30', '\x9B', '\xCA', '\x06', '\x5F', '\x25', '\xD4', '\xA8', '\x45', '\xB3', '\x2B', '\x51', '\x51',
    '\xFF', '\xBD', '\xE9', '\xEA', '\x8B', '\x50', '\x18', '\xB8', '\xDC', '\x61', '\xCE', '\x61', '\x8A', '\xCB',
    '\x0A', '\xB7', '\x86', '\x58', '\x9A', '\xC3', '\x36', '\xF4', '\xE1', '\x9E', '\x9D', '\xFF', '\x74', '\xD0',
    '\x0D', '\x39', '\xEF', '\xFA', '\xA8', '\xB0', '\x37', '\xB0', '\x86', '\xC6', '\xFE', '\x01', '\xD7', '\x86',
    '\x84', '\x8C', '\x8A', '\xB5', '\xC6', '\x55', '\x45', '\xAF', '\xA3', '\x7F', '\x64', '\x58', '\x1B', '\xDA',
    '\x78', '\xFF', '\x72', '\xB7', '\x6B', '\x63', '\x37', '\x44', '\x45', '\xD9', '\xA0', '\x7D', '\xDA', '\xBE',
    '\xCD', '\xA6', '\xE9', '\x66', '\x1B', '\x3F', '\x0C', '\xBD', '\x7B', '\x4F', '\xA3', '\xB1', '\xEF', '\xEE',
    '\x19', '\x24', '\xC3', '\x4E', '\xC3', '\x90', '\x33', '\x30', '\xCB', '\x6A', '\x56', '\xF1', '\xFA', '\x77',
    '\x93', '\x41', '\x64', '\x19', '\x54', '\xA9', '\x5B', '\xC4', '\xF3', '\xF9', '\x01', '\x94', '\xA5', '\x2D',
    '\x71', '\x98', '\x7A', '\x8A', '\x55', '\xA0', '\x39', '\x24', '\x0F', '\xB7', '\x1C', '\x39', '\xC7', '\x6A',
    '\x62', '\x27', '\xDE', '\xFE', '\xA3', '\xB5', '\x2E', '\x5B', '\x6D', '\x94', '\x0E', '\x8A', '\xC3', '\xC5',
    '\xD1', '\xF1', '\x12', '\x5F', '\xDF', '\x74', '\x41', '\x36', '\x11', '\x89', '\x4C', '\xCE', '\x83', '\x46',
    '\x40', '\x8A', '\x00', '\x09', '\xE4', '\xE2', '\xEF', '\x09', '\xB9', '\x47', '\xA6', '\xB5', '\xD2', '\xAD',
    '\x6A', '\x15', '\xE2', '\x78', '\x79', '\x92', '\x9C', '\xA6', '\x67', '\xD1', '\x0F', '\x72', '\x0E', '\xE3',
    '\xE8', '\x26', '\x01', '\x00', '\x00', '\x1F', '\x8B', '\x08', '\x04', '\x00', '\x00', '\x00', '\x00', '\x00',
    '\xFF', '\x06', '\x00', '\x42', '\x43', '\x02', '\x00', '\x1B', '\x00', '\x03', '\x00', '\x00', '\x00', '\x00',
    '\x00', '\x00', '\x00', '\x00', '\x00'};

TEST_F(sam_file_input_f, decompression_by_filename_bgzf)
{
    seqan3::test::tmp_directory tmp{};
    auto filename = tmp.path() / "sam_file_output_test.sam.bgzf";

    {
        std::ofstream of{filename, std::ios::binary};

        std::copy(input_bgzf.begin(), input_bgzf.end(), std::ostreambuf_iterator<char>{of});
    }

    seqan3::sam_file_input fin{filename};

    decompression_impl(*this, fin);
}

TEST_F(sam_file_input_f, decompression_by_stream_bgzf)
{
    seqan3::sam_file_input fin{std::istringstream{input_bgzf}, seqan3::format_sam{}};

    decompression_impl(*this, fin);
}

TEST_F(sam_file_input_f, read_empty_bgzf_file)
{
    std::string empty_bgzf_file{'\x1F', '\x8B', '\x08', '\x04', '\x00', '\x00', '\x00', '\x00', '\x00', '\xFF',
                                '\x06', '\x00', '\x42', '\x43', '\x02', '\x00', '\x1B', '\x00', '\x03', '\x00',
                                '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'};
    seqan3::sam_file_input fin{std::istringstream{empty_bgzf_file}, seqan3::format_sam{}};

    EXPECT_TRUE(fin.begin() == fin.end());
}

#endif

#if defined(SEQAN3_HAS_BZIP2)
std::string input_bz2{
    '\x42', '\x5A', '\x68', '\x39', '\x31', '\x41', '\x59', '\x26', '\x53', '\x59', '\x7B', '\xE2', '\xE1',
    '\x92', '\x00', '\x00', '\x5C', '\x5F', '\x80', '\x00', '\x30', '\x2D', '\xFF', '\xFF', '\x90', '\x3C',
    '\x83', '\x0C', '\x00', '\x27', '\x20', '\x10', '\x60', '\x20', '\x00', '\x8A', '\x86', '\x82', '\x4D',
    '\x4D', '\xA6', '\xA6', '\x9A', '\x60', '\xD4', '\xC8', '\xC9', '\x99', '\x35', '\x34', '\x06', '\x44',
    '\x9B', '\x51', '\xA0', '\x83', '\x4C', '\x06', '\x88', '\x31', '\x19', '\x32', '\xDF', '\x59', '\x81',
    '\x84', '\x10', '\x62', '\x4B', '\x06', '\x22', '\x21', '\xA8', '\xEA', '\x68', '\xCD', '\xA2', '\x15',
    '\xB7', '\xE5', '\xA7', '\xAB', '\x0A', '\xD2', '\xB8', '\x0A', '\xEF', '\xC3', '\x18', '\x35', '\xFE',
    '\x2C', '\xE9', '\x1C', '\x72', '\x8D', '\xA6', '\xE2', '\xC7', '\x3D', '\xBC', '\x41', '\x0E', '\x00',
    '\x50', '\x3E', '\x05', '\x0E', '\x0F', '\x46', '\xF5', '\x2B', '\x39', '\xEF', '\x92', '\xA5', '\x28',
    '\x85', '\xEA', '\xA5', '\x93', '\xE0', '\xFD', '\x27', '\xBF', '\x76', '\xCC', '\xE2', '\x6A', '\xE9',
    '\x32', '\xE0', '\x11', '\x05', '\x09', '\x44', '\xD2', '\x51', '\xB6', '\x90', '\x2A', '\x73', '\x94',
    '\x54', '\x62', '\x96', '\x19', '\xBB', '\x92', '\xE4', '\xB8', '\x20', '\x28', '\x32', '\x8E', '\x0C',
    '\x09', '\xE7', '\xF8', '\xBB', '\x92', '\x29', '\xC2', '\x84', '\x83', '\xDF', '\x17', '\x0C', '\x90'};

TEST_F(sam_file_input_f, decompression_by_filename_bz2)
{
    seqan3::test::tmp_directory tmp{};
    auto filename = tmp.path() / "sam_file_output_test.sam.bz2";

    {
        std::ofstream of{filename, std::ios::binary};

        std::copy(input_bz2.begin(), input_bz2.end(), std::ostreambuf_iterator<char>{of});
    }

    seqan3::sam_file_input fin{filename};

    decompression_impl(*this, fin);
}

TEST_F(sam_file_input_f, decompression_by_stream_bz2)
{
    seqan3::sam_file_input fin{std::istringstream{input_bz2}, seqan3::format_sam{}};

    decompression_impl(*this, fin);
}

TEST_F(sam_file_input_f, read_empty_bz2_file)
{
    std::string empty_zipped_file{'\x42',
                                  '\x5a',
                                  '\x68',
                                  '\x39',
                                  '\x17',
                                  '\x72',
                                  '\x45',
                                  '\x38',
                                  '\x50',
                                  '\x90',
                                  '\x00',
                                  '\x00',
                                  '\x00',
                                  '\x00'};
    seqan3::sam_file_input fin{std::istringstream{empty_zipped_file}, seqan3::format_sam{}};

    EXPECT_TRUE(fin.begin() == fin.end());
}
#endif

// ----------------------------------------------------------------------------
// BAM format specificities
// ----------------------------------------------------------------------------

struct sam_file_input_bam_format_f : public sam_file_input_f
{
    std::vector<seqan3::dna4_vector> const ref_seqs = {"ACTGATCGAGAGGATCTAGAGGAGATCGTAGGAC"_dna4};
    std::vector<std::string> const ref_ids = {"ref"};

    std::string binary_input{
        // corresponds to 'input' from sam_file_input_f fixture
        '\x1F', '\x8B', '\x08', '\x04', '\x00', '\x00', '\x00', '\x00', '\x00', '\xFF', '\x06', '\x00', '\x42', '\x43',
        '\x02', '\x00', '\x8D', '\x00', '\x73', '\x72', '\xF4', '\x65', '\x4C', '\x66', '\x60', '\x60', '\x70', '\xF0',
        '\x70', '\xE1', '\x0C', '\xF3', '\xB3', '\x32', '\xD4', '\x33', '\xE3', '\x0C', '\xF6', '\xB7', '\x2A', '\xCD',
        '\xCB', '\xCE', '\xCB', '\x2F', '\xCF', '\xE3', '\x74', '\xF7', '\xB7', '\xCA', '\xCB', '\xCF', '\x4B', '\xE5',
        '\x72', '\x08', '\x0E', '\xE4', '\x0C', '\xF6', '\xB3', '\x2A', '\x4A', '\x4D', '\xE3', '\xF4', '\xF1', '\xB3',
        '\x32', '\x36', '\xE1', '\x72', '\x08', '\x70', '\xE7', '\xF4', '\x74', '\xB1', '\x2A', '\x28', '\xCA', '\x4F',
        '\x37', '\xE4', '\x0C', '\xF0', '\xB3', '\x4A', '\xCE', '\xCF', '\xCF', '\x89', '\x07', '\xF1', '\x8A', '\x12',
        '\x73', '\xB9', '\x1C', '\x9C', '\xFD', '\x39', '\x43', '\x32', '\x32', '\x8B', '\x15', '\x80', '\x28', '\x51',
        '\x21', '\x39', '\x3F', '\x37', '\x37', '\x35', '\xAF', '\x44', '\x8F', '\x8B', '\x11', '\x68', '\x0D', '\x0B',
        '\x10', '\x03', '\x4D', '\x61', '\x50', '\x02', '\xD2', '\x00', '\xF2', '\x5C', '\x8E', '\x8D', '\x7B', '\x00',
        '\x00', '\x00', '\x1F', '\x8B', '\x08', '\x04', '\x00', '\x00', '\x00', '\x00', '\x00', '\xFF', '\x06', '\x00',
        '\x42', '\x43', '\x02', '\x00', '\x9F', '\x00', '\x73', '\x61', '\x40', '\x00', '\x36', '\x5B', '\x4F', '\x21',
        '\x16', '\x06', '\x4D', '\x06', '\x16', '\x28', '\x9F', '\x13', '\x88', '\x75', '\x18', '\x19', '\x18', '\x8A',
        '\x52', '\x13', '\x53', '\x0C', '\x19', '\x44', '\x80', '\x3C', '\x01', '\x20', '\x16', '\x02', '\x62', '\x05',
        '\x10', '\xED', '\xC1', '\xC0', '\xC4', '\xC4', '\xEC', '\x18', '\xEC', '\xCC', '\xE4', '\xE7', '\xEB', '\xCC',
        '\x1E', '\x04', '\xD5', '\xC3', '\x08', '\x32', '\xC7', '\x0E', '\x64', '\x8E', '\x16', '\x58', '\x3F', '\xBA',
        '\x39', '\x46', '\x0C', '\x05', '\x50', '\x33', '\x40', '\x66', '\x81', '\xCC', '\x14', '\x71', '\x6A', '\xF9',
        '\xE8', '\x00', '\x32', '\x8A', '\x95', '\x8D', '\x9D', '\x83', '\xB3', '\xA2', '\xD2', '\x29', '\x98', '\x19',
        '\x28', '\xCA', '\x0C', '\x74', '\x05', '\x2B', '\x83', '\x1F', '\xD4', '\x04', '\x26', '\x90', '\xA9', '\xF6',
        '\x9E', '\x42', '\xEC', '\x0C', '\xDA', '\x0C', '\x1C', '\x58', '\x4C', '\x35', '\x46', '\x71', '\x9D', '\x03',
        '\x9A', '\x0D', '\x2E', '\x22', '\x8D', '\x8D', '\x40', '\xF5', '\x5C', '\xDC', '\x3C', '\xBC', '\x7C', '\x00',
        '\xC5', '\xFD', '\x4B', '\xCD', '\xF0', '\x00', '\x00', '\x00', '\x1F', '\x8B', '\x08', '\x04', '\x00', '\x00',
        '\x00', '\x00', '\x00', '\xFF', '\x06', '\x00', '\x42', '\x43', '\x02', '\x00', '\x1B', '\x00', '\x03', '\x00',
        '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00'};
};

#if defined(SEQAN3_HAS_ZLIB)
TEST_F(sam_file_input_bam_format_f, construct_by_filename)
{
    seqan3::test::tmp_directory tmp{};
    auto filename = tmp.path() / "sam_file_input_constructor.bam";
    {
        std::ofstream filecreator{filename, std::ios::out | std::ios::binary};
        filecreator << binary_input;
    }

    seqan3::sam_file_input fin{filename,
                               ref_ids,
                               ref_seqs,
                               seqan3::fields<seqan3::field::id, seqan3::field::seq, seqan3::field::qual>{}};

    EXPECT_EQ(fin.header().ref_ids(), ref_ids);
    EXPECT_EQ(fin.header().comments[0], std::string{"This is a comment."});

    size_t counter = 0;
    for (auto & [id, seq, qual] : fin)
    {
        EXPECT_EQ(id, id_comp[counter]);
        EXPECT_EQ(seq, seq_comp[counter]);
        EXPECT_EQ(qual, qual_comp[counter]);

        ++counter;
    }

    EXPECT_EQ(counter, 3u);
}

TEST_F(sam_file_input_bam_format_f, construct_by_stream)
{
    std::istringstream stream{binary_input};

    seqan3::sam_file_input fin{stream,
                               ref_ids,
                               ref_seqs,
                               seqan3::format_bam{},
                               seqan3::fields<seqan3::field::id, seqan3::field::seq, seqan3::field::qual>{}};

    EXPECT_EQ(fin.header().ref_ids(), ref_ids);
    EXPECT_EQ(fin.header().comments[0], std::string{"This is a comment."});

    size_t counter = 0;
    for (auto & [id, seq, qual] : fin)
    {
        EXPECT_EQ(id, id_comp[counter]);
        EXPECT_EQ(seq, seq_comp[counter]);
        EXPECT_EQ(qual, qual_comp[counter]);

        ++counter;
    }

    EXPECT_EQ(counter, 3u);
}
#endif // defined(SEQAN3_HAS_ZLIB)
