/*
  CoreLinux++ 
  Copyright (C) 2000 CoreLinux Consortium
  
   The CoreLinux++ Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   The CoreLinux++ Library Library is distributed in the hope that it will 
   be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with the GNU C Library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.  
*/   

/** \example examp9.cpp
   This example is to show use of the AbstractFactory and Singleton
   patterns.
   
   What we are working with is right out of the GoF book, the maze
   factory example with a couple of twists:
   
   A. We are not creating the Maze game, just a AbstractFactory.
   
   B. The behavior of the MapSite objects are not fully defined. I
   forced some assertion semantics on some things, but I left it
   mostly alone.
   
   C. Sometimes something happens when you enter it, others are
   undefined.
   
   And a couple of notes where the libcorelinux differs from
   the pattern example in the book:
   
   1. We have included a Singleton type template in the library. It's
   success depends on the adherence to protocol that you won't
   instantiate the object the singleton guards. The constructor
   for the Singleton will guard against multiple instantiations
   of the same type (Singleton<Foo> aFoo; Singleton<Foo> bFoo; the
   bFoo will assert).
              
   2. The CoreLinux++ implementation of AbstractFactory relies on
   a collection [0..n] of AbstractAllocators. This was to preserve
   the abstraction of the factory down to the problem domain; in this
   case the Maze example.
   
   3. We defered the binding of Key and collection in AbstractFactory
   so that the developer will have the ultimate flexibility in their
   designs.
   
   4. In libcorelinux++ we define a macro CORELINUX_DEFAULT_ALLOCATOR
   which is used when you just want the ::new and ::delete memory
   management. This increases ease of use, and with the addition of
   instrumentation in both the Allocator base and AbstractFactory, 
   it is easy to monitor which allocations are called more frequently.
   
   Of course, any suggestions to make it better are welcome. Anyway:
   
   We use the corelinux::Identifier as a base for NameIdentifier, we
   will use this to bind allocators instead of some numbering scheme. 
   They are also self describing in the example.
   
   We will use the CORELINUX_DEFAULT_ALLOCATOR, @see MazeFactory.cpp
   
   The adventerous are welcome to submit more complex Memory 
   Management scenarios.
   
*/                   


#include <Common.hpp>
#include <MazeFactory.hpp>
#include <Vector.hpp>
#include <Map.hpp>
#include <Singleton.hpp>

using namespace corelinux;

#include <iostream>
#include <exception>

CORELINUX_VECTOR(  DoorPtr ,  DoorVector );
CORELINUX_VECTOR(  WallPtr ,  WallVector );
CORELINUX_MAP(  RoomNumber,  RoomPtr,  less<RoomNumber> ,  RoomMap );

//
// In module function prototypes
//

int   main( void );

void  doWork( MazeFactoryPtr aFactory );

//
// Functions that work with Engine types
//

void  handleAssertion( AssertionCref );
void  handleException( ExceptionCref );

int main( void )
{

   //
   // Practice gracefull exception management
   //

   cout << endl;

   try
   {
      Singleton<MazeFactory>  aFactory;
      doWork( aFactory.instance() );
   }

   catch( AssertionRef aAssert )
   {
      handleAssertion(aAssert);
   }
   catch( ExceptionRef aException )
   {
      handleException(aException);
   }
   catch( std::exception & e )
   {
      cerr  << e.what() << endl;
   }
   catch( ... )
   {
      cerr  << "Unknown exception." << endl;
   }

   return 0;               
}

void  displayMenu( void )
{
   cout  << endl;
   cout  << "\tCreate a Room                    1" << endl;
   cout  << "\tCreate a Wall                    2" << endl;
   cout  << "\tCreate a Room                    3" << endl;
   cout  << "\tQuit the example                 4" << endl;
   cout  << endl;
}

Int   getCommand( void )
{
   displayMenu();

   Int   aOption;

   cout  << "Enter the option number on the right to execute : ";
   cin   >> aOption;

   return aOption;
}

