dw_fluffos_v1/
dw_fluffos_v1/fluffos-1.22c11/
dw_fluffos_v1/fluffos-1.22c11/ChangeLog.old/
dw_fluffos_v1/fluffos-1.22c11/Win32/
dw_fluffos_v1/fluffos-1.22c11/compat/
dw_fluffos_v1/fluffos-1.22c11/compat/simuls/
dw_fluffos_v1/fluffos-1.22c11/include/
dw_fluffos_v1/fluffos-1.22c11/mudlib/
dw_fluffos_v1/fluffos-1.22c11/testsuite/
dw_fluffos_v1/fluffos-1.22c11/testsuite/clone/
dw_fluffos_v1/fluffos-1.22c11/testsuite/command/
dw_fluffos_v1/fluffos-1.22c11/testsuite/data/
dw_fluffos_v1/fluffos-1.22c11/testsuite/etc/
dw_fluffos_v1/fluffos-1.22c11/testsuite/include/
dw_fluffos_v1/fluffos-1.22c11/testsuite/inherit/
dw_fluffos_v1/fluffos-1.22c11/testsuite/inherit/master/
dw_fluffos_v1/fluffos-1.22c11/testsuite/log/
dw_fluffos_v1/fluffos-1.22c11/testsuite/single/
dw_fluffos_v1/fluffos-1.22c11/testsuite/single/tests/compiler/
dw_fluffos_v1/fluffos-1.22c11/testsuite/single/tests/efuns/
dw_fluffos_v1/fluffos-1.22c11/testsuite/single/tests/operators/
dw_fluffos_v1/fluffos-1.22c11/testsuite/u/
dw_fluffos_v1/fluffos-1.22c11/tmp/
dw_fluffos_v1/lib/
dw_fluffos_v1/lib/binaries/cmds/
dw_fluffos_v1/lib/binaries/cmds/creator/
dw_fluffos_v1/lib/binaries/cmds/living/
dw_fluffos_v1/lib/binaries/cmds/player/
dw_fluffos_v1/lib/binaries/d/admin/obj/
dw_fluffos_v1/lib/binaries/d/liaison/
dw_fluffos_v1/lib/binaries/global/virtual/
dw_fluffos_v1/lib/binaries/global/virtual/setup_compiler/
dw_fluffos_v1/lib/binaries/obj/handlers/autodoc/
dw_fluffos_v1/lib/binaries/obj/handlers/terrain_things/
dw_fluffos_v1/lib/binaries/obj/misc/
dw_fluffos_v1/lib/binaries/obj/misc/buckets/
dw_fluffos_v1/lib/binaries/obj/monster/
dw_fluffos_v1/lib/binaries/obj/reactions/
dw_fluffos_v1/lib/binaries/obj/reagents/
dw_fluffos_v1/lib/binaries/secure/cmds/creator/
dw_fluffos_v1/lib/binaries/secure/master/
dw_fluffos_v1/lib/binaries/std/
dw_fluffos_v1/lib/binaries/std/dom/
dw_fluffos_v1/lib/binaries/std/effects/object/
dw_fluffos_v1/lib/binaries/std/guilds/
dw_fluffos_v1/lib/binaries/std/languages/
dw_fluffos_v1/lib/binaries/std/races/
dw_fluffos_v1/lib/binaries/std/room/
dw_fluffos_v1/lib/binaries/std/room/basic/
dw_fluffos_v1/lib/binaries/std/shops/
dw_fluffos_v1/lib/binaries/std/shops/inherit/
dw_fluffos_v1/lib/binaries/www/
dw_fluffos_v1/lib/cmds/guild-race/
dw_fluffos_v1/lib/cmds/guild-race/crafts/
dw_fluffos_v1/lib/cmds/guild-race/other/
dw_fluffos_v1/lib/cmds/playtester/
dw_fluffos_v1/lib/cmds/playtester/senior/
dw_fluffos_v1/lib/d/admin/
dw_fluffos_v1/lib/d/admin/log/
dw_fluffos_v1/lib/d/admin/mapper/31-10-01/mapmaker/event/
dw_fluffos_v1/lib/d/admin/meetings/
dw_fluffos_v1/lib/d/admin/obj/
dw_fluffos_v1/lib/d/admin/room/we_care/
dw_fluffos_v1/lib/d/admin/save/
dw_fluffos_v1/lib/d/dist/
dw_fluffos_v1/lib/d/dist/mtf/
dw_fluffos_v1/lib/d/dist/pumpkin/
dw_fluffos_v1/lib/d/dist/pumpkin/chars/
dw_fluffos_v1/lib/d/dist/pumpkin/desert/
dw_fluffos_v1/lib/d/dist/pumpkin/gumboot/
dw_fluffos_v1/lib/d/dist/pumpkin/hospital/
dw_fluffos_v1/lib/d/dist/pumpkin/inherit/
dw_fluffos_v1/lib/d/dist/pumpkin/map/
dw_fluffos_v1/lib/d/dist/pumpkin/plain/
dw_fluffos_v1/lib/d/dist/pumpkin/pumpkin/
dw_fluffos_v1/lib/d/dist/pumpkin/save/
dw_fluffos_v1/lib/d/dist/pumpkin/squash/
dw_fluffos_v1/lib/d/dist/pumpkin/terrain/
dw_fluffos_v1/lib/d/dist/pumpkin/woods/
dw_fluffos_v1/lib/d/dist/start/
dw_fluffos_v1/lib/d/learning/TinyTown/buildings/
dw_fluffos_v1/lib/d/learning/TinyTown/map/
dw_fluffos_v1/lib/d/learning/TinyTown/roads/
dw_fluffos_v1/lib/d/learning/add_command/
dw_fluffos_v1/lib/d/learning/arms_and_weps/
dw_fluffos_v1/lib/d/learning/chars/
dw_fluffos_v1/lib/d/learning/cutnpaste/
dw_fluffos_v1/lib/d/learning/examples/npcs/
dw_fluffos_v1/lib/d/learning/examples/player_houses/npcs/
dw_fluffos_v1/lib/d/learning/examples/terrain_map/basic/
dw_fluffos_v1/lib/d/learning/functions/
dw_fluffos_v1/lib/d/learning/handlers/
dw_fluffos_v1/lib/d/learning/help_topics/npcs/
dw_fluffos_v1/lib/d/learning/help_topics/objects/
dw_fluffos_v1/lib/d/learning/help_topics/rcs_demo/
dw_fluffos_v1/lib/d/learning/help_topics/rooms/
dw_fluffos_v1/lib/d/learning/help_topics/rooms/crowd/
dw_fluffos_v1/lib/d/learning/help_topics/rooms/situations/
dw_fluffos_v1/lib/d/learning/items/
dw_fluffos_v1/lib/d/learning/save/
dw_fluffos_v1/lib/d/liaison/
dw_fluffos_v1/lib/d/liaison/NEWBIE/doc/
dw_fluffos_v1/lib/d/liaison/NEWBIE/save/oldlog/
dw_fluffos_v1/lib/db/
dw_fluffos_v1/lib/doc/
dw_fluffos_v1/lib/doc/creator/
dw_fluffos_v1/lib/doc/creator/autodoc/include/reaction/
dw_fluffos_v1/lib/doc/creator/autodoc/include/ritual_system/
dw_fluffos_v1/lib/doc/creator/autodoc/include/talker/
dw_fluffos_v1/lib/doc/creator/autodoc/include/terrain_map/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/baggage/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/clock/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/clothing/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/cont_save/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/corpse/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/money/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/monster/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/scabbard/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/service_provider/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/state_changer/
dw_fluffos_v1/lib/doc/creator/autodoc/obj/wand/
dw_fluffos_v1/lib/doc/creator/autodoc/std/book_dir/
dw_fluffos_v1/lib/doc/creator/autodoc/std/key/
dw_fluffos_v1/lib/doc/creator/autodoc/std/learning/
dw_fluffos_v1/lib/doc/creator/autodoc/std/map/
dw_fluffos_v1/lib/doc/creator/autodoc/std/race/
dw_fluffos_v1/lib/doc/creator/autodoc/std/weapon_logic/
dw_fluffos_v1/lib/doc/creator/files/
dw_fluffos_v1/lib/doc/creator/policy/
dw_fluffos_v1/lib/doc/creator/room/
dw_fluffos_v1/lib/doc/effects/
dw_fluffos_v1/lib/doc/ideas/
dw_fluffos_v1/lib/doc/known_command/
dw_fluffos_v1/lib/doc/lpc/basic_manual/
dw_fluffos_v1/lib/doc/lpc/intermediate/
dw_fluffos_v1/lib/doc/new/add_command/
dw_fluffos_v1/lib/doc/new/handlers/
dw_fluffos_v1/lib/doc/new/living/
dw_fluffos_v1/lib/doc/new/living/race/
dw_fluffos_v1/lib/doc/new/living/spells/
dw_fluffos_v1/lib/doc/new/player/
dw_fluffos_v1/lib/doc/new/room/guild/
dw_fluffos_v1/lib/doc/new/room/outside/
dw_fluffos_v1/lib/doc/new/room/storeroom/
dw_fluffos_v1/lib/doc/object/
dw_fluffos_v1/lib/doc/playtesters/
dw_fluffos_v1/lib/doc/policy/
dw_fluffos_v1/lib/doc/weapons/
dw_fluffos_v1/lib/global/handlers/
dw_fluffos_v1/lib/global/virtual/setup_compiler/
dw_fluffos_v1/lib/include/
dw_fluffos_v1/lib/include/cmds/
dw_fluffos_v1/lib/include/effects/
dw_fluffos_v1/lib/include/npc/
dw_fluffos_v1/lib/include/shops/
dw_fluffos_v1/lib/net/daemon/chars/
dw_fluffos_v1/lib/net/inherit/
dw_fluffos_v1/lib/net/intermud3/
dw_fluffos_v1/lib/net/intermud3/services/
dw_fluffos_v1/lib/net/obj/
dw_fluffos_v1/lib/net/save/
dw_fluffos_v1/lib/net/smnmp/
dw_fluffos_v1/lib/net/snmp/
dw_fluffos_v1/lib/obj/amulets/
dw_fluffos_v1/lib/obj/b_day/
dw_fluffos_v1/lib/obj/examples/
dw_fluffos_v1/lib/obj/food/alcohol/
dw_fluffos_v1/lib/obj/food/chocolates/
dw_fluffos_v1/lib/obj/food/fruits/
dw_fluffos_v1/lib/obj/food/meat/
dw_fluffos_v1/lib/obj/food/nuts/
dw_fluffos_v1/lib/obj/food/seafood/
dw_fluffos_v1/lib/obj/food/vegetables/
dw_fluffos_v1/lib/obj/fungi/
dw_fluffos_v1/lib/obj/furnitures/artwork/
dw_fluffos_v1/lib/obj/furnitures/bathroom/
dw_fluffos_v1/lib/obj/furnitures/beds/
dw_fluffos_v1/lib/obj/furnitures/cabinets/
dw_fluffos_v1/lib/obj/furnitures/chairs/
dw_fluffos_v1/lib/obj/furnitures/chests/
dw_fluffos_v1/lib/obj/furnitures/clocks/
dw_fluffos_v1/lib/obj/furnitures/crockery/
dw_fluffos_v1/lib/obj/furnitures/cupboards/
dw_fluffos_v1/lib/obj/furnitures/cushions/
dw_fluffos_v1/lib/obj/furnitures/fake_plants/
dw_fluffos_v1/lib/obj/furnitures/lamps/
dw_fluffos_v1/lib/obj/furnitures/mirrors/
dw_fluffos_v1/lib/obj/furnitures/outdoor/
dw_fluffos_v1/lib/obj/furnitures/safes/
dw_fluffos_v1/lib/obj/furnitures/shelves/
dw_fluffos_v1/lib/obj/furnitures/sideboards/
dw_fluffos_v1/lib/obj/furnitures/sofas/
dw_fluffos_v1/lib/obj/furnitures/stoves/
dw_fluffos_v1/lib/obj/furnitures/tables/
dw_fluffos_v1/lib/obj/furnitures/wardrobes/
dw_fluffos_v1/lib/obj/handlers/
dw_fluffos_v1/lib/obj/handlers/autodoc/
dw_fluffos_v1/lib/obj/jewellery/anklets/
dw_fluffos_v1/lib/obj/jewellery/bracelets/
dw_fluffos_v1/lib/obj/jewellery/earrings/
dw_fluffos_v1/lib/obj/jewellery/misc/
dw_fluffos_v1/lib/obj/jewellery/necklaces/
dw_fluffos_v1/lib/obj/jewellery/rings/
dw_fluffos_v1/lib/obj/media/
dw_fluffos_v1/lib/obj/misc/buckets/
dw_fluffos_v1/lib/obj/misc/jars/
dw_fluffos_v1/lib/obj/misc/papers/
dw_fluffos_v1/lib/obj/misc/player_shop/
dw_fluffos_v1/lib/obj/misc/shops/
dw_fluffos_v1/lib/obj/misc/traps/
dw_fluffos_v1/lib/obj/monster/
dw_fluffos_v1/lib/obj/monster/godmother/
dw_fluffos_v1/lib/obj/monster/transport/
dw_fluffos_v1/lib/obj/plants/inherit/
dw_fluffos_v1/lib/obj/potions/
dw_fluffos_v1/lib/open/boards/
dw_fluffos_v1/lib/save/autodoc/
dw_fluffos_v1/lib/save/bank_accounts/
dw_fluffos_v1/lib/save/boards/frog/
dw_fluffos_v1/lib/save/books/bed_catalog/
dw_fluffos_v1/lib/save/creators/
dw_fluffos_v1/lib/save/mail/
dw_fluffos_v1/lib/save/mail/p/
dw_fluffos_v1/lib/save/newsrc/b/
dw_fluffos_v1/lib/save/newsrc/c/
dw_fluffos_v1/lib/save/newsrc/d/
dw_fluffos_v1/lib/save/newsrc/f/
dw_fluffos_v1/lib/save/newsrc/p/
dw_fluffos_v1/lib/save/newsrc/s/
dw_fluffos_v1/lib/save/newsrc/w/
dw_fluffos_v1/lib/save/players/c/
dw_fluffos_v1/lib/save/players/d/
dw_fluffos_v1/lib/save/players/g/
dw_fluffos_v1/lib/save/players/p/
dw_fluffos_v1/lib/save/players/s/
dw_fluffos_v1/lib/save/soul/data/
dw_fluffos_v1/lib/save/tasks/
dw_fluffos_v1/lib/save/vaults/
dw_fluffos_v1/lib/secure/cmds/lord/
dw_fluffos_v1/lib/secure/config/
dw_fluffos_v1/lib/secure/items/
dw_fluffos_v1/lib/secure/player/
dw_fluffos_v1/lib/soul/
dw_fluffos_v1/lib/soul/i/
dw_fluffos_v1/lib/soul/j/
dw_fluffos_v1/lib/soul/k/
dw_fluffos_v1/lib/soul/o/
dw_fluffos_v1/lib/soul/q/
dw_fluffos_v1/lib/soul/to_approve/
dw_fluffos_v1/lib/soul/u/
dw_fluffos_v1/lib/soul/v/
dw_fluffos_v1/lib/soul/wish_list/
dw_fluffos_v1/lib/soul/y/
dw_fluffos_v1/lib/soul/z/
dw_fluffos_v1/lib/std/creator/
dw_fluffos_v1/lib/std/effects/
dw_fluffos_v1/lib/std/effects/attached/
dw_fluffos_v1/lib/std/effects/external/
dw_fluffos_v1/lib/std/effects/fighting/
dw_fluffos_v1/lib/std/effects/other/
dw_fluffos_v1/lib/std/environ/
dw_fluffos_v1/lib/std/guilds/
dw_fluffos_v1/lib/std/hospital/
dw_fluffos_v1/lib/std/house/
dw_fluffos_v1/lib/std/house/onebedhouse/
dw_fluffos_v1/lib/std/house/onebedhut/
dw_fluffos_v1/lib/std/house/tworoomflat/
dw_fluffos_v1/lib/std/languages/
dw_fluffos_v1/lib/std/liquids/
dw_fluffos_v1/lib/std/nationality/
dw_fluffos_v1/lib/std/nationality/accents/
dw_fluffos_v1/lib/std/nationality/accents/national/
dw_fluffos_v1/lib/std/nationality/accents/regional/
dw_fluffos_v1/lib/std/npc/goals/
dw_fluffos_v1/lib/std/npc/goals/basic/
dw_fluffos_v1/lib/std/npc/goals/misc/
dw_fluffos_v1/lib/std/npc/inherit/
dw_fluffos_v1/lib/std/npc/plans/
dw_fluffos_v1/lib/std/npc/plans/basic/
dw_fluffos_v1/lib/std/outsides/
dw_fluffos_v1/lib/std/races/shadows/
dw_fluffos_v1/lib/std/room/basic/topography/
dw_fluffos_v1/lib/std/room/controller/
dw_fluffos_v1/lib/std/room/controller/topography/
dw_fluffos_v1/lib/std/room/furniture/games/
dw_fluffos_v1/lib/std/room/furniture/inherit/
dw_fluffos_v1/lib/std/room/inherit/carriage/
dw_fluffos_v1/lib/std/room/inherit/topography/
dw_fluffos_v1/lib/std/room/punishments/
dw_fluffos_v1/lib/std/room/topography/area/
dw_fluffos_v1/lib/std/room/topography/iroom/
dw_fluffos_v1/lib/std/room/topography/milestone/
dw_fluffos_v1/lib/std/shadows/
dw_fluffos_v1/lib/std/shadows/attached/
dw_fluffos_v1/lib/std/shadows/curses/
dw_fluffos_v1/lib/std/shadows/disease/
dw_fluffos_v1/lib/std/shadows/fighting/
dw_fluffos_v1/lib/std/shadows/room/
dw_fluffos_v1/lib/std/shops/controllers/
dw_fluffos_v1/lib/std/shops/objs/
dw_fluffos_v1/lib/std/shops/player_shop/
dw_fluffos_v1/lib/std/shops/player_shop/office_code/
dw_fluffos_v1/lib/std/socket/
dw_fluffos_v1/lib/www/
dw_fluffos_v1/lib/www/external/autodoc/
dw_fluffos_v1/lib/www/external/java/telnet/Documentation/
dw_fluffos_v1/lib/www/external/java/telnet/Documentation/images/
dw_fluffos_v1/lib/www/external/java/telnet/examples/
dw_fluffos_v1/lib/www/external/java/telnet/tools/
dw_fluffos_v1/lib/www/pics/
dw_fluffos_v1/lib/www/secure/creator/
dw_fluffos_v1/lib/www/secure/editors/
dw_fluffos_v1/lib/www/secure/survey_results/
/**
 * Library inherit. Allows players to take out books on loan. If
 * set to be player contributable, then books (most likely player 
 * written, but any kind) can be added/removed by designated 
 * librarian players.
 *
 * @author Aquilo
 */

