/*
  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.  
*/   


#if   !defined(__COMMON_HPP)
#include <Common.hpp>
#endif

#if   !defined(__MAZEBUILDER_HPP)
#include <MazeBuilder.hpp>
#endif

#if   !defined(__MAZEBUILDERFACTROY_HPP)
#include <MazeBuilderFactory.hpp>
#endif

#if   !defined(__MAZE_HPP)
#include <Maze.hpp>
#endif

#if   !defined(__DOOR_HPP)
#include <Door.hpp>
#endif

#if   !defined(__ROOM_HPP)
#include <Room.hpp>
#endif

#if   !defined(__WALLFACTORY_HPP)
#include <WallFactory.hpp>
#endif

using namespace corelinux;

//
// A few things
//

static   AllocatorPtr   aHoldingArea( NULLPTR);
const    RoomNumber     gRootNumber( 1 );
const    RoomNumber     gMaxRooms( 20 );

//
// Ease of use macros
//

#define  USEFACTORYAS( aVar ) \
   MazeBuilderFactoryPtr (aVar)( dynamic_cast<MazeBuilderFactoryPtr>( this->getFactory() ) )

//
// Protected constructor
//

MazeBuilder::MazeBuilder( void ) throw(Assertion)
   :
   Builder<Maze,NameIdentifier>()
{
   NEVER_GET_HERE;
}
//
// Default constructor
//

MazeBuilder::MazeBuilder( MazeBuilderFactoryPtr aFactory )
   :
   Builder<Maze,NameIdentifier>
      (
       (AbstractFactory<NameIdentifier>*)(aFactory)
      )
{

   //
   // First get the name
   //

   NameIdentifier gWallName( MazeBuilderFactory::getWallIdentifier() );

   //
   // We need to substitute the factory default
   // Wall allocator with the flyweight factory
   //

   aHoldingArea = aFactory->setAllocator
                     (
                        gWallName,
                        new WallFactory
                     );

   theSideMap.clear();
   this->constructSideMap();
}

//
// Copy constructor
//

MazeBuilder::MazeBuilder( MazeBuilderCref aBuilder )
   :
   Builder<Maze,NameIdentifier>(aBuilder.getFactory())
{
   theSideMap.clear();
   this->constructSideMap();
}

//
// Virtual destructor, we remove the substituted
// flyweight factory
//

MazeBuilder::~MazeBuilder( void )
{
   USEFACTORYAS( aFactory );

   NameIdentifier gWallName( MazeBuilderFactory::getWallIdentifier() );

   aHoldingArea = aFactory->setAllocator
                     (
                        gWallName,
                        aHoldingArea
                     );

   delete aHoldingArea;
}

//
// Assignment operator
//

MazeBuilderRef MazeBuilder::operator=( MazeBuilderCref )
   throw( Assertion )
{
   NEVER_GET_HERE;
   return (*this);
}

//
// Equality operator
//

bool  MazeBuilder::operator==( MazeBuilderCref aRef ) const
{
   return (this == &aRef);
}
//
// createProduct called from Builder during create
// public method. We create the first room and
// then populate and interconnect the rooms
//

MazePtr  MazeBuilder::createProduct( void ) const
{
   USEFACTORYAS( aFactory );

   NameIdentifier gRoomName( MazeBuilderFactory::getRoomIdentifier() );

   RoomPtr  aRoom
      ( 
         dynamic_cast<RoomPtr>(aFactory->createPart( gRoomName ) )
      );

   aRoom->setRoomNumber( gRootNumber );

   NameIdentifier gWallName( MazeBuilderFactory::getWallIdentifier() );

   MapSitePtr  aSide( aFactory->createPart(gWallName) );

   aRoom->setSide( NORTH, aSide );
   aRoom->setSide( EAST, aSide );
   aRoom->setSide( WEST, aSide );
   aRoom->setSide( SOUTH, aSide );

   MazePtr  aMaze( new Maze(aRoom) );

   createRooms( aMaze );
   connectRoomsWithDoors( aMaze );
   
   return aMaze;
}

//
// destroyProduct called from Builder during destroy
// public method
//

void  MazeBuilder::destroyProduct( MazePtr aMaze ) const
{

   USEFACTORYAS( aFactory );

   //
   // First disconnect and destroy doors
   //

   disconnectAndDestroyDoors( aMaze );

   //
   // Now get rid of the rooms
   //

   RoomMapRef        rooms( aMaze->getRooms() );
   RoomMapIterator   begin( rooms.begin() );
   RoomMapIterator   end( rooms.end() );
   NameIdentifier    gRoomName( MazeBuilderFactory::getRoomIdentifier() );

   while( begin != end )
   {
      aFactory->destroyPart( gRoomName, (*begin).second );
      ++begin;
   }

   //
   // Drop the maze
   //

   delete aMaze;
}