void  doWork( MazeFactoryPtr aFactory )
{
   bool  keepWorking(true);

   DoorVector  doors;
   WallVector  walls;
   RoomMap     rooms;

   do
   {
      Int   aCommand( getCommand() );

      if( aCommand > 4 || aCommand < 0 )
      {
         cerr << "You can't enter non-numeric options!" << endl;
         aCommand = 4;
      }
      else
      {
         ;  // do nothing
      }

      switch( aCommand )
      {
         //
         // Create a new room and insure that the same
         // doesn't already exist
         //

         case  1:
            {
               RoomNumber  aNumber(0);
               cout << endl;
               cout << "Enter a RoomNumber for the new room : ";
               cin  >> aNumber;
               if( rooms.find(aNumber) == rooms.end() )
               {
                  RoomPtr  aRoom( aFactory->createRoom(aNumber) );
                  rooms[aNumber] = aRoom ;
               }
               else
               {
                  cerr << "Room " << aNumber << " already exists!" << endl;
               }
               break;
            }

         //
         // Create a Wall. This is a yawner, but the Room interface allows
         // setting up its four sides with walls or doors.
         //

         case  2:
            {
               walls.push_back( aFactory->createWall() );
               cout << endl;
               cout << "You now have " << walls.size() << " wall" << 
                  ( walls.size() > 1 ? "s." : "." ) << endl;
               cout << endl;
               break;
            }

         //
         // Create a door, we need two (2) valid rooms that the door
         // connects 
         //

         case  3:
            {
               RoomNumber  aFirstNumber(0);
               RoomNumber  aSecondNumber(0);

               cout << endl;
               cout << "Enter the first room the door connects : ";
               cin  >> aFirstNumber;
               cout << endl;
               cout << "Enter the second room the door connects : ";
               cin  >> aSecondNumber;

               if( rooms.find(aFirstNumber) == rooms.end() ||
                   rooms.find(aSecondNumber) == rooms.end() )
               {
                  cerr << "You need to enter valid room numbers." << endl;
               }
               else
               {
                  doors.push_back
                     (
                        aFactory->createDoor
                           (
                              (*rooms.find(aFirstNumber)).second,
                              (*rooms.find(aSecondNumber)).second
                           )
                     );

                  cout << "You now have " << doors.size() << " door" << 
                     ( doors.size() > 1 ? "s." : "." ) << endl;

               }
               break;
            }

         //
         // Add a parent to an object
         //

         case  4:
            keepWorking=false;
            break;

         default:
            ;  //do nothing
            break;
      }
   } while( keepWorking == true );

   //
   // Now we can display info and clean up
   //

   cout << endl;
   cout << "Pre-cleanup Factory Statistics" << endl;
   cout << "==============================" << endl;
   cout << "Total Creates     : " << aFactory->getTotalAllocates() << endl;
   cout << "Total Destroys    : " << aFactory->getTotalDeallocates() << endl;
   cout << "Room Creates      : " << rooms.size() << endl;
   cout << "Wall Creates      : " << walls.size() << endl;
   cout << "Door Creates      : " << doors.size() << endl;
   cout << endl;

   //
   // Clean out doors
   //

   DoorVectorIterator  dItr( doors.begin() );

   while( dItr != doors.end() )
   {
      aFactory->destroyDoor( (*dItr ) );
      ++dItr;
   }

   doors.clear();

   //
   // Clean out walls
   //

   WallVectorIterator  wItr( walls.begin() );

   while( wItr != walls.end() )
   {
      aFactory->destroyWall( (*wItr) );
      ++wItr;
   }

   walls.clear();

   //
   // Clean out rooms
   //

   RoomMapIterator   rItr( rooms.begin() );

   while( rItr != rooms.end() )
   {
      aFactory->destroyRoom( (*rItr).second );
      ++rItr;
   }

   rooms.clear();

   //
   // Final statistics
   //

   cout << endl;
   cout << "Post-cleanup Factory Statistics" << endl;
   cout << "-------------------------------" << endl;
   cout << "Total Creates     : " << aFactory->getTotalAllocates() << endl;
   cout << "Total Destroys    : " << aFactory->getTotalDeallocates() << endl;
   cout << endl;

}

//
// Peform default (just show it)
//

void  handleAssertion( AssertionCref aAssert )
{
   cerr << aAssert.getFile() << ":" << aAssert.getLine() << ":" << 
      "Assertion: ";

   if( aAssert.getType() == Assertion::NEVERGETHERE )
   {
      cerr << "NEVER_GET_HERE";
   }
   else
   {
      if( aAssert.getType() == Assertion::REQUIRE )
      {
         cerr  << "REQUIRE";
      }
      else if( aAssert.getType() == Assertion::ENSURE )
      {
         cerr  << "ENSURE";
      }
      else if( aAssert.getType() == Assertion::CHECK )
      {
         cerr  << "CHECK";
      }
      else 
      {
         cerr  << "ASSERT";
      }
      cerr << "( " << aAssert.getWhy() << " )";
   }

   cerr << endl;
}

void  handleException( ExceptionCref aExcp )
{
   cerr << aExcp.getFile() << ":" << aExcp.getLine() << ":" <<
      "Exception: " << aExcp.getWhy() << endl;
}

/*
   Common rcs information do not modify
   $Author: prudhomm $
   $Revision: 1.4 $
   $Date: 2000/08/31 22:47:56 $
   $Locker:  $
*/