// ToDo 
// Functions to be called when a damaged/lost book is reported for a player
// Prevent returning of books w/ pages ripped out.
// Gving status of a book, who has it on loan, ue back etc...

#include <book_handler.h>
#include <money.h>
#include <language.h>
#include <player.h>
#include <player_handler.h>
#include <am_time.h>

#define BASE_PATHS ({ "/std/book", "/std/book_dir", "/std/leaflet", \
                      "/obj/misc/paper", "/obj/misc/nroff_paper" })

#define TITLE_PROP          "title"
#define AUTHOR_PROP         "author"
#define FROM_LIBRARY_PROP   "from library"
#define BORROWED_BY_PROP    "borrowed by"
#define DUE_BACK_PROP       "due back"
#define REFERENCE_ONLY_PROP "reference only"

#define USER_ACCESS      1
#define LIBRARIAN_ACCESS 2

inherit "/std/room/basic_room";

nosave int       _player_contributable;
nosave string    _library_name;
nosave string    _save_file;
nosave function  _borrow_func;
nosave function  _test_allowed;

int       _loan_length;
int       _fine_per_day;
int       _lost_damaged_fine;
int       _max_loans;

mapping _catalog;    // ([ librarybook id : class _book ])
mapping _accounts;   // ([ playername     : class _account ])
mapping _fines;      // ([ playername     : int fine ])
mapping _access;     // ([ playername     : int access ])

