// (C) Copyright Jorge Lodos 2008
// (C) Copyright Jonathan Turkanis 2004
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt.)

// See http://www.boost.org/libs/iostreams for documentation.

// This is the original (boost 1.34) boost::iostream test for the mapped files with the
// following modifications:
// 1. The namespace for the mapped file was changed to seglib::filemap.
// 2. Added test for privately mapped files.
// 3. The test test_writeable was added for mapped files.
// 4. The test test_resizeable was added for mapped files.
//

#include <fstream>
#include <boost/config.hpp>
#include <boost/detail/workaround.hpp>
#include <boost/test/test_tools.hpp>
#include <boost/test/unit_test.hpp>

#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/device/mapped_file.hpp>
#include <boost/filesystem/path.hpp>
#include "detail/temp_file.hpp"
#include "detail/verification.hpp"

// Code generation bugs cause tests to fail with global optimization.
#if BOOST_WORKAROUND(BOOST_MSVC, < 1300)
# pragma optimize("g", off)
#endif

namespace boost { namespace iostreams { namespace test {

bool test_writeable(mapped_file& mf)
{
    // Test writing
    for (int i = 0; i < data_reps; ++i) {
        memcpy(mf.data(), narrow_data(), chunk_size);
        char buf[chunk_size];
        memcpy(buf, mf.const_data(), chunk_size);
        if (strncmp(buf, narrow_data(), chunk_size) != 0)
            return false;
        memset(mf.data(), 0, chunk_size);
    }
    return true;
}

bool test_resizeable(mapped_file& mf)
{
    // Test resizing
    mapped_file::size_type size = mf.size();
    if (size == 0)
        return false;
    mf.resize(size/2);
    if (mf.size() != size/2)
        return false;
    mf.resize(size);
    if (mf.size() != size)
        return false;
    return true;
}

} } } // End namespaces test, iostreams, boost.