//
// Our method for instantiating a bunch of rooms
//

void  MazeBuilder::createRooms( MazePtr aMaze ) const
{
   USEFACTORYAS( aFactory );
   
   NameIdentifier gRoomName( MazeBuilderFactory::getRoomIdentifier() );

   for( RoomNumber   x=gRootNumber+1; x <= gMaxRooms; ++x )
   {
      RoomPtr  aRoom
         ( 
            dynamic_cast<RoomPtr>(aFactory->createPart( gRoomName ) )
         );

      aRoom->setRoomNumber( x );

      NameIdentifier gWallName( MazeBuilderFactory::getWallIdentifier() );

      MapSitePtr  aSide( aFactory->createPart(gWallName) );

      aRoom->setSide( NORTH, aSide );
      aRoom->setSide( EAST, aSide );
      aRoom->setSide( WEST, aSide );
      aRoom->setSide( SOUTH, aSide );

      aMaze->addRoom(aRoom);
   }

}

//
// Our method for creating doors and connecting them
//

void  MazeBuilder::connectRoomsWithDoors( MazePtr aMaze ) const
{
   USEFACTORYAS( aFactory );

   RoomMapRef           rooms( aMaze->getRooms() );
   SideMapCref          aMap( getSideMap() );
   NameIdentifier       gDoorName( MazeBuilderFactory::getDoorIdentifier() );
   SideMapConstIterator begin(aMap.lower_bound( NORTH ));
   SideMapConstIterator end(aMap.upper_bound( NORTH ));

   //
   // For each direction we create a door
   // set what is on both sides of the door
   // and set the door into the room on opposite sides
   //

   while( begin != end )
   {
      DoorPair aPair( (*begin).second );

      RoomPtr  nRoom( (*rooms.find( aPair.first )).second );
      RoomPtr  sRoom( (*rooms.find( aPair.second )).second );

      if( nRoom != NULLPTR && sRoom != NULLPTR )
      {
         DoorPtr  aDoor
            ( 
               dynamic_cast<DoorPtr>(aFactory->createPart( gDoorName ) )
            );

         aDoor->setFirstRoom(nRoom);
         aDoor->setSecondRoom(sRoom);
         nRoom->setSide( NORTH, aDoor );
         sRoom->setSide( SOUTH, aDoor );

      }
      else
      {
         NEVER_GET_HERE;
      }

      ++begin;
   }

   begin = aMap.lower_bound( EAST );
   end = aMap.upper_bound( EAST );

   while( begin != end )
   {
      DoorPair aPair( (*begin).second );

      RoomPtr  eRoom( (*rooms.find( aPair.first )).second );
      RoomPtr  wRoom( (*rooms.find( aPair.second )).second );

      if( eRoom != NULLPTR && wRoom != NULLPTR )
      {
         DoorPtr  aDoor
            ( 
               dynamic_cast<DoorPtr>(aFactory->createPart( gDoorName ) )
            );

         aDoor->setFirstRoom(eRoom);
         aDoor->setSecondRoom(wRoom);
         eRoom->setSide( EAST, aDoor );
         wRoom->setSide( WEST, aDoor );

      }
      else
      {
         NEVER_GET_HERE;
      }

      ++begin;
   }

   begin = aMap.lower_bound( WEST );
   end = aMap.upper_bound( WEST );

   while( begin != end )
   {
      DoorPair aPair( (*begin).second );

      RoomPtr  wRoom( (*rooms.find( aPair.first )).second );
      RoomPtr  eRoom( (*rooms.find( aPair.second )).second );

      if( wRoom != NULLPTR && eRoom != NULLPTR )
      {
         DoorPtr  aDoor
            ( 
               dynamic_cast<DoorPtr>(aFactory->createPart( gDoorName ) )
            );

         aDoor->setFirstRoom(wRoom);
         aDoor->setSecondRoom(eRoom);
         wRoom->setSide( WEST, aDoor );
         eRoom->setSide( EAST, aDoor );

      }
      else
      {
         NEVER_GET_HERE;
      }

      ++begin;
   }

   begin = aMap.lower_bound( SOUTH );
   end = aMap.upper_bound( SOUTH );

   while( begin != end )
   {
      DoorPair aPair( (*begin).second );

      RoomPtr  sRoom( (*rooms.find( aPair.first )).second );
      RoomPtr  nRoom( (*rooms.find( aPair.second )).second );

      if( sRoom != NULLPTR && nRoom != NULLPTR )
      {
         DoorPtr  aDoor
            ( 
               dynamic_cast<DoorPtr>(aFactory->createPart( gDoorName ) )
            );

         aDoor->setFirstRoom(sRoom);
         aDoor->setSecondRoom(nRoom);
         sRoom->setSide( SOUTH, aDoor );
         nRoom->setSide( NORTH, aDoor );
      }
      else
      {
         NEVER_GET_HERE;
      }

      ++begin;
   }
}