nosave mapping _catalog_by_title;    // ([ "title" : ({ library id }) ])
nosave mapping _catalog_by_author;   // ([ "author" : ({ library id }) ])

/**
 * This is all the info on one of our stored books.
 * @element path the path to the item, if applicable
 * @element auto_load if no path, we store the object whole in this string
 * @element cap_title the capitalized title of the book
 * @element cap_author the capitalized author of the book
 * @element borrowable 1 if this book can be borrowed, 0 if not
 * @element copies Total number of copies owned by library
 * @element loaned An array of due back dates for loaned out copies
 * @element total_borrowed total no of times this book has been borrowed
 */
class _book { 
    string     path;     
    string     auto_load;
    string     cap_title;
    string     cap_author;
    mapping    loaned;
    int        borrowable;
    int        copies;
    int        total_borrowed;
}

/**
 * This holds info on a book a player has borrowed
 * @element id the librarybook id num
 * @element due_back the rl time due back, in secs from 1970
 */
class _loan {
    int       id;
    int       due_back;
}

/**
 * Holds info on a player.
 * @element total_fines_paid how much this blokie has paid in fines in total
 * @element damaged_books total number of books he has damaged
 * @element lost_books total number of books he has lost
 * @element loans info on what  books he currently has out
 */ 
class _account {
    int           total_fines_paid;
    int           lost_damaged;
    class _loan   *loans;
}

int do_add( object *obs, string title, string author );
int do_remove_book( int id );
int do_borrow( mixed arg );
int do_return( object *obs );
int do_list( string arg, string to, string from, string specific );
int do_status( string player );
int do_book_status( int id );
int do_set( string player, string type );
int do_mark( int id, int flag );
int do_set_fine( int i );
int do_set_loan_length( int days );
int do_set_max_loans( int i );
int do_lost_damaged_fine( int i );
int do_report( int id, string arg, string player );

mapping query_loans( string name );
mapping query_borrowed_by( int id );
int query_fine( string name );

void clear_loan( mixed player, int id, int calc_fines );
varargs int add_book_to_library( mixed thing, int copies, string cap_title, 
                                 string cap_author, int borrowable );
void set_access( string player, int access );
int query_access( mixed player );
int do_set( string player, string type );
void save_me();

protected string make_pretty_catalog( string arg, string from, string to, string specific );
protected int compare_widgets( object new_ob, int existing_id );

