/**
 * A hander to allow the testing of values on players when they aren't logged
 * in.  These methods used to exist in the login object but now they're here.
 * <p>
 * @author Ceres
 * @change Partly rewritten to make it faster, and added support for finger
 * info - Sandoz, 2002.
 */
#include <finger.h>
inherit "/global/player/family";
#define MAX_CACHE_SIZE 100
#define CACHE_TIMEOUT  900
#define INACTIVE_LIMIT 604800
class player_info {
    int cached;
    int touched;
    int gender;
    int active;
    int level;
    int age;
    int last;
    int start_time;
    string password;
    string real_name;
    string email;
    string icq;
    string homepage;
    string birthday;
    string location;
    string home_dir;
    string last_on_from;
    string deity;
    string family_name;
    string signature;
    string project;
    string plan;
    string reference;
    mapping map_prop;
}
private int gender, time_on, last_log_on, activity_counter, start_time;
private string password, deity, last_on_from, home_dir;
private mapping aliases, map_prop, new_skills, player_info;
private nosave mapping player_cache;
private nosave int requests, cache_hits;
/** @ignore yes */
void create() {
    player_cache = ([ ]);
} /* create() */
/** @ignore yes */
void clean_cache() {
    string name;
    foreach( name in keys(player_cache) )
        if( player_cache[name]->cached < time() - CACHE_TIMEOUT )
            map_delete( player_cache, name );
} /* clean_cache() */
/** @ignore yes */
private varargs string make_sig( mixed stuff, int flag ) {
    string ret;
    int i, lines;
    if( !sizeof(stuff) ) {
        if( flag )
            return 0;
        return "";
    }
    ret = replace( (string)FINGER_H->make_string( stuff ), ";", "\n");
    if( flag )
        return ret;
    ret += "\n";
    for( i = lines = 0; i < sizeof( ret ) && lines < 4; i++ ) {
        if( ret[ i ] == '\n')
            lines++;
    }
    return "\n--\n"+ret[0..i-1]+"%^RESET%^";
} /* make_sig() */
/** @ignore yes */
private int validate_name( string name ) {
    if( !name || !stringp(name) || name == "logon" || name[0] == '.' ||
        ( sscanf( name, "%*s %*s") == 2 ) || strlen(name) < 2 ||
        sizeof( explode( name, "..") ) > 1 )
        return 0;
    return 1;
} /* validate_name() */
/**
 * This method will load in the player file.  It will return 0 if the player
 * file either does not exist or the input string is incorrect.
 * @param name the name to try and read in
 * @return 1 if the player file exists, 0 if not
 */
int load_player( string name ) {
    class player_info tmp;
    string pfile, dfile;
    mixed file;
    int diff;
    if( !validate_name(name) )
        return 0;
    requests++;
    // Check if we have a cached copy of this file.
    // If so, only use the cached copy if the cached time * 10 is less
    // than the last touched time ie. files which were modified recently
    // should be cached for less time.
    if( player_cache[name] ) {
        if( ( time() - player_cache[name]->cached ) * 10 <
            ( time() - player_cache[name]->touched ) ) {
            cache_hits++;
            return 1;
        }
    }
    pfile = LOGIN_OBJ->query_player_file_name(name);
    dfile = LOGIN_OBJ->query_delete_player_file_name(name);
    // Check if the file exists and get info about it.
    file = unguarded( (: get_dir, pfile+".o", -1 :) );
      if( !sizeof(file) || file[0][1] < 0 ) {
        file = unguarded( (: get_dir, dfile+".o", -1 :) );
        if( !sizeof(file) || file[0][1] < 0 )
            return 0;
    }
    // If we've got a cached copy of this file see if the original's touched
    // time is the same as that for our copy, if so use the cached copy
    // and update the 'cached' time.
    if( player_cache[name] && player_cache[name]->touched == file[0][2] ) {
        player_cache[name]->cached = time();
        cache_hits++;
        return 1;
    }
    // Restore the file and setup the data.
    if( !unguarded( (: restore_object, pfile :) ) &&
        !unguarded( (: restore_object, dfile :) ) )
        return 0;
    if( !mapp( map_prop ) )
        map_prop = ([ ]);
    if( !mapp(aliases) )
        aliases = ([ ]);
    if( !mapp(player_info) )
        player_info = ([ ]);
    tmp = new( class player_info,
               cached       : time(),
               touched      : file[0][2],
               gender       : gender,
               active       : 0,
               level        : 0,
               age          : time_on,
               last         : last_log_on,
               start_time   : start_time,
               password     : password,
               real_name    : player_info["real_name"],
               email        : player_info["email"],
               icq          : player_info["icq"],
               homepage     : player_info["homepage"],
               birthday     : player_info["birthday"],
               location     : player_info["location"],
               home_dir     : home_dir,
               last_on_from : last_on_from,
               deity        : deity,
               family_name  : query_family_name(),
               signature    : make_sig( aliases[".signature"] ),
               project      : make_sig( aliases[".project"], 1 ),
               plan         : make_sig( aliases[".plan"], 1 ),
               reference    : make_sig( aliases[".reference"], 1 ),
               map_prop     : map_prop );
    // Are they active.
    diff = ( time() - last_log_on ) / (3600 * 24 * 7);
    diff *= 10;
    tmp->active = ( activity_counter - diff ) > -50;
    // Calculate their level
    tmp->level = (int)STD_GUILD_OBJ->query_level(TO);
    player_cache[name] = tmp;
    if( sizeof(player_cache) > MAX_CACHE_SIZE &&
        find_call_out("clean_cache") == -1 )
        call_out("clean_cache", 60 );
    return 1;
} /* load_player() */
/**
 * This method returns all the info needed for a player's finger.
 * If the player is online, the updated values will be gotten
 * straight from the player object.
 * @param name the player to get the data for
 * @return the player info for the player
 */
