/*
 * (C) Copyright 1996-2017 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. In applying
 * this licence, ECMWF does not waive the privileges and immunities granted to it by virtue
 * of its status as an intergovernmental organisation nor does it submit to any jurisdiction.
 */

/// @file test_streamable.cc
/// @date Jan 2015
/// @author Florian Rathgeber

// NOTE: cannot include cstdint due to backwards compatbility with gcc 4.5
// which requires enabling c++0x mode for including cstdint
#include <stdint.h>
#include <limits>
#include <sys/types.h>

#include "eckit/filesystem/PathName.h"
#include "eckit/serialisation/FileStream.h"
#include "eckit/serialisation/Streamable.h"

#include "eckit/testing/Test.h"

using namespace std;
using namespace eckit;
using namespace eckit::testing;

namespace eckit {
namespace test {

//-----------------------------------------------------------------------------

template <typename T>
class TestItem : public Streamable {

public:
	TestItem(const T& s) : payload_(s) {}

	TestItem(Stream& s) : Streamable(s), payload_() {
		s >> payload_;
	}

	// From Streamble
	virtual void encode(eckit::Stream& s) const {
		Streamable::encode(s);
		s << payload_;
	}
	virtual const eckit::ReanimatorBase& reanimator() const;

	// Class members
	static const eckit::ClassSpec& classSpec() { return classSpec_; }

	T payload_;

protected:

	virtual void print(std::ostream& s) const {
		s << "TestItem " << payload_;
	}

private:
	// -- Class members
	static eckit::ClassSpec            classSpec_;

