// ==========================================================================
//                 SeqAn - The Library for Sequence Analysis
// ==========================================================================
// Copyright (c) 2006-2010, Knut Reinert, FU Berlin
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of Knut Reinert or the FU Berlin nor the names of
//       its contributors may be used to endorse or promote products derived
//       from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL KNUT REINERT OR THE FU BERLIN BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.
//
// ==========================================================================
// Author: Manuel Holtgrewe <manuel.holtgrewe@fu-berlin.de>
// ==========================================================================
// Generic test code for stream class.
// ==========================================================================

#ifndef TEST_STREAM_TEST_STREAM_GENERIC_H_
#define TEST_STREAM_TEST_STREAM_GENERIC_H_

// Simple usage of the stream.
template <typename TStream>
void testStreamReadSimpleUsage(TStream & stream)
{
    using namespace seqan;

    // Read first line.
    char buffer[100];
    char *ptr = &(buffer[0]);
    while (!streamEof(stream)) {
        int res = streamReadChar(*ptr, stream);
        SEQAN_ASSERT_EQ(res, 0);
        if (*(ptr++) == '\n')
            break;
    }
    char c;
    streamReadChar(c, stream);
    SEQAN_ASSERT_EQ(c, 'W');
}

// A bit more complex usage of the stream.
template <typename TStream>
void testStreamReadComplexUsage(TStream & stream)
{
    using namespace seqan;

    // Read lines and check whether the last one is terminated with a '\n'.
    char c = '\0';
    bool wasEol = false;
    while (!streamEof(stream)) {
        int res = streamReadChar(c, stream);
        if (res == EOF)
            break;
        wasEol = (c == '\n');
    }
    SEQAN_ASSERT_NOT(wasEol);
}

// Helper for testStreamEof() below, to conditionally run streamTell, or
// return defaultValue.
template <typename TStream>
int _runStreamTell(TStream & stream, int /*defaultValue*/, seqan::True const &)
{
    using namespace seqan;
    return streamTell(stream);
}

template <typename TStream>
int _runStreamTell(TStream & /*stream*/, int defaultValue, seqan::False const &)
{
    using namespace seqan;
    return defaultValue;
}

// Test of streamEof().
template <typename TStream>
void testStreamEof(TStream & stream)
{
    using namespace seqan;

    const char buffer[] = "This is a test.";
    
    SEQAN_ASSERT_NOT(streamEof(stream));
    char c;
    for (int i = 0; i < 15; ++i) {
        SEQAN_ASSERT_NOT(streamEof(stream));
        int res = streamReadChar(c, stream);
        SEQAN_ASSERT_EQ(res, 0);
        SEQAN_ASSERT_EQ(c, buffer[i]);
        SEQAN_ASSERT_EQ(_runStreamTell(stream, i + 1, typename HasStreamFeature<TStream, Tell>::Type()), i + 1);
    }
    // SEQAN_ASSERT_NOT(streamEof(stream));  // Not portable, inconsistent behaviour.
    int res = streamReadChar(c, stream);
    SEQAN_ASSERT_NEQ(res, 0);
    SEQAN_ASSERT(streamEof(stream));
    SEQAN_ASSERT(_runStreamTell(stream, 15, typename HasStreamFeature<TStream, Tell>::Type()) == 15 ||
                 _runStreamTell(stream, -1, typename HasStreamFeature<TStream, Tell>::Type()) == -1);
}

// Test of streamPeek().
template <typename TStream>
void testStreamPeek(TStream & stream)
{
    using namespace seqan;

    char c;
    SEQAN_ASSERT_EQ(static_cast<int>(streamTell(stream)), 0);
    int res = streamPeek(c, stream);
    SEQAN_ASSERT_EQ(res, 0);
    SEQAN_ASSERT_EQ(c, 'T');
    res = streamPeek(c, stream);
    SEQAN_ASSERT_EQ(res, 0);
    SEQAN_ASSERT_EQ(c, 'T');
    res = streamPeek(c, stream);
    SEQAN_ASSERT_EQ(res, 0);
    SEQAN_ASSERT_EQ(c, 'T');
    SEQAN_ASSERT_EQ(static_cast<int>(streamTell(stream)), 0);
}

