
#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 <iostream>
#include <fstream>
#include <sstream>

// #define DODS_DEBUG

#include "DDS.h"
//#include "Pix.h"
#include "Byte.h"
#include "Int16.h"
#include "UInt16.h"
#include "Int32.h"
#include "UInt32.h"
#include "Float32.h"
#include "Float64.h"
#include "Str.h"
#include "Url.h"
#include "Array.h"
#include "Structure.h"
#include "Sequence.h"
#include "Grid.h"

#include "TestArray.h"
#include "TestInt16.h"
#include "TestStr.h"
#include "TestTypeFactory.h"
//#include "ce_functions.h"
#include "util.h"
#include "debug.h"

#include "testFile.h"

using namespace std;
using namespace libdap;

int test_variable_sleep_interval = 0; // Used in Test* classes for testing
				      // timeouts.
string cprint = "\
Dataset {\n\
    Int16 var1;\n\
    String var6;\n\
    Int16 var7;\n\
    Structure {\n\
        Structure {\n\
            Int16 var10;\n\
        } var9;\n\
    } var8;\n\
} Test%20Data%20Set;\n\
" ;

string pprint = "\
Dataset {\n\
    Int16 var1;\n\
    String var6;\n\
    Int16 var7;\n\
} Test%20Data%20Set;\n\
" ;

string nprint = "\
Dataset {\n\
} Test%20Data%20Set;\n\
" ;

string containerprint = "\
Dataset {\n\
    Structure {\n\
        Int16 c1var1;\n\
        Int16 c1var3;\n\
    } c1;\n\
    Structure {\n\
        String c2var2;\n\
        Structure {\n\
            Structure {\n\
                Int16 c2var3var1var1;\n\
                Int16 c2var3var1var2;\n\
            } c2var3var1;\n\
        } c2var3;\n\
    } c2;\n\
    Byte var1;\n\
    Float64 var2;\n\
} TestDDS;\n\
" ;


class ddsT : public CppUnit::TestFixture {

CPPUNIT_TEST_SUITE( ddsT ) ;
CPPUNIT_TEST( ddsT_test ) ;
CPPUNIT_TEST( ddsT_containers ) ;
CPPUNIT_TEST_SUITE_END( ) ;

private:
    /* TEST PRIVATE DATA */
    TestTypeFactory *factory;

public:
    void setUp()
    {
        factory = new TestTypeFactory;
    }

    void tearDown()
    {
        delete factory; factory = 0;
    }

