#include <cppunit/TestFixture.h>
#include <cppunit/TestAssert.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <cppunit/ui/text/TestRunner.h>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/CompilerOutputter.h>

#include "config.h"
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <iostream>
#include <fstream>
#include <cstring>

#include "TestByte.h"
#include "TestInt16.h"
#include "TestInt32.h"
#include "TestUInt16.h"
#include "TestUInt32.h"
#include "TestFloat32.h"
#include "TestFloat64.h"
#include "TestStr.h"
#include "TestUrl.h"
#include "TestArray.h"
#include "TestStructure.h"
#include "TestSequence.h"
#include "DataDDS.h"
#include "ConstraintEvaluator.h"
#include "TestTypeFactory.h"
#include "XDRFileMarshaller.h"
#include "XDRStreamMarshaller.h"
#include "XDRFileUnMarshaller.h"

#include "GetOpt.h"
#include "debug.h"

using std::cerr;
using std::cout;
using std::endl;
using std::ofstream;

int test_variable_sleep_interval = 0; // Used in Test* classes for testing timeouts.

static bool debug = false;

#undef DBG
#define DBG(x) do { if (debug) {x;} } while(false)

/**
 * This test has been rewritten so that it no longer depends on the values
 * of Array remaining in place (in the Array object) once serialize() is run.
 * This is part of a change in libdap that will reduce memory consumption
 * when serializing datasets with large numbers of (large) variables.
 * jhrg 8/4/15
 */
class marshT: public CppUnit::TestFixture
{
private:
	TestByte *b;
	TestInt16 *i16;
	TestInt32 *i32;
	TestUInt16 *ui16;
	TestUInt32 *ui32;
	TestFloat32 *f32;
	TestFloat64 *f64;
	TestStr *str;
	TestUrl *url;
	TestByte *ab;

	TestArray *arr;

	TestStructure *s;

	TestSequence *seq, *seq2;

public:
	marshT() : b(0), i16(0), i32(0),
	ui16(0), ui32(0), f32(0), f64(0),
	str(0), url(0), ab(0), arr(0), s(0),
	seq(0), seq2(0)
	{

	}

	void setUp()
	{
		b = new TestByte("b");
		i16 = new TestInt16("i16");
		i32 = new TestInt32("i32");
		ui16 = new TestUInt16("ui16");
		ui32 = new TestUInt32("ui32");
		f32 = new TestFloat32("f32");
		f64 = new TestFloat64("f64");
		str = new TestStr("str");
		url = new TestUrl("url");

		ab = new TestByte("ab");
		arr = new TestArray("arr", ab);
		arr->append_dim(5, "dim1");
		arr->append_dim(3, "dim2");

		s = new TestStructure("s");
		s->add_var(i32);
		s->add_var(str);
		s->add_var(arr);
		s->set_send_p(true);

		seq = new TestSequence("seq");
		seq->add_var(f64);
		seq->add_var(arr);

		seq2 = new TestSequence("seq2");
		seq2->add_var(ui16);
		seq2->add_var(url);
		seq2->set_send_p(true);

		seq->add_var(seq2);
		seq->set_send_p(true);
		seq->set_leaf_sequence();

		// Need to call read so that the value() method calls in the ..read_test()
		// will work.
		b->read();
		i16->read();
		i32->read();
		ui16->read();
		ui32->read();
		f32->read();
		f64->read();

		str->read();
		url->read();
		arr->read();
		s->read();
	}

	void tearDown()
	{
		delete b; b = 0;
		delete i16; i16 = 0;
		delete i32; i32 = 0;
		delete ui16; ui16 = 0;
		delete ui32; ui32 = 0;
		delete f32; f32 = 0;
		delete f64; f64 = 0;

		delete str; str = 0;
		delete url; url = 0;

		delete ab; ab = 0;
		delete arr; arr = 0;
		delete s; s = 0;
	}

	void marshT_test_write(Marshaller &fm)
	{
		ConstraintEvaluator eval;
		TestTypeFactory ttf;
		DataDDS dds(&ttf, "dds");

		try
		{
			DBG(cerr << "serializing using XDRFileMarshaller" << endl);

			b->serialize(eval, dds, fm, false);
			i16->serialize(eval, dds, fm, false);
			i32->serialize(eval, dds, fm, false);
			ui16->serialize(eval, dds, fm, false);
			ui32->serialize(eval, dds, fm, false);
			f32->serialize(eval, dds, fm, false);
			f64->serialize(eval, dds, fm, false);
			str->serialize(eval, dds, fm, false);
			url->serialize(eval, dds, fm, false);
			s->serialize(eval, dds, fm, false);
			arr->serialize(eval, dds, fm, false);
			seq->serialize(eval, dds, fm, false);

			DBG(cerr << "done serializing using XDRFileMarshaller" << endl);
		}
		catch( Error &e )
		{
			string err = "failed:" + e.get_error_message();
			CPPUNIT_FAIL(err.c_str());
		}
	}