	// -- Friends
	friend std::ostream& operator<<(std::ostream& s,const TestItem& p) {
		p.print(s); return s;
	}
};

template <typename T>
const ReanimatorBase& TestItem<T>::reanimator() const
{
	static Reanimator< TestItem<T> > reanimator;
	return reanimator;
}

typedef unsigned char uchar;
typedef long long llong;
#ifndef ulong
typedef unsigned long ulong;
#endif
typedef unsigned long long ullong;

/// Partially specialise ClassSpecs since they must have unique names
template <>
ClassSpec TestItem<char>::classSpec_ = {&Streamable::classSpec(),"TestItemChar",};
template <>
ClassSpec TestItem<uchar>::classSpec_ = {&Streamable::classSpec(),"TestItemUChar",};
template <>
ClassSpec TestItem<bool>::classSpec_ = {&Streamable::classSpec(),"TestItemBool",};
template <>
ClassSpec TestItem<int>::classSpec_ = {&Streamable::classSpec(),"TestItemInt",};
template <>
ClassSpec TestItem<uint>::classSpec_ = {&Streamable::classSpec(),"TestItemUInt",};
template <>
ClassSpec TestItem<short>::classSpec_ = {&Streamable::classSpec(),"TestItemShort",};
template <>
ClassSpec TestItem<ushort>::classSpec_ = {&Streamable::classSpec(),"TestItemUShort",};
template <>
ClassSpec TestItem<long>::classSpec_ = {&Streamable::classSpec(),"TestItemLong",};
template <>
ClassSpec TestItem<ulong>::classSpec_ = {&Streamable::classSpec(),"TestItemULong",};
template <>
ClassSpec TestItem<llong>::classSpec_ = {&Streamable::classSpec(),"TestItemLLong",};
template <>
ClassSpec TestItem<ullong>::classSpec_ = {&Streamable::classSpec(),"TestItemULLong",};
// NOTE: float is not implemented!
// template <>
// ClassSpec TestItem<float>::classSpec_ = {&Streamable::classSpec(),"TestItemFloat",};
template <>
ClassSpec TestItem<double>::classSpec_ = {&Streamable::classSpec(),"TestItemDouble",};
template <>
ClassSpec TestItem<string>::classSpec_ = {&Streamable::classSpec(),"TestItemString",};

//-----------------------------------------------------------------------------

#define test_decode(TYPE, INITIAL, SUFFIX) \
CASE ( "test_decode_" #TYPE "_" #SUFFIX ) \
{ \
	Log::info()  << "Manually (de)serialise Streamable with " #TYPE " member" << std::endl; \
	PathName filename = PathName::unique( "data" ); \
	std::string filepath = filename.asString(); \
	TestItem<TYPE> t(INITIAL); \
	{ \
		FileStream sout( filepath.c_str(), "w" ); \
		t.encode(sout); \
	} \
	{ \
		FileStream sin( filepath.c_str(), "r" ); \
		TestItem<TYPE> t2(sin); \
		Log::info()  << "original: " << t.payload_ << std::endl; \
		Log::info()  << "streamed: " << t2.payload_ << std::endl; \
		EXPECT(t.payload_ == t2.payload_); \
	} \
	if (filename.exists()) filename.unlink(); \
}

#define test_reanimate(TYPE, INITIAL, SUFFIX) \
CASE ( "test_reanimate_" #TYPE "_" #SUFFIX ) \
{ \
	Log::info()  << "(de)serialise Streamable with " #TYPE " member via Reanimator" << std::endl; \
	PathName filename = PathName::unique( "data" ); \
	std::string filepath = filename.asString(); \
	TestItem<TYPE> t(INITIAL); \
	{ \
		FileStream sout( filepath.c_str(), "w" ); \
		sout << t; \
	} \
	{ \
		FileStream sin( filepath.c_str(), "r" ); \
		TestItem<TYPE>* t2 = eckit::Reanimator< TestItem<TYPE> >::reanimate(sin); \
		Log::info()  << "orginal: " << t.payload_ << std::endl; \
		Log::info()  << "streamed: " << t2->payload_ << std::endl; \
		EXPECT(t.payload_ == t2->payload_); \
	} \
	if (filename.exists()) filename.unlink(); \
}

test_decode(char, 'A', max)
test_decode(char, 'z', min)
test_reanimate(char, 'A', max)
test_reanimate(char, 'z', min)

test_decode(uchar, 'A', max)
test_reanimate(uchar, 'A', max)

test_decode(bool, true, true)
test_reanimate(bool, true, true)

test_decode(int, numeric_limits<int32_t>::max(), max)
test_decode(int, numeric_limits<int32_t>::min(), min)
test_reanimate(int, numeric_limits<int32_t>::max(), max)
test_reanimate(int, numeric_limits<int32_t>::min(), min)

test_decode(uint, numeric_limits<uint32_t>::max(), max)
test_reanimate(uint, numeric_limits<uint32_t>::max(), max)

test_decode(short, numeric_limits<short>::max(), max)
test_decode(short, numeric_limits<short>::min(), min)
test_reanimate(short, numeric_limits<short>::max(), max)
test_reanimate(short, numeric_limits<short>::min(), min)

test_decode(ushort, numeric_limits<ushort>::max(), max)
test_reanimate(ushort, numeric_limits<ushort>::max(), max)

// NOTE: long in eckit is always 32 bit!

test_decode(long, numeric_limits<int32_t>::max(), max)
test_decode(long, numeric_limits<int32_t>::min(), min)
test_reanimate(long, numeric_limits<int32_t>::max(), max)
test_reanimate(long, numeric_limits<int32_t>::min(), min)

test_decode(ulong, numeric_limits<uint32_t>::max(), max)
test_reanimate(ulong, numeric_limits<uint32_t>::max(), max)

test_decode(llong, numeric_limits<llong>::max(), max)
test_decode(llong, numeric_limits<llong>::min(), min)
test_reanimate(llong, numeric_limits<llong>::max(), max)
test_reanimate(llong, numeric_limits<llong>::min(), min)

test_decode(ullong, numeric_limits<ullong>::max(), max)
test_reanimate(ullong, numeric_limits<ullong>::max(), max)

// NOTE: float is not implemented!
// test_decode(float, numeric_limits<float>::max(), max)
// test_decode(float, numeric_limits<float>::min(), min)
// test_reanimate(float, numeric_limits<float>::max(), max)
// test_reanimate(float, numeric_limits<float>::min(), min)

test_decode(double, numeric_limits<double>::max(), max)
test_decode(double, numeric_limits<double>::min(), min)
test_reanimate(double, numeric_limits<double>::max(), max)
test_reanimate(double, numeric_limits<double>::min(), min)

test_decode(string, "Hello, World!", _)
test_reanimate(string, "Hello, World!", _)

//-----------------------------------------------------------------------------

} // namespace test
} // namespace eckit

int main(int argc,char **argv)
{
    return run_tests ( argc, argv );
}
