/*
  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 examp10.cpp
   This example is to show use of the Flyweight pattern.
   
   What we do here is utilize a flyweight because of the following
   assumptions:
   
   Assumption: Rooms have sides with cardinality of [4]
   Assumption: The side can be either a door or a wall
   Assumption: The room has at least one (1) door
   
   Unless it is an extreme maze, there is a high probability
   that there are more walls in a room than doors. By using
   the Flyweight pattern we can reduce our instance requirements
   by ( rooms * (sides==walls) ) - 1, for example:
   
   20 rooms with an average of 3 walls each saves 59 wall instances!
   
   The memory savings are the walls * sizeof( wall ) in general and
   the cycle savings are the new and deletes needed to instantiate
   and destroy each wall.
   
   Then there is the probable reduction in the locality of reference 
   faults when actually doing something with a wall. But that is 
   beyond the scope of this example.
   
   Anyway, in this example we play with pre-building a room with
   three (3) walls each. In addition, we utilize the Room interface
   of setting a side to the room instance.
   
   Refer to ex9 for some of the first order class implementations. You
   will notice that we reuse almost everything from ex9 except for the
   Wall (in theory), the MazeFactory for creating the Wall, and the
   changes in the examp10.cpp source.
   
   A more realistic implementation would have a MazeFactory that
   is configurable to register allocators so we don't need to
   expose the application to whether it is using a Flyweight
   or a true Class instance. Stay tuned, with Builder, Factory
   Method and a few other patterns we will achieve this and
   more.
   
*/                   


#include <Common.hpp>
#include <MazeFactory.hpp>
#include <WallFactory.hpp>

#include <Vector.hpp>
#include <Map.hpp>

using namespace corelinux;

#include <iostream>
#include <exception>

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

//
// In module function prototypes
//

int   main( void );

void  doWork( MazeFactoryRef , WallFactoryRef );

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

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

int main( void )
{

   //
   // Practice gracefull exception management
   //

   cout << endl;

   try
   {
      MazeFactory aFactory;
      WallFactory aWallFactory;

      doWork( aFactory, aWallFactory );
   }

   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( MazeFactoryRef aFactory, WallFactoryRef aWallFactory )
{
   bool  keepWorking(true);

   DoorVector  doors;
   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 ;

                  //
                  // To bring this into reality, we will look
                  // to the best case scenario and auto create
                  // three (3) walls per room, all doors lead
                  // to the east!
                  //

                  aRoom->setSide( NORTH, aWallFactory.createType() );
                  aRoom->setSide( SOUTH, aWallFactory.createType() );
                  aRoom->setSide( WEST,  aWallFactory.createType() );
               }
               else
               {
                  cerr << "Room " << aNumber << " already exists!" << endl;
               }
               break;
            }

         //
         // Create a Wall. This is where we saved our job by
         // getting the maze to scale.
         //

         case  2:
            {
               IGNORE_RETURN aWallFactory.createType();
               cout << endl;

               cout << "You now have " << 
                  aWallFactory.getAllocateCount() << " wall" << 
                  ( aWallFactory.getAllocateCount() > 1 ? "s." : "." ) << 
                  endl;

               cout << endl;
               break;
            }

         //
         // Create a door, we need two (2) valid rooms that the door
         // connects. But there is a bug here. Get involved, fix 
         // the sample, become part of the open development process.
         //

         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 << "Door Creates      : " << doors.size() << endl;
   cout << "Wall Creates      : " << aWallFactory.getAllocateCount() << endl;

   cout << "\t For a general memory saving of : " << 
      sizeof( Wall ) * aWallFactory.getAllocateCount() << " bytes!" << endl;

   cout << endl;

   //
   // Clean out doors
   //

   DoorVectorIterator  dItr( doors.begin() );

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

   doors.clear();

   //
   // Clean out walls, but wait!!! We don't have to
   //

   //
   // 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.3 $
   $Date: 2000/08/31 22:51:01 $
   $Locker:  $
*/