	// This test depends on the file written by the .._write_file() test,
	// so it must be run after that test.
	void marshT_test_read(UnMarshaller &um)
	{
		TestTypeFactory ttf;
		DataDDS dds(&ttf, "dds");

		// now read the values in and compare the with each other and the original values
		try
		{
			DBG(cerr << "deserializing XDRFileMarshaller built file" << endl);

			Byte fb("fb");
			fb.deserialize(um, &dds, false);
			CPPUNIT_ASSERT(fb.value() == b->value());

			Int16 fi16("i16");
			fi16.deserialize(um, &dds, false);
			DBG(cerr << "fi16.value(): " << fi16.value() << ", i16.value(): " << i16->value());
			CPPUNIT_ASSERT(fi16.value() == 32000);

			DBG(cerr << " file int32" << endl);
			Int32 fi32("i32");
			fi32.deserialize(um, &dds, false);
			DBG(cerr << "fi32.value(): " << fi32.value() << ", i32.value(): " << i32->value());
			CPPUNIT_ASSERT(fi32.value() == i32->value());

			UInt16 fui16("ui16");
			fui16.deserialize(um, &dds, false);
			DBG(cerr << "fui16.value(): " << fui16.value() << ", ui16.value(): " << ui16->value());
			CPPUNIT_ASSERT(fui16.value() == ui16->value());

			UInt32 fui32("ui32");
			fui32.deserialize(um, &dds, false);
			DBG(cerr << "fui32.value(): " << fui32.value() << ", ui32.value(): " << ui32->value());
			CPPUNIT_ASSERT(fui32.value() == ui32->value());

			Float32 ff32("f32");
			ff32.deserialize(um, &dds, false);
			DBG(cerr << "ff32.value(): " << ff32.value() << ", f32.value(): " << f32->value());
			CPPUNIT_ASSERT(ff32.value() == f32->value());

			Float64 ff64("f64");
			ff64.deserialize(um, &dds, false);
			DBG(cerr << "ff64.value(): " << ff64.value() << ", f64.value(): " << f64->value());
			CPPUNIT_ASSERT(ff64.value() == f64->value());

			Str fstr("str");
			fstr.deserialize(um, &dds, false);
			DBG(cerr << "fstr.value(): " << fstr.value() << ", str.value(): " << str->value());
			CPPUNIT_ASSERT(fstr.value() == str->value());

			Url furl("url");
			furl.deserialize(um, &dds, false);
			DBG(cerr << "furl.value(): " << furl.value() << ", url.value(): " << url->value());
			CPPUNIT_ASSERT(furl.value() == url->value());

			TestStructure fs("fs");
			TestInt32 fsi32("fsi32");
			fs.add_var(&fsi32);
			TestStr fsstr("fsstr");
			fs.add_var(&fsstr);
			TestByte fsab("fsab");
			TestArray fsarr("fsarr", &fsab);
			fsarr.append_dim(5, "dim1");
			fsarr.append_dim(3, "dim2");
			fs.add_var(&fsarr);
			fs.deserialize(um, &dds, false);

			Int32 *fsi32_p = dynamic_cast<Int32 *>(fs.var("fsi32"));
			CPPUNIT_ASSERT(fsi32_p);
			DBG(cerr << "fsi32.value(): " << fsi32_p->value() << ", i32.value(): " << i32->value());
			CPPUNIT_ASSERT(fsi32_p->value() == i32->value());

			Str *fsstr_p = dynamic_cast<Str *>(fs.var("fsstr"));
			CPPUNIT_ASSERT(fsstr_p);
			DBG(cerr << "fstr.value(): " << fsstr_p->value() << ", str.value(): " << dynamic_cast<Str *>(s->var("str"))->value());
			CPPUNIT_ASSERT(fsstr_p->value() == dynamic_cast<Str *>(s->var("str"))->value());

			BaseType *bt = fs.var("fsab");
			CPPUNIT_ASSERT(bt);
			Array *fsarr_p = dynamic_cast<Array *>(bt);
			CPPUNIT_ASSERT(fsarr_p);
			dods_byte fdb[fsarr_p->length() * sizeof(dods_byte)];
			dods_byte db[arr->length() * sizeof(dods_byte)];
			fsarr_p->value(fdb);
			arr->value(db);
			CPPUNIT_ASSERT(fsarr_p->length() == arr->length());
			CPPUNIT_ASSERT(
					!memcmp((void * )fdb, (void * )db,
							fsarr_p->length() * sizeof(dods_byte)));

			DBG(cerr << " file array" << endl);
			TestByte fab("ab");
			TestArray farr("arr", &fab);
			farr.append_dim(5, "dim1");
			farr.append_dim(3, "dim2");
			farr.deserialize(um, &dds, false);
			farr.value(fdb);
			CPPUNIT_ASSERT(farr.length() == arr->length());
			CPPUNIT_ASSERT(
					!memcmp((void * )fdb, (void * )db,
							farr.length() * sizeof(dods_byte)));

			TestSequence fseq("fseq");
			fseq.add_var(f64);
			fseq.add_var(arr);
			TestSequence fseq2("fseq2");
			fseq2.add_var(ui16);
			fseq2.add_var(url);
			fseq2.set_send_p(true);
			fseq.add_var(&fseq2);
			fseq.set_leaf_sequence();

			fseq.deserialize(um, &dds, false);
			unsigned int num_rows = fseq.number_of_rows();
			CPPUNIT_ASSERT(num_rows == 4);
			for (unsigned int i = 0; i < num_rows; i++)
			{
				BaseTypeRow *row = fseq.row_value(i);
				CPPUNIT_ASSERT(row);
				CPPUNIT_ASSERT(row->size() == 3);
				Float64 *f64_p = dynamic_cast<Float64 *>((*row)[0]);
				CPPUNIT_ASSERT(f64_p);
				CPPUNIT_ASSERT(f64_p->value() == f64->value());
				Array *arr_p = dynamic_cast<Array *>((*row)[1]);
				CPPUNIT_ASSERT(arr_p);
				arr_p->value(fdb);
				CPPUNIT_ASSERT(arr_p->length() == arr->length());
				CPPUNIT_ASSERT(
						!memcmp((void * )fdb, (void * )db,
								arr_p->length() * sizeof(dods_byte)));
				Sequence *seq_p = dynamic_cast<Sequence *>((*row)[2]);
				CPPUNIT_ASSERT(seq_p);
				unsigned int num_rows_sub = seq_p->number_of_rows();
				CPPUNIT_ASSERT(num_rows == 4);
				for (unsigned int j = 0; j < num_rows_sub; j++)
				{
					BaseTypeRow *row_sub = seq_p->row_value(j);
					CPPUNIT_ASSERT(row_sub);
					CPPUNIT_ASSERT(row_sub->size() == 2);
					UInt16 *ui16_p = dynamic_cast<UInt16 *>((*row_sub)[0]);
					CPPUNIT_ASSERT(ui16_p);
					CPPUNIT_ASSERT(ui16_p->value() == ui16->value());
					Url *url_p = dynamic_cast<Url *>((*row_sub)[1]);
					CPPUNIT_ASSERT(url_p);
					CPPUNIT_ASSERT(url_p->value() == url->value());
				}
			}

			DBG(cerr << "done deserializing XDRFileMarshaller built file" << endl);
		}
		catch( Error &e )
		{
			string err = "failed:" + e.get_error_message();
			CPPUNIT_FAIL(err.c_str());
		}
	}

