//#
//# Time Server
//# Copyright 2004 by Eric Y. Theriault
//# All Rights Reserved.
//# http://www.eyt.ca/CORBA
//#
#ifdef EYT_MICO
#include "Mico/Time.h"
#include <coss/CosNaming.h>
#else
#ifdef EYT_TAO
#include "TAO/TimeS.h"
#include <orbsvcs/Naming/Naming_Utils.h>
#else
#error Undefined ORB type.
#endif
#endif
#include "MulticastSocket.h"
#ifndef _WIN32
#include <pthread.h>
#endif
#include <iostream>
#include <fstream>
#include <errno.h>
#include <time.h>

using namespace TimeTools;

namespace {
   //
   // Port to multicast on.  This number is random. 
   //
   const int MulticastPort = 7007; 

   //
   // Address to multicast to.  The default is link-local, which is
   // generally OK, but in the presence of a segmented network with
   // routers, this address and the TTL will have to be changed.
   //
   const char *MulticastAddress = "224.0.0.1";

   //
   // This is the message that is multicasted to discover the IOR.
   //
   const char *MulticastDiscoverServer = "DiscoverIOR";

   class Time_impl : public POA_TimeTools::Time {
   public:
      virtual ~Time_impl()
      {
      }

      // Acquire the time...
      TimeTools::TimeOfDay getTime() throw ( CORBA::SystemException );
   };

   // Acquire the time...
   TimeTools::TimeOfDay Time_impl::getTime() throw ( CORBA::SystemException )
   {
      // Acquire the time
      time_t time_now = time( 0 );
      //TODO: Should probably be localtime_r, but MSVC2003 does not have it.
      struct tm *time = localtime( &time_now );

      // Adapt the time.
      TimeOfDay t;
      t.hour = time->tm_hour;
      t.minute = time->tm_min;
      t.second = time->tm_sec;

      // Return and clean up
      return t;
   }

   //
   // Multicast service
   //
   // For more information on Multicasting, please see Chapter 21 [549]
   // of W. Richard Stevens, Bill Fenner, and Andrew M. Rudoff's UNIX
   // Network Programming: Volume 1: The Sockets Networking API--Third
   // Edition, Addison-Wesley, 2004.
   //
   class MulticastIORServer {
   private:
      //
      // IOR to multicast
      //
      std::string ior;

      //
      // Multicast Socket
      //
      MulticastSocket mcast;

      //
      // Pthread handle
      //
#ifdef _WIN32
      HANDLE handle;
#else
      pthread_t handle;
#endif

   private:
      //
      // This awaits for a multicast packet, and if anything is received,
      // it will validate the message and respond with an IOR.
      //
      void execute()
      {
         while ( true ) {
            try {
               // Acquire a packet
               struct sockaddr_in address;
               std::string received = mcast.receive( address );

               // If the packet is good, reply.
               //
               // In a real application, a checksum or something could
               // be used to validate the message, and the reply should
               // contain a more verbose message.
               if ( strcmp( received.c_str(), MulticastDiscoverServer ) == 0 ) {
                  mcast.send( address, ior.c_str() );
               }
            } catch ( std::exception & e ) {
               std::cerr << "Exception raised: " << e.what() << std::endl;
            }
         }
      }

      //
      // Starts the pthread
      //
#ifdef _WIN32
      static DWORD WINAPI run( LPVOID ptr )
#else
      static void *run( void * ptr )
#endif
      {
         reinterpret_cast<MulticastIORServer*>( ptr )->execute();
         return 0;
      }

   public:
      //
      // Creates a service to answer to inbound Multicasts.
      //
      explicit MulticastIORServer( const std::string &IOR ):
              ior( IOR ),
              mcast( MulticastPort, true )
      {
         // Please note that a real implementation would provide options
         // to set the bind address, Time To Live, etc., but this is
         // simply to demonstrate the functionality. 
         mcast.joinGroup( MulticastAddress );
      }

      //
      // Starts the service via a thread.
      //
      void start()
      {
#ifdef _WIN32
          unsigned long threadId = 0;
          handle = CreateThread(
                                static_cast<LPSECURITY_ATTRIBUTES>( 0 ),
                                0,
                                MulticastIORServer::run,
                                static_cast<LPVOID>( this ),
                                0,
                                &threadId
                               );
#else
          if ( pthread_create( &handle, 0, MulticastIORServer::run,
                              this ) != 0 ) {
            throw std::exception();
         }
#endif
      }
   };
};

int main( int argc, char ** argv )
{
   std::string iorFile = "time.ior";

   try {
      // Create and Initialize the ORB
      CORBA::ORB_var orb = CORBA::ORB_init( argc, argv );

      // Get a reference to the root POA and activate the POAManager
      CORBA::Object_var rootPOA = orb->resolve_initial_references( "RootPOA" );
      PortableServer::POA_var poa = PortableServer::POA::_narrow( rootPOA );

      // Activate POA manager
      PortableServer::POAManager_var manager = poa->the_POAManager();
      manager->activate();

      // Create the server object
      Time_impl timeImpl;

      // Create the object reference
      Time_var ref = timeImpl._this();
      CORBA::String_var ior = orb->object_to_string( ref );

      // Create the file
      std::ofstream file( iorFile.c_str(), std::ios::out );
      if ( file.fail() ) {
         std::cerr << "Cannot open " << iorFile << ": "
                   << strerror( errno ) << std::endl;
         return 1;
      }
      file << ior;
      file.close();
      if ( file.fail() ) {
         std::cerr << "Cannot open " << iorFile << ": "
                   << strerror( errno ) << std::endl;
         return 1;
      }

      // Let's get the root naming context
      CORBA::Object_var objectReference =
                       orb->resolve_initial_references( "NameService" );
      CosNaming::NamingContext_var ncRef =
                   CosNaming::NamingContext::_narrow( objectReference );

      // Start the Multicast Server
      std::string reference = orb->object_to_string( objectReference );
      MulticastIORServer mcast( reference );
      mcast.start();

      // Bind the Time object to the reference.
      std::string timeName = "Time";
      CosNaming::Name name;
      name.length( 1 );
      name[ 0 ].id = CORBA::string_dup( timeName.c_str() );
      name[ 0 ].kind = CORBA::string_dup( "" );
      ncRef->rebind( name, ref );

      // Accept requests
      std::cout << "TimeServer is ready and waiting..." << std::endl;
      orb->run();
   } catch ( const CORBA::Exception & e ) { 
      std::cerr << "Caught a CORBA exception." << std::endl;
#ifdef EYT_MICO
      e._print( std::cerr );
      std::cerr << std::endl;
#else
#ifdef EYT_TAO
      std::cerr << e._name() << std::endl;
#else
#error Need a more verbose error here.
#endif
#endif
      return 1;
   } catch ( const std::exception & e ) { 
      std::cerr << "Caught Exception: " << e.what() << std::endl;
      return 1;
   } catch ( ... ) { 
      std::cerr << "Unknown exception caught." << std::endl;
      return 1;
   }
}