//
// Destroys doors and cleans up rooms
//

void  MazeBuilder::disconnectAndDestroyDoors( MazePtr aMaze ) const
{
   USEFACTORYAS( aFactory );

   RoomMapRef           rooms( aMaze->getRooms() );
   SideMapCref          aMap( getSideMap() );
   SideMapConstIterator begin(aMap.lower_bound( NORTH ));
   SideMapConstIterator end(aMap.upper_bound( NORTH ));
   NameIdentifier       gDoorName( MazeBuilderFactory::getDoorIdentifier() );

   //
   // For each direction, find the first door and
   // remove it's reference from the joining rooms, then
   // destroy the door
   //

   while( begin != end )
   {
      DoorPair aPair( (*begin).second );

      RoomPtr     aRoom1( (*rooms.find( aPair.first )).second );
      RoomPtr     aRoom2( (*rooms.find( aPair.second )).second );
      MapSitePtr  aDoor( aRoom1->getSide( NORTH ) );

      aRoom1->setSide( NORTH, NULLPTR );
      aRoom2->setSide( SOUTH, NULLPTR );

      aFactory->destroyPart( gDoorName, aDoor );

      ++begin;
   }

   begin = aMap.lower_bound( EAST );
   end = aMap.upper_bound( EAST );

   while( begin != end )
   {
      DoorPair aPair( (*begin).second );

      RoomPtr     aRoom1( (*rooms.find( aPair.first )).second );
      RoomPtr     aRoom2( (*rooms.find( aPair.second )).second );
      MapSitePtr  aDoor( aRoom1->getSide( EAST ) );

      aRoom1->setSide( EAST, NULLPTR );
      aRoom2->setSide( WEST, NULLPTR );

      aFactory->destroyPart( gDoorName, aDoor );

      ++begin;
   }

   begin = aMap.lower_bound( WEST );
   end = aMap.upper_bound( WEST );

   while( begin != end )
   {
      DoorPair aPair( (*begin).second );

      RoomPtr     aRoom1( (*rooms.find( aPair.first )).second );
      RoomPtr     aRoom2( (*rooms.find( aPair.second )).second );
      MapSitePtr  aDoor( aRoom1->getSide( WEST ) );

      aRoom1->setSide( WEST, NULLPTR );
      aRoom2->setSide( EAST, NULLPTR );

      aFactory->destroyPart( gDoorName, aDoor );

      ++begin;
   }

   begin = aMap.lower_bound( SOUTH );
   end = aMap.upper_bound( SOUTH );
   while( begin != end )
   {
      DoorPair aPair( (*begin).second );

      RoomPtr     aRoom1( (*rooms.find( aPair.first )).second );
      RoomPtr     aRoom2( (*rooms.find( aPair.second )).second );
      MapSitePtr  aDoor( aRoom1->getSide( SOUTH ) );

      aRoom1->setSide( SOUTH, NULLPTR );
      aRoom2->setSide( NORTH, NULLPTR );

      aFactory->destroyPart( gDoorName, aDoor );

      ++begin;
   }

}

//
// Retrieve our door association map
//

SideMapCref  MazeBuilder::getSideMap( void ) const
{
   return theSideMap;
}

/*
Our static room layout is basically what rooms are
connected to doors, our maze:

            10 
            9       
        20  8       19 
            7       18
            6       17
            5 14 15 16
            4
            3
   13 12 11 2
            1 <--- Starting point

*/  

struct   _Layout
{
   Direction   firstSide;
   RoomNumber  firstRoom;
   RoomNumber  secondRoom;
} ;

struct  _Layout  theLayout[] =
{
   {NORTH,1,2},{NORTH,2,3},{NORTH,3,4},
   {NORTH,4,5},{NORTH,5,6},{NORTH,6,7},
   {NORTH,7,8},{NORTH,8,9},{NORTH,9,10},
   {NORTH,16,17},{NORTH,17,18},{NORTH,18,19},
   {WEST,2,11},{WEST,11,12},{WEST,12,13},
   {EAST,5,14},{WEST,14,15},{WEST,15,16},
   {NORTH,0,0}
};

//
// Feed the map for later usage, the ambitious would
// write a data feed from somewhere
//

void  MazeBuilder::constructSideMap( void )
{
   for( Count x = 0; theLayout[x].firstRoom != 0; ++x )
   {
      theSideMap.insert
         ( 
            SideMap::value_type
               (
                  theLayout[x].firstSide,
                  DoorPair( theLayout[x].firstRoom, theLayout[x].secondRoom )
               )
         );
   }
}

/*
   Common rcs information do not modify
   $Author: frankc $
   $Revision: 1.2 $
   $Date: 2000/04/21 02:38:47 $
   $Locker:  $
*/