	void marshT_test_write_file() {
		FILE *f = fopen("test.file", "w");
		XDRFileMarshaller fm(f);

		marshT_test_write(fm);

		fclose(f);
	}

	void marshT_test_read_file() {
		FILE *ff = fopen("test.file", "r");
		XDRFileUnMarshaller um(ff);

		marshT_test_read(um);

		fclose(ff);
		unlink("test.file");
	}

	void marshT_test_write_stream() {
		ofstream strm("test.strm", ios::out | ios::trunc);
		XDRStreamMarshaller sm(strm);

		marshT_test_write(sm);

		strm.close();
	}

	// Not that compelling a test really... (It is the same as .._read_file())
	void marshT_test_read_stream() {
		FILE *sf = fopen("test.strm", "r");
		XDRFileUnMarshaller um(sf);

		marshT_test_read(um);

		fclose(sf);
		unlink("test.strm");
	}

	CPPUNIT_TEST_SUITE( marshT );

	CPPUNIT_TEST(marshT_test_write_file);
	CPPUNIT_TEST(marshT_test_read_file);
	CPPUNIT_TEST(marshT_test_write_stream);
	CPPUNIT_TEST(marshT_test_read_stream);

	CPPUNIT_TEST_SUITE_END( );

};

CPPUNIT_TEST_SUITE_REGISTRATION(marshT);

int main(int argc, char *argv[])
{
    CppUnit::TextTestRunner runner;
    runner.addTest(CppUnit::TestFactoryRegistry::getRegistry().makeTest());

    GetOpt getopt(argc, argv, "d");
    int option_char;

    while ((option_char = getopt()) != -1)
        switch (option_char) {
        case 'd':
            debug = 1;  // debug is a static global
            break;
        default:
            break;
        }

    bool wasSuccessful = true;
    string test = "";
    int i = getopt.optind;
    if (i == argc) {
        // run them all
        wasSuccessful = runner.run("");
    }
    else {
        while (i < argc) {
            test = string("libdap::marshT::") + argv[i++];
            DBG(cerr << "test: " << test << endl);
            wasSuccessful = wasSuccessful && runner.run(test);
        }
    }

    return wasSuccessful ? 0 : 1;
}