/** @ignore yes */
protected void build_catalogs(){
  int id;
  class _book book;

  foreach( id, book in _catalog ){
    if( _catalog_by_author[ lower_case(book->cap_author) ] )
      _catalog_by_author[ lower_case(book->cap_author) ] += ({ id });
    else
      _catalog_by_author[ lower_case(book->cap_author) ] = ({ id });

    if( _catalog_by_title[ lower_case(book->cap_title) ] )
      _catalog_by_title[ lower_case(book->cap_title) ] += ({ id });
    else
      _catalog_by_title[ lower_case(book->cap_title) ] = ({ id });
  }

}


/** @ignore yes */
void create(){
  object sign;
  ::create();

  _catalog_by_author = ([ ]);
  _catalog_by_title = ([ ]);

  /* Some default settings */
  _player_contributable = 1;
  _library_name = "The illustrious library of fluff";
  _save_file = base_name( this_object() ) + ".o";

  if( master()->file_exists(_save_file) ){
    unguarded( (: restore_object, _save_file :) );
  } else {
    _catalog = ([ ]);
    _accounts = ([ ]);
    _fines = ([ ]);
    _access = ([ ]);
    _loan_length = 3 * 24 * 60 * 60;
    _fine_per_day = 400;
    _lost_damaged_fine = 1050;
    _max_loans = 5;
  }

  build_catalogs();

  add_help_file("p_library_user");
  add_help_file("p_library_librarian");

  sign = add_sign( 
      "The plaque is a traditional brass plate, set on top a piece "
      "of cherry oak. It looks very formal.\n",
      "For some reason, reading this sign doesn't give you as much "
      "information as looking at it would.\n",
      "brass plaque", "plaque", "common" );
  sign->add_extra_look( this_object() );
  sign->set_read_mess( 0, 0, 0 );

/*
  if ( !do_setup ) { 
    this_object()->setup();
  }
*/

}


/*
void setup(){
  set_short("small library");
  set_long("A library. Lots of books everywhere.\n");
  set_light(70);
}
*/

/** @ignore yes */
int query_library(){  return 1;  }

/** @ignore yes */
void init(){
  if( _player_contributable ){
    add_command( "add", "<indirect:object:me'book'> with "
        "title <string'title'> by author <word'author'>",
        (: do_add( $1, $4[1], $4[2] ) :) );
    add_command( "remove", "<number'book id'>", 
        (: do_remove_book( $4[0] ) :));
  }

  add_command( "borrow", "<number'book id'>", (: do_borrow( $4[0] ) :) );
  add_command( "return", "<indirect:object:me-here>" );
  add_command( "pay", "[fine]"  );

  add_command( "view", "catalogue by {author|title}", (: do_list( $4[0], "a", "z", 0 ) :) );
  add_command( "view", "catalogue by {author|title} from "
      "<word'letter'> to <word'letter'>", (: do_list( $4[0], $4[1], $4[2], 0 ) :) );

  add_command( "view", "works by <string'author'>", 
      (: do_list( "author", 0, 0, $4[0] ) :) );
  add_command( "view", "works titled <string'title'>",
      (: do_list( "title", 0, 0, $4[0] ) :) );

  add_command( "status", "", (: do_status(0) :) );

  add_command( "status", "book <number'book id'>", 
               (: do_book_status( $4[0] ) :) );  

  add_command( "report", "<number'book id'> as {damaged|lost}",
               (: do_report( $4[0], $4[1], 0 ) :) );

  /* Librarian only commands */

  add_command( "set", "access for <word'player'> to {allowed|disallowed}", 
               (: do_set( $4[0], $4[1] ) :) );

  add_command( "set", "fine per day to <number'amount'>", 
               (: do_set_fine( $4[0] ) :) );

  add_command( "set", "loan length to <number'number of days'>", 
               (: do_set_loan_length( $4[0] ) :) );

  add_command( "set", "maximum number of loans to <number'number'>", 
               (: do_set_max_loans( $4[0] ) :) );

  add_command( "set", "lost or damaged fine to <number'amount'>", 
               (: do_lost_damaged_fine( $4[0] ) :) );

  add_command( "mark", "<number'book id'> as reference only",
    (: do_mark( $4[0], 1) :) );

  add_command( "mark", "<number'book id'> as not reference only", 
    (: do_mark( $4[0], 0) :) );

  add_command( "status", "<word'player'>", (: do_status($4[0]) :) );

  add_command( "report", "<number'book id'> as {damaged|lost} for <word'player'>",
               (: do_report( $4[0], $4[1], $4[2] ) :) );



}

/** @ignore yes */
protected string id_to_name( int id ){
  if( !_catalog[id] )
    return 0;
  return "'" +_catalog[id]->cap_title+ "' by " + _catalog[id]->cap_author;
}

/** @ignore yes */
protected int valid_media( object ob ){
  if( ob->query_book() || ob->query_paper() || ob->query_leaflet() )
    return 1;
}

/** @ignore yes */
int do_add( object *obs, string title, string author ){
  object t_p, t_o;
  int num, id, *ids;

  t_p = this_player();
  t_o = this_object();

  debug_printf( "Obs: %O\n", obs );

  if( query_access(this_player()) < LIBRARIAN_ACCESS ){
    add_failed_mess("Only librians can do this.\n");
    return 0;
  }

  if( strlen(author) > 14 ){
    add_failed_mess("The author's name is too long. It has to be less than 13 characters.\n");
    return 0;
  }

  if( strlen(title) > 26 ){
    add_failed_mess("The title is too long. It can only use less than 27 characters.\n");
    return 0;
  }

  if( sizeof(obs) > 1 ){
    add_failed_mess("You can only add one item at a time.\n");
    return 0;
  }

  if( !t_o->valid_media(obs[0]) ){
    add_failed_mess( "The library does not lend items like $I.\n", obs);
    return 0;
  }

  if( obs[0]->query_magic_scroll() || obs[0]->query_spell_book() ){
    add_failed_mess( "This library does not deal in magical works.\n" );
    return 0;
  }

  if( obs[0]->query_property(FROM_LIBRARY_PROP) ){
    add_failed_mess( "This item belongs to a library and cannot be added.\n" );
    return 0;
  }


  if( (num = obs[0]->query_book_num()) ){   // then its a player written book
    if( lower_case(author) != BOOK_HANDLER->query_book_owner(num) ){
      add_failed_mess( "The book has been authored by " + 
        capitalize(BOOK_HANDLER->query_book_owner(num)) + " and not " + 
        author + ".\n");
      return 0;
    }
  }



  if( (ids = _catalog_by_title[ lower_case(title) ]) ){
    foreach( id in ids ){
      if( lower_case(_catalog[id]->cap_author) == lower_case(author) ){
        // Then we already have a book of the same title by the same author
        // Make sure that they are infact the same...
        if( compare_widgets( obs[0], id ) ){
          add_book_to_library( id, 1 );
          add_succeeded_mess( "$N add$s another copy of "
              +id_to_name(id)+ " to the library.\n" );
          obs[0]->move( "/room/rubbish" );
          return 1;
        } else {
          add_failed_mess("$I doesn't seem to be the same as the copy held "
            "in the library. Catalogue it under a different title or author.\n", obs );
          return 0;
        }
      }
    }
  }
        
  if( member_array(base_name(obs[0]), BASE_PATHS) == -1 )
    add_book_to_library( base_name(obs[0]), 1, title, author, 1 );
  else
    add_book_to_library( obs[0], 1, title, author, 1 );    

  add_succeeded_mess( "$N $V an item to the library.\n" );
  obs[0]->move( "/room/rubbish" );
  return 1;
}

