// **********************************************************************
//
// Copyright (c) 1999
// Object Oriented Concepts, Inc.
// Billerica, MA, USA
//
// All Rights Reserved
//
// **********************************************************************

#include <OB/CORBA.h>
#include <OB/Util.h>
#include <OB/Timer.h>
#include <OB/Narrow_impl.h>
#include <OB/Hashtable.h>
#include <OB/Properties.h>

#include <OB/OBNaming_skel.h>

#include <Types.h>

#include <OBNaming_impl.h>

#include <stdio.h>
#include <stdlib.h>

#include <NamingDatabase.h>
#include <Logger.h>

#include <Config.h> // Make sure that the compiler complains if the
                    // configuration doesn't match

static void
usage(const char* progName)
{
    cerr << "Usage:\n";
    cerr << progName << " [options]\n"
	"\n"
	"Options:\n"
	"-h, --help             Show this message.\n"
	"-v, --version          Show Naming Service version.\n"
	"-i, --ior              Print IOR on standard output.\n"
	"-n, --no-updates       No automatic updates with ORBacus Names.\n"
        "-s, --start            Run for the first time.\n"
        "-d, --database FILE    Use FILE as the database.\n"
        "-t, --timeout MINS     Purge the database every MINS minutes.\n"
	"                       (The default is 5 minutes.)\n"
	;
}