// Test of testStreamReadBlock(), limit is longer than stream.
template <typename TStream>
void testStreamReadBlockHitLimit(TStream & stream)
{
    using namespace seqan;

    String<char> buffer;
    resize(buffer, 20);
    size_t charsRead = streamReadBlock(begin(buffer, Standard()), stream, 20);
    resize(buffer, charsRead);
    SEQAN_ASSERT_EQ(charsRead, 10u);
    SEQAN_ASSERT_EQ(strcmp(toCString(buffer), "XXXXXXXXXX"), 0);
    SEQAN_ASSERT(_runStreamTell(stream, 10, typename HasStreamFeature<TStream, Tell>::Type()) == 10 ||
                 _runStreamTell(stream, -1, typename HasStreamFeature<TStream, Tell>::Type()) == -1);
}

// Test of testStreamReadBlock(), limit is shorter than stream.
template <typename TStream>
void testStreamReadBlockHitNoLimit(TStream & stream)
{
    using namespace seqan;

    String<char> buffer;
    resize(buffer, 20);
    size_t charsRead = streamReadBlock(begin(buffer, Standard()), stream, 5);
    resize(buffer, charsRead);
    SEQAN_ASSERT_EQ(charsRead, 5u);
    SEQAN_ASSERT(buffer == "XXXXX");
    SEQAN_ASSERT(_runStreamTell(stream, 5, typename HasStreamFeature<TStream, Tell>::Type()) == 5);
}

// Test of testStreamWriteBlock(), iterator interface.
template <typename TStream>
void testStreamWriteBlock(TStream & stream)
{
    using namespace seqan;

    String<char> buffer;
    append(buffer, "ABCDEFGH");
    size_t charsWritten = streamWriteBlock(stream, begin(buffer, Standard()), length(buffer));
    SEQAN_ASSERT_EQ(charsWritten, 8u);
    SEQAN_ASSERT_EQ(_runStreamTell(stream, 8, typename HasStreamFeature<TStream, Tell>::Type()), 8);
}

// Test of streamWriteChar().
template <typename TStream>
void testStreamWriteChar(TStream & stream)
{
    using namespace seqan;

    int res = streamWriteChar(stream, '3');
    SEQAN_ASSERT_EQ(res, 0);
    res = streamWriteChar(stream, '4');
    SEQAN_ASSERT_EQ(res, 0);
    res = streamWriteChar(stream, '5');
    SEQAN_ASSERT_EQ(res, 0);
    SEQAN_ASSERT_EQ(_runStreamTell(stream, 3, typename HasStreamFeature<TStream, Tell>::Type()), 3);
}

template <typename TStream>
void testStreamPut(TStream & stream)
{
    using namespace seqan;

    int res = streamPut(stream, 'c');
    SEQAN_ASSERT_EQ(res, 0);
    res = streamPut(stream, '\n');
    SEQAN_ASSERT_EQ(res, 0);

    res = streamPut(stream, CharString("seq"));
    SEQAN_ASSERT_EQ(res, 0);
    res = streamPut(stream, '\n');
    SEQAN_ASSERT_EQ(res, 0);

    res = streamPut(stream, "sss");
    SEQAN_ASSERT_EQ(res, 0);
    res = streamPut(stream, '\n');
    SEQAN_ASSERT_EQ(res, 0);

    res = streamPut(stream, 12);
    SEQAN_ASSERT_EQ(res, 0);
    res = streamPut(stream, '\n');
    SEQAN_ASSERT_EQ(res, 0);

    res = streamPut(stream, 34u);
    SEQAN_ASSERT_EQ(res, 0);
    res = streamPut(stream, '\n');
    SEQAN_ASSERT_EQ(res, 0);

    res = streamPut(stream, 56l);
    SEQAN_ASSERT_EQ(res, 0);
    res = streamPut(stream, '\n');
    SEQAN_ASSERT_EQ(res, 0);

    res = streamPut(stream, 78ul);
    SEQAN_ASSERT_EQ(res, 0);
    res = streamPut(stream, '\n');
    SEQAN_ASSERT_EQ(res, 0);

    res = streamPut(stream, 5.4f);
    SEQAN_ASSERT_EQ(res, 0);
    res = streamPut(stream, '\n');
    SEQAN_ASSERT_EQ(res, 0);

    res = streamPut(stream, 6.5);
    SEQAN_ASSERT_EQ(res, 0);
    res = streamPut(stream, '\n');
    SEQAN_ASSERT_EQ(res, 0);

    res = streamPut(stream, Dna('A'));
    SEQAN_ASSERT_EQ(res, 0);
    res = streamPut(stream, '\n');
    SEQAN_ASSERT_EQ(res, 0);

    res = streamPut(stream, DnaString("ACGT"));
    SEQAN_ASSERT_EQ(res, 0);
    res = streamPut(stream, '\n');
    SEQAN_ASSERT_EQ(res, 0);

    res = streamPut(stream, Dna5String("ACGTN"));
    SEQAN_ASSERT_EQ(res, 0);
    res = streamPut(stream, '\n');
    SEQAN_ASSERT_EQ(res, 0);
}