    void ddsT_test()
    {
	DDS dds( factory, "TestDDS" ) ;

	string dsn = dds.get_dataset_name() ;
	CPPUNIT_ASSERT( dsn == "TestDDS" ) ;

	dds.set_dataset_name( "Test Data Set" ) ;
	dsn = dds.get_dataset_name() ;
	CPPUNIT_ASSERT( dsn == "Test Data Set" ) ;

	string fn = dds.filename() ;
	CPPUNIT_ASSERT( fn == "" ) ;

	dds.filename( "dds_test.data" ) ;
	fn = dds.filename() ;
	CPPUNIT_ASSERT( fn == "dds_test.data" ) ;

	try
	{
	    dds.add_var( (BaseType *)NULL ) ;
	    CPPUNIT_FAIL( "succeeded in adding a null var" ) ;
	}
	catch( InternalErr &e )
	{
	}

	try
	{
	    BaseType *bt = factory->NewInt16( "var1" ) ;
	    dds.add_var( bt ) ;
	    delete bt ;
	    bt = factory->NewInt16( "var2" ) ;
	    dds.add_var( bt ) ;
	    delete bt ;
	    bt = factory->NewInt16( "var3" ) ;
	    dds.add_var( bt ) ;
	    delete bt ;
	    bt = factory->NewInt16( "var4" ) ;
	    dds.add_var( bt ) ;
	    delete bt ;
	    bt = factory->NewInt16( "var5" ) ;
	    dds.add_var( bt ) ;
	    delete bt ;
	    bt = factory->NewStr( "var6" ) ;
	    dds.add_var( bt ) ;
	    delete bt ;
	    bt = factory->NewArray( "var7", factory->NewInt16( "" ) ) ;
	    dds.add_var( bt ) ;
	    delete bt ;
	    bt = factory->NewStructure( "var8" ) ;
	    Structure *s = (Structure *)bt ;
	    BaseType *bts1 = factory->NewStructure( "var9" ) ;
	    Structure *s1 = (Structure *)bts1 ;
	    BaseType *bts2 = factory->NewInt16( "var10" ) ;
	    s1->add_var( bts2 ) ;
	    delete bts2 ; bts2 = 0 ;
	    s->add_var( bts1 ) ;
	    delete bts1 ; bts1 = 0 ;
	    dds.add_var( bt ) ;
	}
	catch( InternalErr &e )
	{
	    CPPUNIT_FAIL( "failed to add a var" ) ;
	}

	int nv = dds.num_var() ;
	CPPUNIT_ASSERT( nv == 8 ) ;

	vector<string> vs ;
	typedef std::vector<string>::const_iterator vs_citer ;
	typedef std::vector<string>::iterator vs_iter ;
	vs.push_back( "var1" ) ;
	vs.push_back( "var2" ) ;
	vs.push_back( "var3" ) ;
	vs.push_back( "var4" ) ;
	vs.push_back( "var5" ) ;
	vs.push_back( "var6" ) ;
	vs.push_back( "var7" ) ;
	vs.push_back( "var8" ) ;

	DDS::Vars_iter dvsc = dds.var_begin() ;
	vs_citer vsc = vs.begin() ;
	for( ; dvsc != dds.var_end() && vsc != vs.end(); dvsc++, vsc++ )
	{
	    CPPUNIT_ASSERT( (*dvsc)->name() == *vsc ) ;
	}
	CPPUNIT_ASSERT( dvsc == dds.var_end() && vsc == vs.end() ) ;
	if( dvsc != dds.var_end() && vsc == vs.end() )
	{
	    CPPUNIT_FAIL( "Too many vars" ) ;
	}
	else if( dvsc == dds.var_end() && vsc != vs.end() )
	{
	    CPPUNIT_FAIL( "Too few vars" ) ;
	}

	for( vsc = vs.begin(); vsc != vs.end(); vsc++ )
	{
	    if( *vsc == "var2" )
	    {
		vs_iter &vsi = (vs_iter &)vsc ;
		vs.erase( vsi ) ;
		break ;
	    }
	}

        dvsc = dds.var_begin() ;
        vsc = vs.begin() ;
        for( ; dvsc != dds.var_end() && vsc != vs.end(); dvsc++, vsc++ )
        {
            if( (*dvsc)->name() == "var2" )
            {
                DDS::Vars_iter &dvsi = (DDS::Vars_iter &)dvsc ;
                dds.del_var( dvsi ) ;
            }
            CPPUNIT_ASSERT( (*dvsc)->name() == *vsc ) ;
        }

	nv = dds.num_var() ;
	CPPUNIT_ASSERT( nv == 7 ) ;
	if( nv != 7 )
	{
	    for( dvsc = dds.var_begin(); dvsc != dds.var_end(); dvsc++ )
	    {
		DBG2( cerr << "    " << (*dvsc)->name() << endl ) ;
	    }
	}

	for( vsc = vs.begin(); vsc != vs.end(); vsc++ )
	{
	    if( *vsc == "var3" )
	    {
		vs_iter &vsi = (vs_iter &)vsc ;
		vs.erase( vsi ) ;
		break ;
	    }
	}
	dvsc = dds.var_begin() ;
	vsc = vs.begin() ;
	for( ; dvsc != dds.var_end() && vsc != vs.end(); dvsc++, vsc++ )
	{
	    if( (*dvsc)->name() == "var3" )
	    {
		DDS::Vars_iter &dvsi = (DDS::Vars_iter &)dvsc ;
		dds.del_var( dvsi ) ;
	    }
	    CPPUNIT_ASSERT( (*dvsc)->name() == *vsc ) ;
	}

	nv = dds.num_var() ;
	CPPUNIT_ASSERT( nv == 6 ) ;
	if( nv != 6 )
	{
	    for( dvsc = dds.var_begin(); dvsc != dds.var_end(); dvsc++ )
	    {
		DBG2( cerr << "    " << (*dvsc)->name() << endl ) ;
	    }
	}

	for( vsc = vs.begin(); vsc != vs.end(); vsc++ )
	{
	    if( *vsc == "var4" )
	    {
		vs_citer vsc2 = vsc ;
		vsc2++ ;
		vsc2++ ;
		vs_iter &vsi = (vs_iter &)vsc ;
		vs_iter &vsi2 = (vs_iter &)vsc2 ;
		vs.erase( vsi, vsi2 ) ;
		break ;
	    }
	}
	dvsc = dds.var_begin() ;
	vsc = vs.begin() ;
	for( ; dvsc != dds.var_end() && vsc != vs.end(); dvsc++, vsc++ )
	{
	    if( (*dvsc)->name() == "var4" )
	    {
		DDS::Vars_iter dvsc2 = dvsc ;
		dvsc2++ ;
		dvsc2++ ;
		DDS::Vars_iter &dvsi = (DDS::Vars_iter &)dvsc ;
		DDS::Vars_iter &dvsi2 = (DDS::Vars_iter &)dvsc2 ;
		dds.del_var( dvsi, dvsi2 ) ;
	    }
	    CPPUNIT_ASSERT( (*dvsc)->name() == *vsc ) ;
	}

	nv = dds.num_var() ;
	CPPUNIT_ASSERT( nv == 4 ) ;
	if( nv != 4 )
	{
	    for( dvsc = dds.var_begin(); dvsc != dds.var_end(); dvsc++ )
	    {
		DBG2( cerr << "    " << (*dvsc)->name() << endl ) ;
	    }
	}

	BaseType *bt = dds.var( "varnot" ) ;
	CPPUNIT_ASSERT( !bt ) ;

	bt = dds.var( "var6" ) ;
	CPPUNIT_ASSERT( bt ) ;
	if( bt )
	{
	    CPPUNIT_ASSERT( bt->name() == "var6" ) ;
	}

	string find_var = "var6" ;
	bt = dds.var( find_var ) ;
	CPPUNIT_ASSERT( bt ) ;
	if( bt )
	{
	    CPPUNIT_ASSERT( bt->name() == "var6" ) ;
	}

	find_var = "var10" ;
	bt = dds.var( find_var ) ;
	CPPUNIT_ASSERT( bt ) ;
	if( bt )
	{
	    CPPUNIT_ASSERT( bt->name() == "var10" ) ;
	}

	find_var = "var10" ;
	BaseType::btp_stack btps ;
	bt = dds.var( find_var, &btps ) ;
	CPPUNIT_ASSERT( bt ) ;
	if( bt )
	{
	    CPPUNIT_ASSERT( bt->name() == "var10" ) ;
	    if( bt->name() == "var10" )
	    {
		CPPUNIT_ASSERT( btps.size() == 2 ) ;
		if( btps.size() == 2 )
		{
		    CPPUNIT_ASSERT( btps.top()->name() == "var8" ) ;
		    btps.pop() ;
		    CPPUNIT_ASSERT( btps.top()->name() == "var9" ) ;
		    btps.pop() ;
		}
	    }
	}

	find_var = "var8.var9.var10" ;
	bt = dds.var( find_var, &btps ) ;
	CPPUNIT_ASSERT( bt ) ;
	if( bt )
	{
	    CPPUNIT_ASSERT( bt->name() == "var10" ) ;
	    if( bt->name() == "var10" )
	    {
		CPPUNIT_ASSERT( btps.size() == 2 ) ;
		if( btps.size() == 2 )
		{
		    CPPUNIT_ASSERT( btps.top()->name() == "var9" ) ;
		    btps.pop() ;
		    CPPUNIT_ASSERT( btps.top()->name() == "var8" ) ;
		    btps.pop() ;
		}
	    }
	}

	{
            ostringstream sof;
            dds.print( sof );
            CPPUNIT_ASSERT(sof.str().find(cprint) != string::npos);
	}

	{
            ostringstream sof;
            dds.print_constrained( sof );
            CPPUNIT_ASSERT(sof.str().find(nprint) != string::npos);
	}

	dds.mark_all( true ) ;

	{
            ostringstream sof;
            dds.print_constrained( sof );
            CPPUNIT_ASSERT(sof.str().find(cprint) != string::npos);
	}

	bool mark_ret = dds.mark( "var8", false ) ;
	CPPUNIT_ASSERT( mark_ret == true ) ;

	{
            ostringstream sof;
            dds.print_constrained( sof );
            CPPUNIT_ASSERT(sof.str().find(pprint) != string::npos);
	}
    }