int
main(int argc, char* argv[], char*[])
{
    try
    {
	//
	// Create ORB.
	//
	CORBA_ORB_var orb = CORBA_ORB_init(argc, argv);

	//
	// Resolve the service object if available as an initial reference.
	//
	const char* service = "NameService";
	CORBA_Object_var serviceObject;
	try
	{
	    serviceObject = orb -> resolve_initial_references(service);
	}
	catch(CORBA_ORB::InvalidName&)
	{
	}
	
	//
	// Set the IIOP port based on the port defined in the IIOP profile
	// of the service object (if it exists). Then create the BOA.
	//
	CORBA_BOA::set_iiop_port_from_object(serviceObject);
	CORBA_BOA_var boa = orb -> BOA_init(argc, argv);
	
#ifdef HAVE_JTC
	orb -> conc_model(CORBA_ORB::ConcModelThreaded);

	//
	// Ensure the BOA thread model is suitable. If the BOA
	// threading model is not thread pool is thread per request
	// then we force thread per request. The naming service
	// has to be able to accept callbacks from the same client
	// therefore thread per client is not acceptable.
	//
	CORBA_BOA::ConcModel model = boa -> conc_model();
        if(model != CORBA_BOA::ConcModelThreadPool &&
           model != CORBA_BOA::ConcModelThreadPerRequest)
        {
	    boa -> conc_model(CORBA_BOA::ConcModelThreadPerRequest);
        }
#else
	//
	// If we haven't compiled with JThreads/C++ we run in reactive
	// model.
	//
	orb -> conc_model(CORBA_ORB::ConcModelReactive);
	boa -> conc_model(CORBA_BOA::ConcModelReactive);
#endif
	
	//
	// Get options.
	//
	bool ior = false;
	bool noUpdates = false;
	bool start = false;
	int timeout = 5; // Every 5 minutes.
	CORBA_String_var database;
	
	//
	// Check properties first.
	//
	OBProperties* properties = OBProperties::instance();
	OBProperties::KeySeq keys = properties -> getKeys("ooc.naming.");
	{
	    for(CORBA_ULong i = 0; i < keys.length(); ++i)
	    {
		const char* key = keys[i];
		const char* value = properties -> getProperty(key);
		assert(value != 0);
		
		if(strcmp(key, "ooc.naming.database") == 0)
		{
		    database = CORBA_string_dup(value);
		}
		else if(strcmp(key, "ooc.naming.timeout") == 0)
		{
		    timeout = atoi(value);
		}
		else if(strcmp(key, "ooc.naming.no_updates") == 0)
		{
		    noUpdates = (strcmp(value, "true") == 0) ||
			(strcmp(value, "TRUE") == 0) ||
			(strcmp(value, "1") == 0);
		}
		else
		{
		    cerr << argv[0] << ": `" << key << "': unknown property"
			 << endl;
		}
	    }
	}
	
	for(int i = 1 ; i < argc && *argv[i] == '-' ; i++)
	{
	    if(strcmp(argv[i], "--help") == 0 ||
	       strcmp(argv[i], "-h") == 0)
	    {
		usage(argv[0]);
		return 0;
	    }
	    else if(strcmp(argv[i], "--version") == 0 ||
		    strcmp(argv[i], "-v") == 0)
	    {
		cerr << "Naming Service " << OBVersion << endl;
		return 0;
	    }
	    else if(strcmp(argv[i], "--ior") == 0 ||
		    strcmp(argv[i], "-i") == 0)
	    {
		ior = true;
	    }
	    else if(strcmp(argv[i], "--no-updates") == 0 ||
		    strcmp(argv[i], "-n") == 0)
	    {
		noUpdates = true;
	    }
	    else if(strcmp(argv[i], "--timeout") == 0 ||
		    strcmp(argv[i], "-t") == 0)
	    {
		++i;
		if(i >= argc)
		{
		    cerr << argv[0] << ": " << argv[i-1]
			 << " expects argument" << endl;
		    usage(argv[0]);
		    return 1;
		}
		timeout = atoi(argv[i]);
	    }
	    else if(strcmp(argv[i], "--database") == 0 ||
		    strcmp(argv[i], "-d") == 0)
	    {
		++i;
		if(i >= argc)
		{
		    cerr << argv[0] << ": " << argv[i-1]
			 << " expects argument" << endl;
		    usage(argv[0]);
		    return 1;
		}
		database = CORBA_string_dup(argv[i]);
	    }
	    else if(strcmp(argv[i], "--start") == 0 ||
		    strcmp(argv[i], "-s") == 0)
	    {
		start = true;
	    }
	    else
	    {
		cerr << argv[0] << ": unknown option `"
		     << argv[i] << "'\n" << endl;
		usage(argv[0]);
		return 1;
	    }
	}
	
	if(timeout <= 0)
	{
	    cerr << argv[0] << ": timeout value must be > 0" << endl;
	    return 1;
	}
	
	if(start && database == 0)
	{
	    cerr << argv[0]
		 << ": --start (-s) requires the the --database (-d) option"
		 << endl;
	    return 1;
	}
	
	//
	// Print IOR on standard output.
	//
	properties -> setProperty("ooc.naming.display_root_ior",
				  (ior) ? "true" : "false");
	
	NamingContextSet* ncs = new NamingContextSet(63);
	NamingDatabase* store = 0;
	LoggerHandle logger = 0;
	
	CosNaming_NamingContext_ptr root;
        
	if(database != 0)
	{
	    //
	    // Create a new NamingDatabase.
	    //
	    store = new NamingDatabase(boa, orb, database, ncs, noUpdates,
				       start);
	    
	    //
	    // Start logger.
	    //
	    logger = new Logger(store, timeout*60);
#ifdef HAVE_JTC
	    logger -> start();
#endif
	}
	else
	{
	    cerr << "Running in non-persistent mode." << endl;
	}
        
	//
	// Create the root naming context.
	//
	if(start || database == 0)
	    root = new CosNaming_OBNamingContext_impl(
		orb, store, ncs, "NameService", noUpdates);
	
	//
	// Run implementation.
	//
	boa -> impl_is_ready(CORBA_ImplementationDef::_nil());

#ifdef HAVE_JTC
	if(logger)
	{
	    logger -> halt();
	    while(logger -> isAlive())
	    {
		try
		{
		    logger -> join();
		}
		catch(const JTCInterruptedException&)
		{
		}
	    }
	}
	logger = 0;
#else
	delete logger;
#endif
	//
	// Clean up all contexts.
	//
	NamingContextSet::Enumerator contexts = ncs -> keys();

	CORBA_ULong context;
	for(context = 0; context < contexts.length(); context++)
	{
	    CosNaming_NamingContext_ptr nc;
	    ncs -> get(contexts[context], nc);
	    CosNaming_OBNamingContext_impl* impl =
		CosNaming_OBNamingContext_impl::_narrow_impl(nc);
	    impl -> _OB_releaseInternal();
	    CORBA_release(impl);
	}
	delete ncs;
	delete store;
    }
    catch(CORBA_SystemException& ex)
    {
	OBPrintException(ex);
	return 1;
    }

    return 0;
}