class finger_info query_finger_info( string name ) {
    object ob;
    class player_info tmp;
    if( !ob = find_player(name) ) {
        if( !load_player(name) )
            return 0;
        tmp = player_cache[name];
    }
    if( ob ) {
        return new( class finger_info,
            gender       : ob->query_gender(),
            age          : ob->query_time_on(),
            last         : ob->query_last_log_on(),
            start_time   : ob->query_start_time(),
            real_name    : ob->query_real_name(),
            email        : ob->query_email(),
            icq          : ob->query_icq(),
            homepage     : ob->query_homepage(),
            birthday     : ob->query_birthday(),
            location     : ob->query_where(),
            home_dir     : ob->query_home_dir(),
            last_on_from : ob->query_last_on_from(),
            project      : make_sig( ob->query_player_alias(".project"), 1 ),
            plan         : make_sig( ob->query_player_alias(".plan"), 1 ) );
    }
    return new( class finger_info,
           gender       : tmp->gender,
           age          : tmp->age,
           last         : tmp->last,
           start_time   : tmp->start_time,
           real_name    : tmp->real_name,
           email        : tmp->email,
           icq          : tmp->icq,
           homepage     : tmp->homepage,
           birthday     : tmp->birthday,
           location     : tmp->location,
           home_dir     : tmp->home_dir,
           last_on_from : tmp->last_on_from,
           project      : tmp->project,
           plan         : tmp->plan );
} /* query_finger_info() */
/**
 * This method figures out if the user exists even if they are not on.
 * @param str the name of the user
 * @return 1 if they exist, 0 if they do not
 */
int test_user( string str ) {
    if( find_player(str) )
        return 1;
    if( !validate_name(str) )
        return 0;
    return file_size( LOGIN_OBJ->query_player_file_name(str)+".o") > 0 ||
           file_size( LOGIN_OBJ->query_delete_player_file_name(str)+".o") > 0;
} /* test_user() */
/**
 * This method determines the gender of the player even if they are
 * not currently on
 * @param str the name of the user
 * @return the player's gender
 * @see /std/living/gender.c
 */
int test_gender( string str ) {
    object ob;
    if( ob = find_player(str) )
        return ob->query_gender();
    if( !load_player(str) )
        return 0;
    return ((class player_info)player_cache[str])->gender;
} /* test_gender() */
/**
 * This method determines if a player is still active.
 * If you need to perform this on a lot of players please use the noload
 * parameter. When noload is set to 1 test_active will not attempt to load
 * the player file if it isn't currently loaded and will instead just do a
 * simple calculation of the players last login time. This is less accurate
 * but avoids lagging the mud.
 * @param player the name of the user
 * @param noload optional parameter to prevent test_active() loading the
 * player file.
 * @return active or inactive (1 or 0)
 */