    void ddsT_containers()
    {
	DDS dds( factory, "TestDDS" ) ;

	// set the container to c1 and make sure the container is created
	dds.container_name( "c1" ) ;
	CPPUNIT_ASSERT( dds.container_name() == "c1" ) ;

	Structure *c1 = dds.container() ;
	CPPUNIT_ASSERT( c1 ) ;
	CPPUNIT_ASSERT( c1->name() == "c1" ) ;

	CPPUNIT_ASSERT( dds.num_var() == 1 ) ;

	// set the container to c2 and make sure the container is created
	dds.container_name( "c2" ) ;
	CPPUNIT_ASSERT( dds.container_name() == "c2" ) ;

	Structure *c2 = dds.container() ;
	CPPUNIT_ASSERT( c2 ) ;
	CPPUNIT_ASSERT( c2->name() == "c2" ) ;

	CPPUNIT_ASSERT( dds.num_var() == 2 ) ;

	// set back to c1, make sure new one not created
	dds.container_name( "c1" ) ;
	CPPUNIT_ASSERT( dds.container_name() == "c1" ) ;

	c1 = dds.container() ;
	CPPUNIT_ASSERT( c1 ) ;
	CPPUNIT_ASSERT( c1->name() == "c1" ) ;

	CPPUNIT_ASSERT( dds.num_var() == 2 ) ;

	// start adding variables and make sure not added directly to dds.
	// Make sure can find those variables without referencing the
	// container.
	try
	{
	    BaseType *bt = factory->NewInt16( "c1var1" ) ;
	    dds.add_var( bt ) ;
	    delete bt ;
	    bt = factory->NewStr( "c1var2" ) ;
	    dds.add_var( bt ) ;
	    delete bt ;
	    bt = factory->NewArray( "c1var3", factory->NewInt16( "" ) ) ;
	    dds.add_var( bt ) ;
	    delete bt ;
	}
	catch( InternalErr &e )
	{
	    CPPUNIT_FAIL( "failed to add a var" ) ;
	}
	CPPUNIT_ASSERT( dds.num_var() == 2 ) ;
	CPPUNIT_ASSERT( c1->element_count() == 3 ) ;

	BaseType *bt = dds.var( "c1var1" ) ;
	CPPUNIT_ASSERT( bt ) ;
	CPPUNIT_ASSERT( bt->name() == "c1var1" ) ;
	Int16 *ibt = dynamic_cast<Int16 *>(bt) ;
	CPPUNIT_ASSERT( ibt ) ;

	bt = dds.var( "c1var2" ) ;
	CPPUNIT_ASSERT( bt ) ;
	CPPUNIT_ASSERT( bt->name() == "c1var2" ) ;
	Str *strbt = dynamic_cast<Str *>(bt) ;
	CPPUNIT_ASSERT( strbt ) ;

	dds.del_var( "c1var2" ) ;
	bt = dds.var( "c1var2" ) ;
	CPPUNIT_ASSERT( bt == 0 ) ;
	CPPUNIT_ASSERT( dds.num_var() == 2 ) ;
	CPPUNIT_ASSERT( c1->element_count() == 2 ) ;

	// set container to "", add vars, make sure added directly to dds.
	// Make sure can find those variables
	dds.container_name( "" ) ;
	CPPUNIT_ASSERT( dds.container_name() == "" ) ;
	CPPUNIT_ASSERT( dds.container() == 0 ) ;
	try
	{
	    BaseType *bt = factory->NewByte( "var1" ) ;
	    dds.add_var( bt ) ;
	    delete bt ;
	    bt = factory->NewFloat64( "var2" ) ;
	    dds.add_var( bt ) ;
	    delete bt ;
	    bt = factory->NewUInt32( "var3" ) ;
	    dds.add_var( bt ) ;
	    delete bt ;
	}
	catch( InternalErr &e )
	{
	    CPPUNIT_FAIL( "failed to add a var" ) ;
	}
	CPPUNIT_ASSERT( dds.num_var() == 5 ) ;
	CPPUNIT_ASSERT( c1->element_count() == 2 ) ;

	BaseType::btp_stack btps ;
	bt = dds.var( "c1var1", btps ) ;
	CPPUNIT_ASSERT( bt && bt->name() == "c1var1" ) ;
	CPPUNIT_ASSERT( btps.size() == 1 ) ;
	BaseType *btp = btps.top() ;
	CPPUNIT_ASSERT( btp && btp->name() == "c1" ) ;
	btps.pop() ;

	bt = dds.var( "var1" ) ;
	CPPUNIT_ASSERT( bt ) ;
	CPPUNIT_ASSERT( bt->name() == "var1" ) ;
	Byte *bbt = dynamic_cast<Byte *>(bt) ;
	CPPUNIT_ASSERT( bbt ) ;

	bt = c1->var( "var1" ) ;
	CPPUNIT_ASSERT( bt == 0 ) ;

	dds.del_var( "var3" ) ;
	bt = dds.var( "var3" ) ;
	CPPUNIT_ASSERT( bt == 0 ) ;
	CPPUNIT_ASSERT( dds.num_var() == 4 ) ;
	CPPUNIT_ASSERT( c1->element_count() == 2 ) ;

	// set to c2, add variables and make sure not added directly to dds.
	// Make sure can find those variables without referencing the
	// container.
	dds.container_name( "c2" ) ;
	CPPUNIT_ASSERT( dds.container_name() == "c2" ) ;
	c2 = dds.container() ;
	CPPUNIT_ASSERT( c2 && c2->name() == "c2" ) ;

	try
	{
	    BaseType *bt = factory->NewInt32( "c2var1" ) ;
	    dds.add_var( bt ) ;
	    delete bt ;
	    bt = factory->NewStr( "c2var2" ) ;
	    dds.add_var( bt ) ;
	    delete bt ;
	    Structure *s = factory->NewStructure( "c2var3" ) ;
	    Structure *s1 = factory->NewStructure( "c2var3var1" ) ;
	    bt = factory->NewInt16( "c2var3var1var1" ) ;
	    s1->add_var( bt ) ;
	    delete bt ; bt = 0 ;
	    bt = factory->NewInt16( "c2var3var1var2" ) ;
	    s1->add_var( bt ) ;
	    delete bt ; bt = 0 ;
	    s->add_var( s1 ) ;
	    delete s1 ; s1 = 0 ;
	    dds.add_var( s ) ;
	    delete s ; s = 0 ;
	}
	catch( InternalErr &e )
	{
	    CPPUNIT_FAIL( "failed to add a var" ) ;
	}
	CPPUNIT_ASSERT( dds.num_var() == 4 ) ;
	CPPUNIT_ASSERT( c2->element_count() == 3 ) ;

	bt = dds.var( "c2var1" ) ;
	CPPUNIT_ASSERT( bt && bt->name() == "c2var1" ) ;
	Int32 *i32bt = dynamic_cast<Int32 *>( bt ) ;
	CPPUNIT_ASSERT( i32bt ) ;

	bt = dds.var( "c2var2" ) ;
	CPPUNIT_ASSERT( bt && bt->name() == "c2var2" ) ;
	strbt = dynamic_cast<Str *>( bt ) ;
	CPPUNIT_ASSERT( strbt ) ;

	bt = dds.var( "c2var3" ) ;
	CPPUNIT_ASSERT( bt && bt->name() == "c2var3" ) ;
	Structure *sbt = dynamic_cast<Structure *>( bt ) ;
	CPPUNIT_ASSERT( sbt ) ;

	bt = dds.var( "var1" ) ;
	CPPUNIT_ASSERT( bt == 0 ) ;

	bt = dds.var( "var2" ) ;
	CPPUNIT_ASSERT( bt == 0 ) ;

	bt = dds.var( "c2var3var1var2", btps ) ;
	CPPUNIT_ASSERT( bt && bt->name() == "c2var3var1var2" ) ;
	CPPUNIT_ASSERT( btps.size() == 3 ) ;
	btp = btps.top() ;
	CPPUNIT_ASSERT( btp && btp->name() == "c2" ) ;
	btps.pop() ;
	btp = btps.top() ;
	CPPUNIT_ASSERT( btp && btp->name() == "c2var3" ) ;
	btps.pop() ;
	btp = btps.top() ;
	CPPUNIT_ASSERT( btp && btp->name() == "c2var3var1" ) ;
	btps.pop() ;

	dds.del_var( "c2var1" ) ;
	bt = dds.var( "c2var1" ) ;
	CPPUNIT_ASSERT( bt == 0 ) ;
	CPPUNIT_ASSERT( dds.num_var() == 4 ) ;
	CPPUNIT_ASSERT( c2->element_count() == 2 ) ;

	// print the dds and make sure it looks good.
	ostringstream sstrm ;
	dds.print( sstrm ) ;
	cout << sstrm.str() << endl ;
	CPPUNIT_ASSERT( sstrm.str() == containerprint ) ;
    }
};

CPPUNIT_TEST_SUITE_REGISTRATION( ddsT ) ;

/* NOTHING NEEDS TO BE CHANGED BELOW HERE */

int main( int, char ** )
{
    CppUnit::TextUi::TestRunner runner ;
    CppUnit::TestFactoryRegistry &registry =
	CppUnit::TestFactoryRegistry::getRegistry() ;
    runner.addTest( registry.makeTest() ) ;
    runner.setOutputter( CppUnit::CompilerOutputter::defaultOutputter(
                                                        &runner.result(),
                                                        std::cerr ) );
    bool wasSuccessful = runner.run( "", false ) ;
    return wasSuccessful ? 0 : 1;
}