// Test of streamReadChar().
template <typename TStream>
void testStreamReadChar(TStream & stream)
{
    using namespace seqan;

    char c;
    int res = streamReadChar(c, stream);
    SEQAN_ASSERT_EQ(res, 0);
    SEQAN_ASSERT_EQ(c, '1');
    res = streamReadChar(c, stream);
    SEQAN_ASSERT_EQ(res, 0);
    SEQAN_ASSERT_EQ(c, '2');
    res = streamReadChar(c, stream);
    SEQAN_ASSERT_EQ(res, 0);
    SEQAN_ASSERT_EQ(c, '3');
    // SEQAN_ASSERT_NOT(streamEof(stream));  // Non-portable, different behaviour.
    res = streamReadChar(c, stream);
    SEQAN_ASSERT_NEQ(res, 0);
    SEQAN_ASSERT(streamEof(stream));
    SEQAN_ASSERT(_runStreamTell(stream, 3, typename HasStreamFeature<TStream, Tell>::Type()) == 3 ||
                 _runStreamTell(stream, -1, typename HasStreamFeature<TStream, Tell>::Type()) == -1);
}

// Test of streamSeek().
template <typename TStream>
void testStreamSeek(TStream & stream)
{
    using namespace seqan;

    // TODO(holtgrew): Do real tests for all variants.
    // TODO(holtgrew): Use constant for origin in the future.
    char c;
    SEQAN_ASSERT_EQ(static_cast<int>(streamTell(stream)), 0);
    int res = streamPeek(c, stream);
    SEQAN_ASSERT_EQ(res, 0);
    SEQAN_ASSERT_EQ(c, '0');
    SEQAN_ASSERT_EQ(static_cast<int>(streamTell(stream)), 0);

    res = streamSeek(stream, 4, SEEK_CUR);
    SEQAN_ASSERT_EQ(res, 0);
    SEQAN_ASSERT_EQ(static_cast<int>(streamTell(stream)), 4);

    res = streamPeek(c, stream);
    SEQAN_ASSERT_EQ(res, 0);
    SEQAN_ASSERT_EQ(c, '4');
    SEQAN_ASSERT_EQ(static_cast<int>(streamTell(stream)), 4);

    res = streamSeek(stream, -2, SEEK_CUR);
    SEQAN_ASSERT_EQ(res, 0);
    SEQAN_ASSERT_EQ(static_cast<int>(streamTell(stream)), 2);

    res = streamPeek(c, stream);
    SEQAN_ASSERT_EQ(c, '2');
    SEQAN_ASSERT_EQ(static_cast<int>(streamTell(stream)), 2);
}


std::fstream* createFastAFile(seqan::CharString &tempFilename)
{
    using namespace seqan;

    tempFilename = SEQAN_TEMP_FILENAME();
    char filenameBuffer[1000];
    strncpy(filenameBuffer, toCString(tempFilename), 999);

    std::fstream *file = new std::fstream(filenameBuffer, std::ios_base::in | std::ios_base::out | std::ios_base::binary | std::ios_base::trunc);
    SEQAN_ASSERT(file->is_open());

    char const * STR =
"> sequenceID_with special chars an irregular linebreaks\n\
AAAACGTGCGGTTGGGCAAAAAACTTTCTTATATTCTATCTATCTTGTAGCTAGCTGTAGCTAGCTAGCATCGTAGC\n\
CCCAGAGTGTCATGCATGTCGA\n\
ACGTGTTTTTGGGGCGGTTATATATATATATATT\n\
\n\
>sequence2... with no linebreaks and no newline at end\n\
ACGTNNNNNNNCGTACTTGCTAGCTAGCTAGCTAGCTAGCATCGTACGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATTTTTTTTTTACTATCATCTACTATCTACTATCATCTACTATCATTCATCGATCGATCGATCGTACGTACGATCGATCGATCGATCGTACGATCGATGCTACGTACGTACG";


    file->write(STR, strlen(STR));
    file->seekg(0);
    file->seekp(0);
    return file;
}