varargs int test_active( string player, int noload ) {
    mixed file;
    if( find_player(player) )
        return 1;
    if( noload ) {
        if( player_cache[player] )
            return ((class player_info)player_cache[player])->active;
        file = unguarded( (: stat,
               LOGIN_OBJ->query_player_file_name(player)+".o" :) );
        return ( sizeof(file) && file[1] > time() - INACTIVE_LIMIT );
    }
    if( !load_player(player) )
        return 0;
    return ((class player_info)player_cache[player])->active;
} /* test_active() */
/**
 * This method determines the level of the player even if they are
 * not currently on
 * @param str the name of the user
 * @return the players level
 */
int test_level( string str ) {
    object ob;
    if( ob = find_player(str) )
        return ob->query_level();
    if( !load_player(str) )
        return 0;
    return ((class player_info)player_cache[str])->level;
} /* test_level() */
/** @ignore yes */
mapping query_skills()  {
    if( mapp( new_skills ) )
        return new_skills;
    return ([ ]);
} /* query_skills() */
/**
 * This method checks to see if the name is banished of not.
 * @param name the check for banishment.
 * @return 1 if it banished, 0 if not
 */
int test_banished( string name ) {
    return !(string)BASTARDS_H->query_player_ob( name, 0 );
} /* test_banished() */
/**
 * This method determines the real name of the player even if they are
 * not currently on
 * @param str the name of the user
 * @return the players real name
 */
string test_real_name( string str ) {
    object ob;
    if( ob = find_player(str) )
        return ob->query_real_name();
    if( !load_player(str) )
        return 0;
    return ((class player_info)player_cache[str])->real_name;
} /* test_real_name() */
/**
 * This method determines the ICQ number information of the player
 * even if they are not currently on.
 * @param str the name of the user
 * @return the players ICQ number as a string
 */
string test_icq( string str ) {
    object ob;
    if( ob = find_player(str) )
        return ob->query_icq();
    if( !load_player(str) )
        return 0;
    return ((class player_info)player_cache[str])->icq;
} /* test_icq() */
/**
 * This method determines the email of the player even if they are
 * not currently on.
 * @param str the name of the user
 * @return the players email
 */
string test_email( string str ) {
    object ob;
    if( ob = find_player(str) )
        return ob->query_email();
    if( !load_player(str) )
        return 0;
    return ((class player_info)player_cache[str])->email;
} /* test_email() */
/**
 * This method determines the homepage of the player even if they are
 * not currently on.
 * @param str the name of the user
 * @return the players homepage
 */
string test_homepage( string str ) {
    object ob;
    if( ob = find_player(str) )
        return ob->query_homepage();
    if( !load_player(str) )
        return 0;
    return ((class player_info)player_cache[str])->homepage;
} /* test_homepage() */
/**
 * This method returns the current value of the property on the
 * player, even if they are not currently on.
 * @param name the player name
 * @param str the property to query
 * @return the value of the property
 */
mixed test_property( string name, string str ) {
    object ob;
    if( ob = find_player(name) )
        return ob->query_property(str);
    if( !load_player(name) )
        return 0;
    return ((class player_info)player_cache[name])->map_prop[str];
} /* test_property() */
/**
 * This method updates the cached properties, it's called by
 * /secure/login.
 */
void special_add_property( string player, string prop, mixed value ) {
    if( player_cache[player] )
        player_cache[player]->map_prop[prop] = value;
} /* special_add_property() */
/**
 * This method determines the deity of the player even if they are
 * not currently on.
 * @param str the name of the user
 * @return the players deity
 */
string test_deity( string str ) {
    object ob;
    if( ob = find_player(str) )
        return ob->query_deity();
    if( !load_player(str) )
        return 0;
    return ((class player_info)player_cache[str])->deity;
} /* test_deity() */
/**
 * This method determines the age of the player even if they are
 * not currently on.
 * @param str the name of the user
 * @return the players age
 */
int test_age( string str ) {
    object ob;
    if( ob = find_player(str) )
        return ob->query_time_on();
    if( !load_player(str) )
        return 0;
    return ((class player_info)player_cache[str])->age;
} /* test_age() */
/**
 * This method determines the last log on of the player
 * even if they are not currently on.
 * @param str the name of the user
 * @return the player's last log on
 */
int test_last( string str ) {
    object ob;
    if( ob = find_player(str) )
        return ob->query_last_log_on();
    if( !load_player(str) )
        return 0;
    return ((class player_info)player_cache[str])->last;
} /* test_last() */
/**
 * This method determines the time the player started at.
 * @param str the name of the user
 * @return the player's start time
 */
int test_start_time( string str ) {
    object ob;
    if( ob = find_player(str) )
        return ob->query_start_time();
    if( !load_player(str) )
        return 0;
    return ((class player_info)player_cache[str])->start_time;
} /* test_start_time() */
/**
 * This method returns the player's family name.
 * @param str the name of the user
 * @return the player's family name
 */