/** @ignore yes */
void remove_book( int id ){
  object ob;
  class _book book;
  int i;

  if( !_catalog[id] )
    return;

  book = _catalog[id];

  for( i = 0; i < (book->copies - sizeof(book->loaned)); i++ ){
    if( book->path )
      ob = clone_object( book->path );
    if( book->auto_load )
      ob = PLAYER_OB->load_auto_load_to_array( book->auto_load, this_player() )[0];

    ob->move( this_object() );
  }

  _catalog_by_title[lower_case(book->cap_title)] -= ({ id });
  _catalog_by_author[lower_case(book->cap_author)] -= ({ id });

  if( !sizeof( _catalog_by_title[lower_case(book->cap_title)] ) )
    map_delete( _catalog_by_title, lower_case(book->cap_title) );
  if( !sizeof( _catalog_by_author[lower_case(book->cap_author)] ) )
    map_delete( _catalog_by_author, lower_case(book->cap_author) );

  map_delete( _catalog, id );
  save_me();
}

/** @ignore yes */
int do_remove_book( int id ){
  int out;
  string player, bit;
  class _account account;
  class _loan loan;

  if( query_access(this_player()) < LIBRARIAN_ACCESS ){
    add_failed_mess("Only librians can do this.\n");
    return 0;
  }

  id = to_int(id);
  if( !_catalog[id] ){
    add_failed_mess("There is no item with that id.\n");
    return 0;
  }

  foreach( player, account in _accounts ){
    foreach( loan in account->loans ){
      if( loan->id == id )
        out++;
    }
  }

  if( out ){
    if( out > 1 ) 
      bit = "are " +query_num(out)+ " ";
    else 
      bit = "is one"+" ";
    add_failed_mess( "There " +bit+ id_to_name(id) + 
        ", out on loan already. You can only remove a book if all "
        "copies are in the library.\n" );
    return 0;
  } else {
    add_succeeded_mess("$N $V " +id_to_name(id) + " from the library.\n" );
    remove_book(id);
    tell_object( this_player(), "Any remaining copies will be placed on the floor.\n");
    return 1;
  } 

}

/** @ignore yes */
int do_borrow( mixed arg ){
  int id, ret, t;
  object ob;
  class _book book;
  class _loan loan;
  string name;
  
  name = this_player()->query_name();
  id = to_int(arg);

  if( query_access(this_player()) < USER_ACCESS ){
    add_failed_mess("You do not have access to this library.\n");
    return 0;
  }

  if( _borrow_func ){
    ret = evaluate( _borrow_func, this_player(), id );
    if( ret != 1 )
      return ret;
  }

  if( _fines[name] ){
    add_failed_mess("You can't borrow a book whilst having outstanding fines.\n" );
    return 0;
  }

  if( !_catalog[id] ){
    add_failed_mess("There is no book with an id of " +id+ ".\n" );
    return 0;
  } else {
    book = _catalog[id];
  }

  if( _accounts[name] && sizeof(_accounts[name]->loans) >= _max_loans ){
    add_failed_mess("You have already borrowed the maximum number of books.\n");
    return 0;
  }

  if( (book->copies - sizeof(book->loaned)) < 1 ){
    add_failed_mess("Unfortunately, all copies of " +
        id_to_name(id) + " are out on loan.\n");
    return 0;
  }

  if( _accounts[name] && sizeof( _accounts[name]->loans ) ){
    foreach( loan in _accounts[name]->loans ){
      if( loan->id == id ){
        add_failed_mess("You have already borrowed a copy of " + 
            id_to_name(id) + ".\n");
        return 0;
      }
    }
  }

  if( book->path )
    ob = clone_object( book->path );
  if( book->auto_load )
    ob = PLAYER_OB->load_auto_load_to_array( book->auto_load, this_player() )[0];

  if( !ob ){
    add_failed_mess("Uh oh, something buggered with book id: " + id + 
      ". Please tell a creator.\n" );
    return 0;
  } 

  t = time() + _loan_length;

  ob->add_property( TITLE_PROP, book->cap_title );
  ob->add_property( AUTHOR_PROP, book->cap_author );
  ob->add_property( FROM_LIBRARY_PROP, _library_name );
  ob->add_property( BORROWED_BY_PROP, name );
  ob->add_property( DUE_BACK_PROP, t );

  book->loaned[name] = t;
  book->total_borrowed++;
  _catalog[id] = book;

  loan = new( class _loan, id : id, due_back : t );

  if( _accounts[name] )
    _accounts[name]->loans += ({ loan });
  else {
    // Start them up an account
    _accounts[name] = new( class _account, total_fines_paid : 0,
                           lost_damaged : 0,
                           loans : ({ loan }) );
  }


  if( !book->borrowable ){
    tell_object( this_player(), id_to_name(id)+ " is a reference only book. "
      "It will be placed on "
      "a lecturn for you to read and cannot be removed from the library. Please return it "
      "in the normal way after use.\n");
    ob->move( this_object() );
    ob->reset_get();
    ob->add_property( "there", "sitting on a lecturn" );
    ob->add_property( REFERENCE_ONLY_PROP, 1 );
  } else {
    tell_object( this_player(), "You have until "+ am_time(t) + 
        " to return it.\n");
    ob->move( this_player() );
  }  
  save_me();
  add_succeeded_mess( "$N $V " +id_to_name(id)+ ".\n");
  return 1;
}

/** @ignore yes */
int do_return( object *obs ){
  string name, bit;
  int id, *owed, i, *ours, due;
  class _loan loan;
  class _book book;
  object ob, *not_ours, *wrong_player, *returned;

  name = this_player()->query_name();
  owed = ({ }); 
  ours = not_ours = wrong_player = returned = ({ });


  if( !sizeof(_accounts[name]->loans) ){
    add_failed_mess( "You don't have any books out on loan.\n" );
    return 0;
  }

  foreach( loan in _accounts[name]->loans ){
    owed += ({ loan->id });
  }

  foreach( ob in obs ){
    if( ob->query_property( FROM_LIBRARY_PROP ) == _library_name ){
      if( ob->query_property( BORROWED_BY_PROP ) == name ){     
        foreach( id in owed ){
          if( (book = _catalog[id]) ){
            if( ob->query_property( TITLE_PROP ) == book->cap_title && 
                ob->query_property( AUTHOR_PROP ) == book->cap_author ){

              /* Make sure it has same pages in here... */

              map_delete( _catalog[id]->loaned, name );
              ob->move("/room/rubbish");
              returned += ({ ob });
              ours += ({ id });
            }  
          } else {
            // Book no longer matches id....What to do?
            // Take book from player, clear loan then dest?
            ob->move("/room/rubbish");
            returned += ({ ob });
          }
        }
      } else {
        wrong_player += ({ ob });
      }
    } else {
      not_ours += ({ ob });
    }
  }

  if( sizeof(returned) ){
    foreach( i in ours )
      clear_loan( this_player(), i, 1 );
    if( _fines[name] )
      tell_object(this_player(), "Note: You have fines that are due.\n");
    add_succeeded_mess( "$N $V $I.\n", returned );
    return 1;
  }

  if( (i=sizeof(not_ours)) ){
    if( i > 1 ) bit = "do not"; else bit = "does not";
    add_failed_mess( "$I " +bit+ " belong to this library.\n", not_ours );
    return 0;
  }
 
  if( (i=sizeof(wrong_player)) ){
    if( i > 1 ) bit = "were not"; else bit = "was not";
    add_failed_mess( "$I " +bit+ " were not loaned to you. Only the borrower may "
        "return items.\n", wrong_player );
    return 0;
  }

}

/** @ignore yes */
int do_list( string arg, string from, string to, string specific ){

  if( (to && !stringp(to)) || (from && !stringp(from)) ){
    add_failed_mess( "The ranges must be letters.\n");
    return 0;
  }

  if( specific ){
    tell_object( this_player(), make_pretty_catalog( arg, 0, 0, lower_case(specific) ) );
    return 1;
  } else {
    tell_object( this_player(), make_pretty_catalog( arg, from, to, 0 ) );
    return 1;
  }

}