void mapped_file_test()
{
    using namespace boost::iostreams;
    BOOST_MESSAGE("about to begin");

    //--------------Reading from a mapped_file_source-------------------------//

    {
        // Note: the ifstream second is placed in a nested scope because 
        // closing and reopening a single ifstream failed for CW 9.4 on Windows.

        // Test reading from a stream based on a mapped_file_source,
        // in chars.
        boost::iostreams::test::test_file test1, test2;
        boost::iostreams::stream<mapped_file_source> first(test1.name());
        {
            std::ifstream second( test2.name().c_str(), 
                             BOOST_IOS::in | BOOST_IOS::binary );
            BOOST_CHECK_MESSAGE(
                boost::iostreams::test::compare_streams_in_chars(first, second),
                "failed reading from stream<mapped_file_source> in chars"
            );

            BOOST_MESSAGE(
                "done reading from stream<mapped_file_source> in chars"
            );
        }
        first.close();

        // Test reading from a stream based on a mapped_file_source,
        // in chunks. (Also tests reopening the stream.)
        first.open(mapped_file_source(test1.name()));
        {
            std::ifstream second( test2.name().c_str(), 
                             BOOST_IOS::in | BOOST_IOS::binary );
            BOOST_CHECK_MESSAGE(
                boost::iostreams::test::compare_streams_in_chunks(first, second),
                "failed reading from stream<mapped_file_source> in chunks"
            );

            BOOST_MESSAGE(
                "done reading from stream<mapped_file_source> in chunks"
            );
        }
    }

    //--------------Writing to a mapped_file_sink-----------------------------//

    {
        // Test writing to a stream based on a mapped_file_sink, in 
        // chars.
        boost::iostreams::test::uppercase_file  first, second; // Will overwrite these.
        boost::iostreams::test::test_file       test;

        boost::iostreams::stream<mapped_file_sink> out;
        out.open(mapped_file_sink(first.name()));
        boost::iostreams::test::write_data_in_chars(out);
        out.close();
        BOOST_CHECK_MESSAGE(
            boost::iostreams::test::compare_files(first.name(), test.name()),
            "failed writing to stream<mapped_file_sink> in chars"
        );

        BOOST_MESSAGE(
            "done writing to stream<mapped_file_source> in chars"
        );

        // Test writing to a stream based on a mapped_file_sink, in 
        // chunks. (Also tests reopening the stream.)
        out.open(mapped_file_sink(second.name()));
        boost::iostreams::test::write_data_in_chunks(out);
        out.close();
        BOOST_CHECK_MESSAGE(
            boost::iostreams::test::compare_files(second.name(), test.name()),
            "failed writing to stream<mapped_file_sink> in chunks"
        );

        BOOST_MESSAGE(
            "done writing to stream<mapped_file_source> in chunks"
        );
    }

    //--------------Writing to a newly created file---------------------------//

    {
        // Test writing to a newly created mapped file.
        boost::iostreams::test::temp_file  first, second;
        boost::iostreams::test::test_file  test;

        mapped_file_params p(first.name());
        p.new_file_size = boost::iostreams::test::data_reps * boost::iostreams::test::data_length();
        boost::iostreams::stream<mapped_file_sink> out;
        out.open(mapped_file_sink(p));
        boost::iostreams::test::write_data_in_chars(out);
        out.close();
        BOOST_CHECK_MESSAGE(
            boost::iostreams::test::compare_files(first.name(), test.name()),
            "failed writing to newly created mapped file in chars"
        );

        
        // Test writing to a newly created mapped file. 
        // (Also tests reopening the stream.)
        p.path = second.name();
        out.open(mapped_file_sink(p));
        boost::iostreams::test::write_data_in_chunks(out);
        out.close();
        BOOST_CHECK_MESSAGE(
            boost::iostreams::test::compare_files(second.name(), test.name()),
            "failed writing to newly created mapped file in chunks"
        );
    }

    //--------------Writing to a pre-existing file---------------------------//
    {
        // Test for Bug #3953 - writing to a pre-existing mapped file.
        boost::iostreams::test::test_file  first, test;

        mapped_file_params p(first.name());
        p.new_file_size = boost::iostreams::test::data_reps * boost::iostreams::test::data_length();
        boost::iostreams::stream<mapped_file_sink> out;
        out.open(mapped_file_sink(p));
        boost::iostreams::test::write_data_in_chars(out);
        out.close();
        BOOST_CHECK_MESSAGE(
            boost::iostreams::test::compare_files(first.name(), test.name()),
            "failed writing to pre-existing mapped file in chars"
        );
    }
    
    //--------------Random access with a mapped_file--------------------------//

    {
        // Test reading, writing and seeking within a stream based on a 
        // mapped_file, in chars.
        boost::iostreams::test::test_file test;
        boost::iostreams::stream<mapped_file> io;
        io.open(mapped_file(test.name()));
        BOOST_CHECK_MESSAGE(
            boost::iostreams::test::test_seekable_in_chars(io),
            "failed seeking within stream<mapped_file> in chars"
        );

        BOOST_MESSAGE(
            "done seeking within stream<mapped_file> in chars"
        );

        io.close();

        // Test reading, writing and seeking within a stream based on a 
        // mapped_file, in chunks. (Also tests reopening the 
        // stream.)
        io.open(mapped_file(test.name()));
        BOOST_CHECK_MESSAGE(
            boost::iostreams::test::test_seekable_in_chunks(io),
            "failed seeking within stream<mapped_file> in chunks"
        );

        BOOST_MESSAGE(
            "done seeking within stream<mapped_file> in chunks"
        );
    }

    //--------------Resizing a mapped_file------------------------------------//

    {
        // Test resizing a mapped_file.
        boost::iostreams::test::test_file test;
        mapped_file mf;
        mf.open(test.name());
        BOOST_CHECK_MESSAGE(
            boost::iostreams::test::test_resizeable(mf),
            "failed resizing a mapped_file"
        );
        
        BOOST_MESSAGE(
            "done resizing a mapped_file"
        );
    }

    //--------------Random access with a private mapped_file------------------//

    {
        // Use 2 copies of the file to compare later
        boost::iostreams::test::test_file orig, copy;
        
        // Test reading and writing within a mapped_file. 
        // Since the file is privately mapped, it should remain
        // unchanged after writing when opened in readonly mode.
        mapped_file mf;
        mf.open(orig.name(), mapped_file::priv);
        BOOST_CHECK_MESSAGE(
            boost::iostreams::test::test_writeable(mf),
            "failed seeking within private mapped_file"
        );
        BOOST_CHECK_MESSAGE(
            boost::iostreams::test::compare_files(orig.name(), copy.name()),
            "failed writing to private mapped_file"
        );
        
        BOOST_MESSAGE(
            "done seeking within private mapped_file"
        );
        
        mf.close();

        // Test reopening the mapped file.
        mf.open(orig.name(), mapped_file::priv);
        BOOST_CHECK_MESSAGE(
            boost::iostreams::test::test_writeable(mf),
            "failed reopening private mapped_file"
        );
        BOOST_CHECK_MESSAGE(
            boost::iostreams::test::compare_files(orig.name(), copy.name()),
            "failed writing to reopened private mapped_file"
        );
        
        BOOST_MESSAGE(
            "done reopening private mapped_file"
        );
    }

    //-------------Check creating opening mapped_file with char*-------------//
    
    {
        boost::iostreams::test::test_file orig;
        char name[50];
        std::strcpy(name, orig.name().c_str());
        
        mapped_file mf((char*) name);

        BOOST_CHECK_MESSAGE(
            boost::iostreams::test::test_writeable(mf),
            "failed seeking within private mapped_file"
        );

        mf.close();
    }

    //---------Check creating opening mapped_file with filesystem3 path------//
    {
        boost::iostreams::test::test_file orig;
        
        mapped_file mf(boost::filesystem::path(orig.name()));

        BOOST_CHECK_MESSAGE(
            boost::iostreams::test::test_writeable(mf),
            "failed seeking within private mapped_file"
        );

        mf.close();
    }
}

#if BOOST_WORKAROUND(BOOST_MSVC, < 1300)
# pragma optimize("", on)
#endif

boost::unit_test::test_suite* init_unit_test_suite(int, char* []) 
{
    boost::unit_test::test_suite* test = BOOST_TEST_SUITE("mapped_file test");
    test->add(BOOST_TEST_CASE(&mapped_file_test));
    return test;
}