string test_family( string str ) {
    object ob;
    if( ob = find_player(str) )
        return ob->query_family_name();
    if( !load_player(str) )
        return 0;
    return ((class player_info)player_cache[str])->family_name;
} /* test_family() */
/**
 * This method returns the player's birthday date.
 * @param str the name of the user
 * @return the player's birthday
 */
string test_birthday( string str ) {
    object ob;
    if( ob = find_player(str) )
        return ob->query_birthday();
    if( !load_player(str) )
        return 0;
    return ((class player_info)player_cache[str])->birthday;
} /* test_birthday() */
/**
 * This method returns the player's location.
 * @param str the name of the user
 * @return the player's location
 */
string test_location( string str ) {
    object ob;
    if( ob = find_player(str) )
        return ob->query_where();
    if( !load_player(str) )
        return 0;
    return ((class player_info)player_cache[str])->location;
} /* test_location() */
/** @ignore yes */
int test_password( string name, string pass ) {
    if( !load_player(name) )
        return 0;
    // Have to do this since its the only function that requires restoring
    // from a file if the player is active!
    if( find_player(name) && player_cache[name]->password == "") {
        unguarded( (: restore_object,
            LOGIN_OBJ->query_player_file_name(name) :) );
        player_cache[name]->password = password;
    }
    return crypt( pass, player_cache[name]->password ) ==
                        player_cache[name]->password;
} /* test_password() */
/** @ignore yes */
string get_password( string name ) {
    if( file_name(PO) != SECURE_HANDLER_DIR "/ftp_auth")
        return "x";
    if( !load_player(name) )
        return "x";
    if( find_player(name) && player_cache[name]->password == "") {
        unguarded( (: restore_object,
            LOGIN_OBJ->query_player_file_name(name) :) );
        player_cache[name]->password = password;
    }
    return ((class player_info)player_cache[name])->password;
} /* get_password() */
/**
 * This method returns the signature to use on posts for the player
 * even when they are off line.
 * @param name the name of the player
 * @return the signature, "" if none
 */
string query_signature( string name ) {
    object ob;
    if( ob = find_player(name) )
        return make_sig( ob->query_player_alias(".signature") );
    if( !load_player(name) )
        return "";
    return ((class player_info)player_cache[name])->signature;
} /* query_signature() */
/**
 * This method returns the project to use in the player's finger,
 * even when they are off line.
 * @param name the name of the player
 * @return the project or 0, if none
 */
string query_project( string name ) {
    object ob;
    if( ob = find_player(name) )
        return make_sig( ob->query_player_alias(".project"), 1 );
    if( !load_player(name) )
        return 0;
    return ((class player_info)player_cache[name])->project;
} /* query_project() */
/**
 * This method returns the plan to use in the player's finger,
 * even when they are off line.
 * @param name the name of the player
 * @return the plan or 0, if none
 */
string query_plan( string name ) {
    object ob;
    if( ob = find_player(name) )
        return make_sig( ob->query_player_alias(".plan"), 1 );
    if( !load_player(name) )
        return 0;
    return ((class player_info)player_cache[name])->plan;
} /* query_plan() */
/**
 * This method returns the reference string to use in the player's refer,
 * even when they are off line.
 * @param name the name of the player
 * @return the reference string or 0, if none
 */
string query_reference( string name ) {
    object ob;
    if( ob = find_player(name) )
        return make_sig( ob->query_player_alias(".reference"), 1 );
    if( !load_player(name) )
        return 0;
    return ((class player_info)player_cache[name])->reference;
} /* query_reference() */
/**
 * This method should be called whenever a player logs on, or
 * is refreshed, so that we wouldn't have outdated data in the cache.
 * @param name the name of the player to delete from the cache
 */
void delete_from_cache( string name ) {
    map_delete( player_cache, name );
} /* delete_from_cache() */
/** @ignore yes */
mixed stats() {
    int percentage;
    if( requests )
        percentage = ( cache_hits * 100 ) / requests;
    return ({
        ({ "cache size", sizeof(player_cache) }),
        ({ "requests", requests }),
        ({ "cache hits", cache_hits }),
        ({ "cache misses", requests - cache_hits }),
        ({ "percentage hits", percentage }),
        });
} /* stats() */
/** @ignore yes */
nomask int query_prevent_shadow( object ob ) { return 1; }