/** @ignore yes */
int do_pay(){
  int cash, fine;
  string place, str;

  place = query_property( "place" );
  if ( !place || ( place == "" ) )
    place = "default";

  cash = this_player()->query_value_in( place );
  if ( place != "default" )
    cash += this_player()->query_value_in( "default" );

  if( !cash ){
    add_failed_mess("You have money!.\n");
    return 0;
  }

  fine = _fines[this_player()->query_name()];
  if( fine <= 0 ){
    add_failed_mess("You have no outstanding fines to pay.\n");
    return 0;
  }

  if( cash >= fine )
    cash = fine;
  
  this_player()->pay_money( MONEY_HAND->create_money_array(cash, place), place );
  _fines[this_player()->query_name()] -= cash;
  _accounts[this_player()->query_name()]->total_fines_paid += cash;

  if( _fines[this_player()->query_name()] <= 0 ){
    str = MONEY_HAND->money_value_string( cash, place );
    add_succeeded_mess("$N pay$s all $p fines ("+str+").\n");
    map_delete( _fines, this_player()->query_name() );
    save_me();
    return 1;
  } else {
    str = MONEY_HAND->money_value_string( fine - cash, place );
    add_succeeded_mess("$N pay$s some of $p fine, but sill owe$s ("+str+").\n");
    save_me();
    return 1;
  }

}

/** @ignore yes */
int do_status( string player ){
  int fine;
  string place, str, bit;
  class _loan loan;

  if( !player )
    player = this_player()->query_name();

  player = lower_case(player);

  if( player != this_player()->query_name() &&
      query_access(this_player()) < LIBRARIAN_ACCESS ){
    add_failed_mess("You do not have access to see other accounts.\n");
    return 0;
  }

  str = "Account status for " + PLAYER_HANDLER->query_cap_name(player) +".\n";

  if( _accounts[player] && sizeof(_accounts[player]->loans)  ){
    str += "Loaned items:\n";
    str += sprintf( "%|4s%|=30s%|=20s%|9s\n", "Id", "Item", "Due back", "Overdue" );
    foreach( loan in _accounts[player]->loans ){
      if( loan->due_back - time() > 0 )
        bit = "";
      else 
        bit = "*Yes*";
      str += sprintf( "%|4d%|=30s%|=20s%|9s\n", loan->id,
          id_to_name(loan->id), am_time( loan->due_back ), bit );
    }
  } else {
    str += "No items currently on loan.\n";
  }
  

  place = query_property( "place" );
  if ( !place || ( place == "" ) )
    place = "default";

  fine = _fines[player];
  if( fine >= 0 ){
    str += sprintf( "%-15s%-6s\n", "Fines Due:", 
           MONEY_HAND->money_value_string( fine, place ) );
  } else {
    str += "No fines due.\n";
  }
  if( _accounts[player] ){
    str += "Total previously paid fines: " +
      MONEY_HAND->money_value_string( _accounts[player]->total_fines_paid, place );
    str += ".\nBooks lost or damaged: " + _accounts[player]->lost_damaged + ".\n";
  }


  tell_object( this_player(), str );
  add_succeeded_mess("");
  return 1;
}


/** @ignore yes */
int do_book_status( int id ){
  string str, name, date;
  class _book book;

  id = to_int(id);

  if( !_catalog[id] ){
    add_failed_mess("The id " +id+ ", does not point to an item.\n");
    return 0;
  }
  book = _catalog[id];
  str = "Status for id: " +id+ " - " + id_to_name(id) + ".\n";
 
  if( sizeof(book->loaned) ){
    str += sprintf( "%|=14s%|=30s\n", "On loan to", "Date Due Back " );
    foreach( name, date in book->loaned ){
      str += sprintf( "%|=12s%|=30s\n", 
             PLAYER_HANDLER->query_cap_name(name), am_time(date) );
    }
  } else {
    str += "Currently, no copies of the item are on loan.";
  }

  str += "The library holds " + (string)book->copies; 
  if( book->copies == 1 ) str += " copy "; else str += " copies ";
  str += "in total, " + (string)( book->copies - sizeof(book->loaned) ) +
         " of which are available.\n";

  str += "It has been borrowed a total of " + book->total_borrowed;
  if( book->total_borrowed == 1 ) str += " time.\n"; else str += " times.\n";
 
  if( !book->borrowable )
    str += "It is a reference only item.\n";
  tell_object( this_player(), str);
  add_succeeded_mess("$N look$s at the status of a library item.\n");
  return 1;
}


/** @ignore yes */
int do_set( string player, string type ){

  player = lower_case(player);

  if( query_access(this_object()) <= query_access(player) ){
    add_failed_mess( "You do not have permission to do this.\n");
    return -1;
  }

  if( type == "allowed" ){
    if( query_access(player) < USER_ACCESS ){
      set_access( player, USER_ACCESS );
      add_succeeded_mess("$N allow$s " + PLAYER_HANDLER->query_cap_name(player) + 
        " to use the library.\n");
      save_me();
      return 1;
    }
  }

  if( type == "disallowed" ){
    if( query_access(player) == USER_ACCESS ){
      set_access( player, 0 );
      add_succeeded_mess("$N disallow$s " + PLAYER_HANDLER->query_cap_name(player) + 
        " from using the library.\n");
      save_me();
      return 1;
    }
  }
}


int do_mark( int id, int flag ){

  if( query_access(this_player()) < LIBRARIAN_ACCESS ){
    add_failed_mess("You do not have permission to do this.\n");
    return 0;
  }

  if( !_catalog[id] ){
    add_failed_mess("There is no item with that id.\n");
    return 0;
  }

  if( flag ){
    add_succeeded_mess("$N $V " + id_to_name(id) + " to reference only.\n");
    _catalog[id]->borrowable = 0;
    return 1;
  } else {
    add_succeeded_mess("$N $V " + id_to_name(id) + " to not reference only.\n");
    _catalog[id]->borrowable = 1;
    return 1;
  }
  save_me();
}

int do_set_fine( int i ){
  string place;

  if( query_access(this_player()) < LIBRARIAN_ACCESS ){
    add_failed_mess("You do not have permission to do this.\n");
    return 0;
  }

  place = query_property( "place" );
  if( !place || ( place == "" ) )
    place = "default";
   
  i = to_int(i);

  if( i < 0 ){
    add_failed_mess("Don't be silly, thats a negative value!\n");
    return 0;
  }

  if( i > 4000 ){
    add_failed_mess("The maximum fine you can set per day is 4000 units (" +
        MONEY_HAND->money_value_string( 4000, place ) + ").\n" );
    return 0;
  }

  add_succeeded_mess("$N set$s the fine per day to " + i +
    " ("+ MONEY_HAND->money_value_string( i, place ) + ").\n" );
  _fine_per_day = i;
  save_me();
  return 1;
}


int do_set_loan_length( int days ){
  string str;

  if( query_access(this_player()) < LIBRARIAN_ACCESS ){
    add_failed_mess("You do not have permission to do this.\n");
    return 0;
  }
  days = to_int(days);

  if( days > 10 ){
    add_failed_mess("The maximum loan length is 10 days.\n");
    return 0;
  }

  if( days < 1 ){
    add_failed_mess("The minimum loan length is 1 day.\n");
    return 0;
  }
  if( days == 1 )  str = " day"; else str = " days";

  add_succeeded_mess("$N set$s the loan length to " 
     + query_num(days) + str + ".\n" );
  _loan_length = days * AM_SECONDS_PER_DAY;
  save_me();
  return 1;
}

