// Copyright (c) 2013-2020, SIB - Swiss Institute of Bioinformatics and
//                          Biozentrum - University of Basel
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


#include <promod3/core/check_io.hh>
#include <promod3/core/portable_binary_serializer.hh>
#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>

#include <ost/base.hh>
#include <cstdio>
#include <fstream>

BOOST_AUTO_TEST_SUITE( core );

using namespace promod3::core;

// dummy custom data type
struct MyStruct {
  float f;
  char c;
};

// templatized function to write header
template <typename T>
void WriteHeader(T& out_stream) {
  WriteMagicNumber(out_stream);
  // set version
  WriteVersionNumber(out_stream, 1);
  // add sizes
  WriteTypeSize<int>(out_stream);
  WriteTypeSize<uint>(out_stream);
  WriteTypeSize<float>(out_stream);
  WriteTypeSize<MyStruct>(out_stream);
  // check base types
  WriteBaseType<char>(out_stream);
  WriteBaseType<int>(out_stream);
  WriteBaseType<Real>(out_stream);
  WriteBaseType(out_stream, (Real)0.5); // ok for any IEEE floats
}

// templatized function to read header (all good)
template <typename T>
void ReadHeaderWell(T& in_stream) {
  BOOST_CHECK_NO_THROW(CheckMagicNumber(in_stream));
  BOOST_CHECK_EQUAL(GetVersionNumber(in_stream), (uint32_t)1);
  BOOST_CHECK_NO_THROW(CheckTypeSize<int>(in_stream));
  BOOST_CHECK_NO_THROW(CheckTypeSize<uint>(in_stream, true));
  BOOST_CHECK_NO_THROW(CheckTypeSize<double>(in_stream, true));
  BOOST_CHECK_NO_THROW(CheckTypeSize<MyStruct>(in_stream));
  BOOST_CHECK_NO_THROW(CheckBaseType<char>(in_stream));
  BOOST_CHECK_NO_THROW(CheckBaseType<int>(in_stream));
  BOOST_CHECK_NO_THROW(CheckBaseType<Real>(in_stream));
  BOOST_CHECK_NO_THROW(CheckBaseType(in_stream, (Real)0.5));
}

// templatized function to read header (with errors)
template <typename T>
void ReadHeaderBadly(T& in_stream) {
  CheckMagicNumber(in_stream);
  GetVersionNumber(in_stream);
  CheckTypeSize<int>(in_stream);
  CheckTypeSize<uint>(in_stream);
  BOOST_CHECK_THROW(CheckTypeSize<char>(in_stream, true), promod3::Error);
  BOOST_CHECK_THROW(CheckTypeSize<char>(in_stream), promod3::Error);
  BOOST_CHECK_THROW(CheckBaseType(in_stream, (char)3), promod3::Error);
  BOOST_CHECK_THROW(CheckBaseType(in_stream, (int)4), promod3::Error);
  BOOST_CHECK_THROW(CheckBaseType(in_stream, (Real)5), promod3::Error);
  BOOST_CHECK_THROW(CheckBaseType<Real>(in_stream), promod3::Error);
}

BOOST_AUTO_TEST_CASE(test_check_io_fstream) {

  // generate a test file
  String filename = "mytest.dat";
  {
    std::ofstream out_stream(filename.c_str(), std::ios::binary);
    WriteHeader(out_stream);
    out_stream.close();
  }

  // check that checks work without exceptions
  {
    std::ifstream in_stream(filename.c_str(), std::ios::binary);
    ReadHeaderWell(in_stream);
    in_stream.close();
  }

  // check exceptions for check base type (also used by CheckMagicNumber)
  {
    std::ifstream in_stream(filename.c_str(), std::ios::binary);
    ReadHeaderBadly(in_stream);
    in_stream.close();
  }

  // clean up
  std::remove(filename.c_str());
}

BOOST_AUTO_TEST_CASE(test_check_io_portable) {

  // generate a test file
  String filename = "mytest.dat";
  {
    std::ofstream out_stream_(filename.c_str(), std::ios::binary);
    PortableBinaryDataSink out_stream(out_stream_);
    WriteHeader(out_stream);
    out_stream_.close();
  }

  // check that checks work without exceptions
  {
    std::ifstream in_stream_(filename.c_str(), std::ios::binary);
    PortableBinaryDataSource in_stream(in_stream_);
    ReadHeaderWell(in_stream);
    in_stream_.close();
  }

  // check exceptions for check base type (also used by CheckMagicNumber)
  {
    std::ifstream in_stream_(filename.c_str(), std::ios::binary);
    PortableBinaryDataSource in_stream(in_stream_);
    ReadHeaderBadly(in_stream);
    in_stream_.close();
  }

  // clean up
  std::remove(filename.c_str());
}

BOOST_AUTO_TEST_SUITE_END();