std::fstream* createFastAFileProtein(seqan::CharString &tempFilename)
{
    using namespace seqan;

    tempFilename = SEQAN_TEMP_FILENAME();
    char filenameBuffer[1000];
    strncpy(filenameBuffer, toCString(tempFilename), 999);

    std::fstream *file = new std::fstream(filenameBuffer, std::ios_base::in | std::ios_base::out | std::ios_base::binary | std::ios_base::trunc);
    SEQAN_ASSERT(file->is_open());

    char const * STR =
"> sequenceID_with special chars an irregular linebreaks\n\
LCLYTHIGRNIYYGSYLYSETWNTGIMLLLITMATAFMGYVLPWGQMSFWGAT\n\
VITNLFSAIPYIGTNLV\n\
EWIWGGFSVDKATLNRFFAF\n\
HFILPFTMVALAGVHLTFLHETGSNNPLGLTSDSDKIPFHPYYTIKDFLG\n\
>sequence2... with no linebreaks and no newline at end\n\
GLMPFLHTSKHRSMMLRPLSQALFWTLTMDLLTLTWIGSQPVEYPYTIIGQMASILYFSIILAFLPIAGX";


    file->write(STR, strlen(STR));
    file->seekg(0);
    file->seekp(0);
    return file;
}

std::fstream* createFastQFile(seqan::CharString &tempFilename)
{
    using namespace seqan;

    tempFilename = SEQAN_TEMP_FILENAME();
    char filenameBuffer[1000];
    strncpy(filenameBuffer, toCString(tempFilename), 999);

    std::fstream *file = new std::fstream(filenameBuffer, std::ios_base::in | std::ios_base::out | std::ios_base::binary | std::ios_base::trunc);
    SEQAN_ASSERT(file->is_open());

    char const * STR =
"@SEQ_ID\n\
GATTTGGGGTTCAAAGCAGTATCGATCAAATAGTAAATCCATTTGTTCAACTCACAGTTT\n\
+\n\
!''*((((***+))%%%++)(%%%%).@***-+*''))**55CCF>>>>>>CCCCCCC65\n\
@ 2ndSequence with formatting obscurities\n\
GATTTGGGGTTCAAAGC\n\
AGTATCGATCAAATAGTAAATCCATTT\n\
GTTCAACTCACAGTTT\n\
+ 2ndSequence with formatting obscurities\n\
!''*((((***+))%%%\n\
++)(%%%%).\n\
@***-+*''))**55CCF>>>>>>CCCCCCC65";
    // the second quality-string is line-broken at places to produce lines
    // beginning with '+' and '@'. The new code handles this absolutely fine!

    file->write(STR, strlen(STR));
    file->seekg(0);
    file->seekp(0);
    return file;
}

// something that is definitely not a sequence format
std::fstream* createBogusFile(seqan::CharString &tempFilename)
{
    using namespace seqan;

    tempFilename = SEQAN_TEMP_FILENAME();
    char filenameBuffer[1000];
    strncpy(filenameBuffer, toCString(tempFilename), 999);

    std::fstream *file = new std::fstream(filenameBuffer, std::ios_base::in | std::ios_base::out | std::ios_base::binary | std::ios_base::trunc);
    SEQAN_ASSERT(file->is_open());

    char const * STR =
"asd45623\n\
-.-+´adfasddsad2342asfd8ss8g9s8g9s8fags9d8ga9s\n\
67utghjgnm\n\
!-.,()//&%$$%§§%\n\
e5bv56 u67 gdh g x#++++´\n\
..\n\
.....asdsadfasd0809809vnvbnojokjojokjokj\n\
";
    file->write(STR, strlen(STR));
    file->seekg(0);
    file->seekp(0);
    return file;
}

#endif  // TEST_STREAM_TEST_STREAM_GENERIC_H_