/** @ignore yes */
int do_set_max_loans( int i ){

  if( query_access(this_player()) < LIBRARIAN_ACCESS ){
    add_failed_mess("You do not have permission to do this.\n");
    return 0;
  }
  i = to_int(i);

  if( i < 1 ){
    add_failed_mess("The minimum amount of loans is one.\n");
    return 0;
  }

  if( i > 10 ){
    add_failed_mess("The maximum amount of loans a player can have is ten.\n");
    return 0;
  }

  add_succeeded_mess("$N set$s the maximum number of loans to " 
     + query_num(i) + " items.\n" );
  _max_loans = i;
  save_me();
  return 1;

}

int do_lost_damaged_fine( int i ){
  string place;
 
  if( query_access(this_player()) < LIBRARIAN_ACCESS ){
    add_failed_mess("You do not have permission to do this.\n");
    return 0;
  }
  i = to_int(i);
  place = query_property( "place" );
  if( !place || ( place == "" ) )
    place = "default";
   
  if( i < 0 ){
    add_failed_mess("Don't be silly, thats a negative value!\n");
    return 0;
  }

  if( i > 20000 ){
    add_failed_mess("The maximum fine you can set per day is 20000 units (" +
        MONEY_HAND->money_value_string( 20000, place ) + ").\n" );
    return 0;
  }

  add_succeeded_mess("$N set$s the lost or damaged fine to " + i +
    " ("+ MONEY_HAND->money_value_string( i, place ) + ").\n" );
  _lost_damaged_fine = i;
  save_me();
  return 1;


}

int do_report( int id, string arg, string player ){
  string str;
  class _loan loan, match;

  if( player && query_access(this_player()) < LIBRARIAN_ACCESS ){
    add_failed_mess("You can only report your damaged or lost books.\n");
    return 0;
  }
 
  if( player ){
    str = PLAYER_HANDLER->query_cap_name(player)+ " does";
    player = lower_case(player);
  } else {
    str = "You do";
    player = this_player()->query_name();
  }

  if( !_accounts[player] ){
    add_failed_mess(str + " not have any items on loan.\n");
    return 0;
  }

  if( !_catalog[id] ){
    add_failed_mess("The id " + id + " does not exist.\n");
    return 0;
  }


  foreach( loan in _accounts[player]->loans ){
    if( loan->id == id ){
      match = loan;
    }
  }
  if( !match ){
    add_failed_mess(str + " not have " + id_to_name(id) + " out on loan.\n");
    return 0;
  }

  _accounts[player]->loans -= ({ match });
  map_delete( _catalog[id]->loaned, player );
  _catalog[id]->copies--;
  
  if( player == this_player()->query_name() ) 
    str = "";
  else 
    str = " for " + PLAYER_HANDLER->query_cap_name(player);

  if( _fines[player] )
    _fines[player] += _lost_damaged_fine;
  else
    _fines[player] = _lost_damaged_fine;

  if( arg == "lost" ){
    add_succeeded_mess("You report the loss of " + id_to_name(id) + str + ".\n");
    _accounts[player]->lost_damaged++;
    save_me();
    return 1;
  } 
  if( arg == "damaged" ){
    add_succeeded_mess("You report the damaging of " +id_to_name(id) + str + ".\n");
    _accounts[player]->lost_damaged++;
    save_me();
    return 1;
  }

}


/** @ignore yes */
protected string make_pretty_catalog( string arg, string from, string to, string specific ){
  string widget, c_widget, str, *list;
  int id, *ids;

  if( !from )
    from = "a";
  else
    from = lower_case(from);

  if( !to )
    to = "z";
  else 
    to = lower_case(to);

  if( from > to ){ 
    /* Then they have said between z and a, rather than a to z,
       silly buggers */
    widget = to;
    to = from;
    from = widget;
  }

  if( specific ){
    if( _catalog_by_title[specific] || _catalog_by_author[specific] )
      list = ({ specific });
    else
      list = ({ });
  }

  if( arg == "title" ){

    str = sprintf( "%|=4s%|=26s%|=12s%|=9s%|=8s\n", 
            "Id", "Title", "Author", "Copies", "On Loan" );

    if( !list )
      list = sort_array( keys(_catalog_by_title), 0 );

    foreach( widget in list ){
      ids = _catalog_by_title[ widget ];
      if( widget && sizeof(ids) && (widget[0..0] >= from) && (widget[0..0] <= to) ){
        foreach( id in ids ){
          c_widget = _catalog[ id ]->cap_title;
          str += sprintf( "%|=4d%|=26s%|=12s%|=9d%|=8d\n", 
                   id, c_widget, _catalog[id]->cap_author, 
                   _catalog[id]->copies, sizeof(_catalog[id]->loaned) );
        }
      }
    }
    return str;
  } 


  if( arg == "author" ){

    str = sprintf( "%|=4s%|=12s%|=26s%|=9s%|=8s\n", 
            "Id", "Author", "Title", "Copies", "On Loan" );

    if( !list )
      list = sort_array( keys(_catalog_by_author), 0 );

    foreach( widget in list ){
      ids = _catalog_by_author[ widget ];
      if( widget && sizeof(ids) && (widget[0..0] >= from) && (widget[0..0] <= to) ){
        foreach( id in ids ){
          c_widget = _catalog[ id ]->cap_title;
          str += sprintf( "%|=4d%|=12s%|=26s%|=9d%|=8d\n", 
                   id, _catalog[id]->cap_author, c_widget,
                   _catalog[id]->copies, sizeof(_catalog[id]->loaned) );
        }
      }
    }
    return str;
  }


}

/**
 * This clears the loan of library book id the specified player.
 * If its overdue it sorts out adding a fine to his name.
 * @param player Either the player name or object
 * @param id the library book id
 * @param calc_fines if this is 1, then we add fines onto the player
 * if appropriate, if 0 then we ignore fines and just clear the loan
 */
void clear_loan( mixed player, int id, int calc_fines ){
  int fine;
  class _loan loan;

  if( objectp(player) && userp(player) )
    player = player->query_name();

  if( !stringp(player) )  return;
  if( !_accounts[player] || !sizeof(_accounts[player]->loans) )  return;

  foreach( loan in _accounts[player]->loans ){
    if( loan->id == id ){
      if( calc_fines && (loan->due_back - time() < 0) ){   // Naughty!
        fine = (((time() - loan->due_back) / AM_SECONDS_PER_DAY) + 1) * _fine_per_day;
        if( _fines[player] )
          _fines[player] += fine;
        else
          _fines[player] = fine;
      }
      _accounts[player]->loans -= ({ loan });
    }
  }  
  save_me();
}


/** @ignore yes */
protected int find_blank_id(){
  int *ids;
  int unused_id, i;

  ids = sort_array( keys(_catalog), -1 );
  if( !sizeof(ids) ){
    unused_id = 1;
  } else {
    if( ids[0] > sizeof(ids) ){
      for( i = 0; i < sizeof(ids); i++ ){
        if( undefinedp(_catalog[i]) ){
          unused_id = i;
          break;
        }
      }
    } else {
      unused_id = sizeof(ids) + 1;
    }
  }
  if( unused_id == 0 )
    unused_id = 1;

  return unused_id;
}

/** @ignore yes */
protected void add_book_to_catalogs( class _book new_book, int id ){

  _catalog[id] = new_book;
  save_me();

  if( _catalog_by_author[lower_case(new_book->cap_author)] )
    _catalog_by_author[lower_case(new_book->cap_author)] += ({ id });
  else
    _catalog_by_author[lower_case(new_book->cap_author)] = ({ id });

  if( _catalog_by_title[lower_case(new_book->cap_title)] )
    _catalog_by_title[lower_case(new_book->cap_title)] += ({ id });
  else
    _catalog_by_title[lower_case(new_book->cap_title)] = ({ id });

}


/**
 * This function adds the specified object to the library 
 * @param thing Either a filename, or object. If its an object we record its autoload info
 * @param copies how many copies to add
 * @param cap_title The capitalized title its cataloged under
 * @param cap_author The capitalized author of the work
 * @param borrowable 1 if it can be borrowed, 0 if its reference only
 */
