// 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.


/// \brief Defines procedures to check validity of binary files.
/// (see portableIO doc for details)

#ifndef PROMOD3_CHECK_IO_HH
#define PROMOD3_CHECK_IO_HH

#include <iostream>
#include <fstream>

#include <ost/base.hh>
#include <ost/stdint.hh>

#include <promod3/core/message.hh>

namespace promod3{ namespace core{

// private namespace for private helper functions
namespace {

// our magic number
uint32_t magic_number = 1234;

// write an object to an ost-like data sink
template <typename STREAM, typename T>
void WriteRaw(STREAM& stream, const T& t) {
  stream & t;
}

// overload WriteRaw for std::ofstream
template <typename T>
void WriteRaw(std::ofstream& stream, const T& t) {
  stream.write(reinterpret_cast<const char*>(&t), sizeof(T));
}

// read an object from an ost-like source
template <typename STREAM, typename T>
void ReadRaw(STREAM& stream, T& t) {
  stream & t;
}

// overload WriteRaw for std::ofstream
template <typename T>
void ReadRaw(std::ifstream& stream, T& t) {
  stream.read(reinterpret_cast<char*>(&t), sizeof(T));
}

} // anon ns

/// \brief Write a base-type value to a stream
/// By default the value "1" is converted to T and written.
/// Default is ok for integers, floating points, and most enums.
/// It is recommended though to choose your own value for enums.
template <typename T, typename STREAM>
void WriteBaseType(STREAM& stream, const T t = T(1)) {
  WriteRaw(stream, t);
}

/// \brief Check that read base-type value has expected value
/// By default the value "1" is expected (needs to match WriteBaseType).
/// \throw Error if read-value doesn't match the expected value
template <typename T, typename STREAM>
void CheckBaseType(STREAM& stream, const T expected = T(1)) {
  T value;
  ReadRaw(stream, value);
  if (value != expected) {
    std::stringstream ss;
    ss << "Failed to read " << expected << " correctly, got " << value;
    throw promod3::Error(ss.str());
  }
}

/// \brief Write a magic number into the stream
/// \throw Error if byte-size is not 8-bit
template <typename STREAM>
void WriteMagicNumber(STREAM& stream) {
  // ensure byte-size is 8bits
  if (sizeof(uint32_t) != 4) {
    throw promod3::Error("Unsopported byte-size (we need 8-bit bytes)!");
  }
  WriteBaseType(stream, magic_number);
}

/// \brief Read and check magic number from the stream
/// \throw Error if byte-size is not 8-bit
/// \throw Error if read magic number is wrong
template <typename STREAM>
void CheckMagicNumber(STREAM& stream) {
  // ensure byte-size is 8bits
  if (sizeof(uint32_t) != 4) {
    throw promod3::Error("Unsopported byte-size (we need 8-bit bytes)!");
  }
  // check magic number
  CheckBaseType(stream, magic_number);
}

/// \brief Write a version number into the stream
template <typename STREAM>
void WriteVersionNumber(STREAM& stream, const uint32_t version_number) {
  WriteRaw(stream, version_number);
}

/// \brief Get version number from stream
template <typename STREAM>
uint32_t GetVersionNumber(STREAM& stream) {
  uint32_t v = 0;
  ReadRaw(stream, v);
  return v;
}

/// \brief Write check for size of template argument T.
/// Call this as e.g.: WriteTypeSize<int>(out_stream)
template <typename T, typename STREAM>
void WriteTypeSize(STREAM& stream) {
  uint32_t v = sizeof(T);
  WriteRaw(stream, v);
}

/// \brief Check size of template argument T.
/// Call this as e.g.: CheckTypeSize<int>(out_stream)
/// \param ok_if_greater (def=false): false if type-size must be as in file
///                                   true if bigger type-size ok
/// \throw Error if type-size not compatible with the one written in file
template <typename T, typename STREAM>
void CheckTypeSize(STREAM& stream, bool ok_if_greater = false) {
  uint32_t exp_size = 0;
  uint32_t local_size = sizeof(T);
  ReadRaw(stream, exp_size);
  if ((exp_size != local_size && !ok_if_greater) ||
      (exp_size > local_size && ok_if_greater)) {
    std::stringstream ss;
    // Note: in Boost > 1.56, we can use Boost.TypeIndex for nicer error
    ss << "Type-size mismatch for type of expected size " << exp_size
       << ", while on this machine it has size " << local_size;
    throw promod3::Error(ss.str());
  }
}

}}//ns

#endif