varargs int add_book_to_library( mixed thing, int copies, string cap_title, 
                                 string cap_author, int borrowable ){
  class _book book;
  int id, *ids;

  if( intp(thing) ){
    if( _catalog[thing] ){
      _catalog[thing]->copies += copies;
      return 1;
    } else {
      return 0;
    }
  }

  if( !cap_title || !cap_author || cap_title == "" || cap_author == "" )
    return 0;

  if( (ids = _catalog_by_title[ lower_case(cap_title) ]) ){
    foreach( id in ids ){
      if( lower_case(_catalog[id]->cap_author) == lower_case(cap_author) )
          /*= Then we already have a book of the same title by the same author =*/
          return add_book_to_library( id, 1 );
    }
  }

  if( stringp(thing) ){    
    book = new( class _book );
    book->path = thing;
    book->cap_author = cap_author;
    book->cap_title = cap_title;
    book->borrowable = 1;
    book->copies = copies;
    book->loaned = ([ ]);
    book->total_borrowed = 0;

    id = find_blank_id();
    add_book_to_catalogs( book, id );
    return 1;
  }
  
  if( objectp(thing) ){
    book = new( class _book );
    book->auto_load = AUTO_LOAD_OB->create_auto_load( ({ thing }) );
    book->cap_author = cap_author;
    book->cap_title = cap_title;
    book->borrowable = 1;
    book->copies = copies;
    book->loaned = ([ ]);
    book->total_borrowed = 0;

    id = find_blank_id();
    add_book_to_catalogs( book, id );
    return 1;
  }

}


/**
 * Returns 1, if we think that the new object is the same as the existing one we
 * have in stock.
 * @ignore yes
 */
protected int compare_widgets( object new_ob, int existing_id ){
  class _book book;
  object ob;
  mixed info;
  int perc;

  book = _catalog[existing_id];
  if( !book )  return 0;

  if( book->path )
    ob = clone_object( book->path );
  if( book->auto_load )
    ob = PLAYER_OB->load_auto_load_to_array( book->auto_load, this_player() )[0];

  if( !ob ){
    // Shouldn't happen...
    return 0;
  }

  if( base_name(ob) != base_name(new_ob) )
    return 0; // Then they can't be the same surely...

  if( inherits("/std/book.c", new_ob) ){
    perc = BOOK_HANDLER->compare_pages(
             BOOK_HANDLER->query_pages( new_ob ),
             BOOK_HANDLER->query_pages( ob )
           );
  }

  if( inherits("/std/leaflet.c", new_ob) ){
    perc = BOOK_HANDLER->compare_pages(
             map( new_ob->query_pages(), (: $1[0][0] :) ),
             map( ob->query_pages(), (: $1[0][0] :) )
           );
  }

  if( inherits("/obj/misc/paper", new_ob) ||
      inherits("/obj/misc/nroff_paper", new_ob) ){
    perc = BOOK_HANDLER->compare_pages(
      map( new_ob->query_read_mess(), (: $1[0] :) ),
      map( ob->query_read_mess(), (: $1[0] :) )
    );
  }
  ob->move( "/room/rubbish" );

  if( perc > 97 )
    return 1;
  else
    return 0;
}



/**
 * This is used to sort out the look msg of the sign.
 * @ignore yes
 */
string extra_look( object ob ){
  string *libs, str, place;

  str = "Welcome to " + _library_name + ".\n";

  libs = filter( keys(_access), (: _access[$1] == LIBRARIAN_ACCESS :) );
  if( !sizeof(libs) )
    str += "Currently there are no caretakers of the books.\n";
  if( sizeof(libs) == 1 )
    str += PLAYER_HANDLER->query_cap_name(libs[0]) + " is the sole caretaker "
          " of the books.\n";
  if( sizeof(libs) > 1 ){
    libs = map( libs, (: PLAYER_HANDLER->query_cap_name($1) :) );
    str += query_multiple_short(libs) + " are caretakers of the books.\n";
  }

  place = query_property("place");
  if( !place || place == "" )
    place = "default";
  
  str += "Books may be borrowed for a maximum of " +
         (_loan_length / AM_SECONDS_PER_DAY) +
         " Disc days, before fines are levied.\n";
  str += "Fines currently stand at " +
         MONEY_HAND->money_value_string( _fine_per_day, place ) + 
         " per day the book is overdue.\n";
  str += "A charge of " + MONEY_HAND->money_value_string( _lost_damaged_fine, place ) +
         " will be incurred if you lose or damage a book.\n";
  str += "The maximum number of items you can borrow at one time currently stands at "+
         query_num(_max_loans) + ".\n";

  return str;
}

/** @ignore yes */
protected void save_it(){
  unguarded( (: save_object, _save_file :) );
}

/** @ignore yes */
void save_me(){
  if( find_call_out("save_it") == -1 )
    call_out( "save_it", 2 );
}

/*
void dest_me(){
  object ob, *ref;

  ref = filter( all_inventory(), (: $1->query_property(REFERENCE_ONLY_PROP) :) ) );
  foreach( ob in ref ){

  }
}
*/

/**
 * Returns a mapping of book ids to due back dates for the given player.
 * @param name the player
 * @return a mapping in the form ([ book id : due back ])
 */
mapping query_loans( string name ){
  class _loan loan;
  mapping m = ([ ]);

  if( !_accounts[name] )
    return ([ ]);

  foreach( loan in _accounts[name]->loans ){
    m[ loan->id ] = loan->due_back;
  }  

  return m;

}

/**
 * Returns a mapping of players who borrowed the given book id, mapped to their 
 * due back dates.
 * @param id valid book id
 */
mapping query_borrowed_by( int id ){
  if( _catalog[id] )
    return _catalog[id]->loaned;
  else
    return ([ ]);
}

/**
 * @param name the string name of the person to check
 * @return How much of a fine this person has
 */
int query_fine( string name ){  return _fines[name];  }

/**
 * Debug only
 * @ignore yes
 */
mapping query_catalog(){
  return _catalog;
}

/** @ignore yes */
mapping query_catalog_by_title(){
  return _catalog_by_title;
}

/** @ignore yes */
mapping query_catalog_by_author(){
  return _catalog_by_author;
}

/** @ignore yes */
mapping query_all_accounts(){
  return _accounts;
}


/***** ******/

void set_library_name( string str ){  _library_name = str;  }

string query_library_name(){  return _library_name;  }

void set_player_contributable( int i ){  _player_contributable = i;  }

int query_player_contributable(){  return _player_contributable;  }

void set_max_loans( int i ){  _max_loans = i;  }

int query_max_loans(){  return _max_loans;  }

void set_loan_length( int i ){  _loan_length = i;  }

int query_loan_length(){  return _loan_length;  }

void set_fine_per_day( int i ){  _fine_per_day = i;  }

int query_fine_per_day(){  return _fine_per_day;  }

void set_lost_damaged_fine( int i ){ _lost_damaged_fine = i;  }

int query_lost_damaged_fine(){ return _lost_damaged_fine;  }

void set_save_file( string s ){  _save_file = s;  }

string query_save_file(){  return _save_file;  }

void set_borrow_func( function f ){  _borrow_func = f;  }

function query_borrow_func(){  return _borrow_func;  }


void set_access( string player, int access ){ 
  if( !access ){
    map_delete( _access, player );
  } else {
    _access[player] = access;
  }
  save_me();
}

int query_access( mixed player ){
/*
  if( userp(player) )
    player = player->query_name();
 
  if( stringp(player) )
    player = lower_case(player);
  else 
    return 0;

  return _access[player];
*/
  return LIBRARIAN_ACCESS;
}