/******************************************************************************
 * This file contains the personnel related functions
 *****************************************************************************/
/**
 * @ignore yes 
 * Adds a new employee.
 * Also prevent the employee from receiving a bonus until they have
 * worked a full month by setting EMP_NOBONUS to 1.
 * @param player the employee to add
 */
private void add_employee(string player)
{
    if (!_employees[player])
    {
        _employees += ([player:EMP_MAP]);
        _employees[player][EMP_NOBONUS] = 1;
        set_emp_time(player);
        _got_bonus += ({player});
        save_me();
        save_emps();
    }
}
/* add_employee() */
/**
 * @ignore yes 
 * Demote a manager or supervisor.
 * @param demoter the person doing the demoting
 * @param demotee the person being demoted
 */
private void demote(string demoter, string demotee)
{
    int points = _employees[demotee][EMP_POINTS] & CLOCKED_IN;
    if (_employees[demotee][EMP_POINTS] & MANAGER)
        points += (SUPER_POINTS * 32) + EMPLOYEE + SUPERVISOR;      
    else points += EMPLOYEE;
    _employees[demotee][EMP_POINTS] = points;
    save_emps();
    PLAYER_SHOP->auto_mail(demotee, _proprietor, "Demotion", "",
      "This is to advise you that you have today been demoted.\n"
      "This demotion will now stay on your employment record.\n");
    employee_log(demotee, "Demoted by "+ demoter);
    shop_log(PERSONNEL, demoter, "demoted "+ cap_name(demotee), PAID);
}
/* demote() */
/**
 * @ignore yes 
 * Managers' office.
 * Commend employees.  Adds 5% of their promotion target.
 */
int do_commend(string emp)
{
    string commender;
    if (!_employees[emp])
    {
        tell_object(this_player(), cap_name(emp)+
          " is not an active employee!\n");
        return 1;
    }
    if (_employees[emp][EMP_POINTS] & MANAGER)
    {
        tell_object(this_player(), "You can't commend a manager.\n");
        return 1;
    }
    if (_employees[emp][EMP_POINTS] & NPC)
    {
        tell_object(this_player(), "Don't be silly!  "
          "You can't commend $C$"+ emp +".\n");
        return 1;
    }
    commender = this_player()->query_cap_name();
    AUTO_MAILER->auto_mail(emp, lower_case(commender), "Commendation",
      "", "This is to advise you that you have today received a "
      "commendation for outstanding service.\nThis will now stay on "
      "your employment record.\n");
    employee_log(emp, "Received a commendation from "+ commender);
    shop_log(PERSONNEL, commender, "commended "+ cap_name(emp), PAID);
    _employees[emp][EMP_POINTS] += (_employees[emp][EMP_POINTS] & SUPERVISOR)?
    to_int(MANAGER_POINTS * 0.05 * 32) : to_int(SUPER_POINTS * 0.05 * 32);
    save_emps();
    tell_object(this_player(), "You commend "+ cap_name(emp)+ ".\n");
    return 1;
}
/* do_commend() */
/**
 * @ignore yes 
 * Managers' office.
 * Demote supervisors or managers.
 */
int do_demote(string emp)
{
    object tp = this_player();
    emp = lower_case(emp);
    if (!_employees[emp])
    {
        tell_object(tp, cap_name(emp)+ " is not an active employee!\n");
        return 1;
    }
    if (_employees[emp][EMP_POINTS] & MANAGER && !tp->query_creator())
    {
        tell_object(tp, "You don't have the authority to "
          "demote $C$"+ emp +".\n");
        return 1;
    }
    if (!( _employees[emp][EMP_POINTS] & SUPERVISOR))
    {
        tell_object(tp, "Don't be silly!  You can't demote $C$"+
          emp +".\n");
        return 1;
    }
    demote(tp->query_cap_name(), emp);
    tell_object(tp, "You demote "+ cap_name(emp)+ ".\n");
    return 1;
}
/* do_demote() */
/**
 * @ignore yes 
 * Managers' office.
 * Fire an employee.
 */
int do_fire(mixed *args)
{
    args[0] = lower_case(args[0]);
    if (!_employees[args[0]])
    {
        tell_object(this_player(), cap_name(args[0])+
          " doesn't work at the shop!\n");
        return 1;
    }
    if ((member_array(args[0], _retired) != -1) ||
      query_manager(args[0]) && !this_player()->query_creator())
    {
        tell_object(this_player(), "You don't have the authority "
          "to fire $C$"+ args[0] +".\n");
        return 1;
    }
    if (_employees[args[0]][EMP_POINTS] & NPC)
    {
        tell_object(this_player(), "Don't be silly!  You can't fire $C$"+
          args[0] +".\n");
        return 1;
    }
    fire_them(this_player()->query_name(), args[0], args[1]);
    tell_object(this_player(), "You fire "+ cap_name(args[0])+
      " for "+ args[1]+ ".\n" );
    return 1;
}
/* do_fire() */
/**
 * @ignore yes 
 * Managers' office.
 * Place an employee on leave.
 */
int do_leave(mixed *args)
{
    object tp = this_player();
    args[0] = lower_case(args[0]);
    if (!_employees[args[0]])
    {
        tell_object(tp, cap_name(args[0])+ " is not an employee!\n");
        return 1;
    }
    if (args[1] > MAX_LEAVE)
    {
        tell_object(tp, "You cannot place an employee on leave "
          "for more than "+ MAX_LEAVE+ " days at a time.\n");
        return 1;
    }
    _times[args[0]] = time() + (args[1] * 86400);
    remove_call_out(_call_times);
    _call_times = call_out((: save_times() :), PERS_DELAY);
    add_succeeded_mess(cap_name(args[0])+
      " is on leave until "+ ctime(time() + (args[1] * 86400))+ ".\n");
    shop_log(PERSONNEL, tp->query_name(), "placed "+
      cap_name(args[0])+ " on leave for " + args[1]+ " days", PAID);
    employee_log(args[0], "Placed on leave by "+ tp->query_cap_name()+
      " for "+ args[1]+ " days.");
    tell_object(tp, "You place "+ cap_name(args[0])+
      " on leave for " + args[1]+ " days.\n");
    return 1;
}
/* do_leave() */
/**
 * @ignore yes 
 * This employee has requested to be passed over (or not) for promotion.
 */
private int do_promote(string on)
{
    object tp = this_player();
    add_succeeded_mess("");
    if (tp->query_creator())
    {
        tell_object(tp, "Creators don't get promoted.\n");
        return 1;
    }
    switch (on)
    {
    case "off" :
        _employees[tp->query_name()][EMP_NOPROMOTE] = TRUE;
        tell_object(tp, "You have now requested to be "
          "passed over for promotion.\n");
        break;
    case "on" :
        if (tp->query_property("no score"))
        {
            tell_object(tp, "Sorry, you cannot be promoted.\n");
            return 1;
        }
        _employees[tp->query_name()][EMP_NOPROMOTE] = FALSE;
        tell_object(tp, "You have now requested to be "
          "considered for promotion.\n");
        break;
    }
    save_emps();
    return 1;
}
/* do_promote() */
/**
 * @ignore yes 
 * Employee wishes to terminate their employment with the shop.
 */
private int do_resign()
{
    string word = this_player()->query_name();
    add_succeeded_mess("$N $V.\n");
    remove_employee(word);
    shop_log(PERSONNEL, word, "resigned", UNPAID);
    employee_log(word, "Resigned");
    return 1;
}
/* do_resign() */
/**
 * @ignore yes 
 * Managers' office.
 * Retire from management.
 */
int do_retire()
{
    string manager = this_player()->query_name();
    if (!(_employees[manager][EMP_POINTS] & MANAGER)) return 0;
    remove_employee(manager);
    _retired += ({manager});
    shop_log(PERSONNEL, manager, "retired from management", UNPAID);
    employee_log(manager, "Retired from management");
    save_me();
    add_succeeded_mess("$N retire$s.\n");
    return 1;
}
/* do_retire() */
/**
 * @ignore yes 
 * Managers' office.
 * Suspend employee's bonus for x months.
 */
int do_suspend(mixed *args)
{
    string suspender;
    object tp = this_player();
    args[0] = lower_case(args[0]);
    if (!_employees[args[0]])
    {
        tell_object(tp, cap_name(args[0])+ " is not an active employee!\n");
        return 1;
    }
    if ((_employees[args[0]][EMP_POINTS] & MANAGER) && 
      (!tp->query_creator()))
    {
        tell_object(tp, "You don't have the authority to "
          "suspend $C$"+ args[0] +"'s bonus.\n");
        return 1;
    }
    if (_employees[args[0]][EMP_POINTS] & NPC)
    {
        tell_object(tp, "Don't be silly!  "
          "You can't suspend $C$"+ args[0] +"'s bonus.\n");
        return 1;
    }
    suspender = tp->query_cap_name();
    _employees[args[0]][EMP_NOBONUS] = args[1];
    save_emps();
    AUTO_MAILER->auto_mail(args[0], _proprietor, "Suspended bonus", "",
      sprintf( "This is to advise you that you have had your bonus "
        "entitlement suspended for %d month%s.\nThis suspension will "
        "now stay on your employment record.\n", args[1],
        (args[1] == 1)?"":"s"));
    employee_log(args[0], sprintf("Bonus suspended for %d month%s by %s",
        args[1], (args[1] == 1)?"":"s", suspender));
    shop_log(PERSONNEL, suspender,
      sprintf("suspended %s's bonus for %d month%s", args[0],
        args[1], (args[1] == 1)?"":"s"), PAID);
    tell_object(tp, "You suspend "+ cap_name(args[0])+
      "'s bonus for "+ args[1]+ " months.\n");
    return 1;
}
/* do_suspend() */
/**
 * @ignore yes 
 * Managers' office.
 * Warn employees.  Removes 5% of their promotion target.
 */
int do_warn(mixed *args)
{
    string warner;
    object tp = this_player();
    int points;
    args[0] = lower_case(args[0]);
    if (!_employees[args[0]])
    {
        tell_object(tp, cap_name(args[0])+ " is not an active employee!\n");
        return 1;
    }
    if (_employees[args[0]][EMP_POINTS] & MANAGER && !tp->query_creator() )
    {
        tell_object( tp, "You don't have the authority to "
          "warn $C$"+ args[0] +".\n" );
        return 1;
    }
    if (_employees[args[0]][EMP_POINTS] & NPC)
    {
        tell_object(tp, "Don't be silly!  You can't warn $C$"+ args[0] +".\n");
        return 1;
    }
    warner = tp->query_cap_name();
    AUTO_MAILER->auto_mail(args[0], _proprietor, "Official warning", "",
      "This is to advise you that you have today received a formal "
      "warning for " + args[1] + ".\nThis warning will now stay on "
      "your employment record.\n");
    employee_log(args[0], "Received a warning from "+ warner+ 
      " for "+ args[1]);
    shop_log(PERSONNEL, warner, "warned "+
      cap_name( args[0] ) + " for "+ args[1], PAID);
    points = _employees[args[0]][EMP_POINTS] & CLOCKED_IN;
    if (_employees[args[0]][EMP_POINTS] & SUPERVISOR)
        _employees[args[0]][EMP_POINTS] -= to_int(MANAGER_POINTS * 0.05 * 32);
    else
    {
        _employees[args[0]][EMP_POINTS] -= to_int(SUPER_POINTS * 0.05 * 32) +
        EMPLOYEE;
        if (_employees[args[0]][EMP_POINTS] < 1)
            _employees[args[0]][EMP_POINTS] = EMPLOYEE + points;
    }
    save_emps();
    tell_object(tp, "You warn "+ cap_name(args[0])+ " for "+
      args[1]+ ".\n");
    return 1;
}
/* do_warn() */
/**
 * @ignore yes 
 * Used when employees are fired by managers, or automatically.
 * @param word the person doing the firing
 * @param them the person being fired
 * @param reason the reason for being fired
 */
private void fire_them(string word, string them, string reason)
{
    if (!_employees[them]) return;
    BANK_HANDLER->adjust_account(them, BANKS[_employees[them][EMP_BANK]][1],
      _employees[them][EMP_PAY]);
    shop_log(ACCOUNTS, _proprietor, "paid "+ 
      MONEY_HAND->money_value_string(_employees[them][EMP_PAY], _place)+
      " to "+ cap_name(them), UNPAID);
    shop_log(PERSONNEL, word, "fired "+ cap_name(them) +
      " for "+ reason, PAID);
    PLAYER_SHOP->auto_mail(them, word, _shop_name, "", 
      "Unfortunately, I have to inform you that you have today "
      "been fired for " + reason + ".  You have been paid the sum of "+
      MONEY_HAND->money_value_string( _employees[them][EMP_PAY], _place )+
      " for the work you have carried out to this date.\nIf you feel you "
      "have been unfairly dismissed, please refer to a manager.\n");
    employee_log(them, "Fired by "+ cap_name(word)+
      " for "+ reason);
    remove_employee(them);
}
/* fire_them() */
/**
 * @ignore yes 
 * Used when applicant has sufficient supporting votes to be accepted.
 * @param word the person to hire
 */
private void hire(string word)
{
    int gender;
    remove_applicant(word);
    /* Do not hire if not a user, already an employee, or banned */
    if (!test_player(word) || _employees[word] || query_baddie(word)) return;
    add_employee(word);
    employee_log(word, "Hired");
    shop_log(PERSONNEL, _proprietor, "hired "+ cap_name(word), UNPAID);
    PLAYER_SHOP->auto_mail(word, _proprietor, _shop_name, "", 
      "Congratulations!  You've been hired to work at "+ _shop_name+
      ".  You'll find that you can now move through the counter "
      "to the back areas of the shop.  The first things you should "
      "do are \"claim\" a new badge and staff handbook.\n");
    gender = PLAYER_HANDLER->test_gender(word);
    add_board_message("New employee", sprintf("%s has today been employed to "
        "work for the shop.  Please make %s feel welcome, and assist %s while "
        "%s gets started in %s new position.\n", cap_name(word),
        ({"it", "him", "her"})[gender] ,({"it", "him", "her"})[gender],
        ({"it", "he", "she"})[gender], ({"its", "his", "her"})[gender]));
    /* Update the other accepted applicants */
    remove_call_out(_call_mail_hirees);
    _call_mail_hirees = call_out((: mail_hirees() :), 5);
}
/* hire() */
/**
 * @ignore yes 
 * Used by the employee list to show managers & creators the last
 * time an employee was active.  Employees will be highlighted yellow
 * if they are currently on an inactivity warning, and red if they are
 * within 7 days of being fired/demoted
 * @param emp the employee to query
 */
private string query_worked(string emp)
{
    string blurb;
    /* Clocked in */
    if (_employees[emp][EMP_POINTS] & CLOCKED_IN)
        return " is currently clocked in";
    /* NPC */
    if (_employees[emp][EMP_POINTS] & NPC)
        return " has gone home for tea";
    /* On leave */
    if (_times[emp] > time())
        return " - %^CYAN%^on leave until "+ ctime(_times[emp])+ "%^RESET%^";
    blurb = " - last action ";
    if (_employees[emp][EMP_POINTS] & MANAGER)
    {
        if ((time() - _times[emp]) > ((60*60*24*MGR_DEMOTE)-7))
            blurb += "%^RED%^"; 
        else if ((time() - _times[emp]) > (60*60*24*MGR_WARN))
            blurb += "%^RED%^"; 
    }
    else if (_employees[emp][EMP_POINTS] & SUPERVISOR)
    {
        if (( time() - _times[emp]) > ((60*60*24*SPR_DEMOTE)-7))
            blurb += "%^RED%^"; 
        else if ((time() - _times[emp]) > (60*60*24*SPR_WARN))
            blurb += "%^YELLOW%^"; 
    }
    else if (( time() - _times[emp]) > ((60*60*24*EMP_FIRE)-7))
        blurb += "%^RED%^";
    else if ((time() - _times[emp]) > (60*60*24*EMP_WARN))
        blurb += "%^YELLOW%^";
    return blurb + ctime(_times[emp])+ "%^RESET%^";
}
/* query_worked() */
/**
* @ignore yes 
* Sets the last action time of an employee.
* This time is the last time an employee did something worth
* recording and is used to determine if they are inactive.
* @param employee The employee.
*/
private void set_emp_time(string employee)
{
    if (!_employees[employee]) return;
    if (_employees[employee][EMP_INACTIVE])
    {
        _employees[employee][EMP_INACTIVE] = 0;   // Reset inactivity flag
        save_emps();
    }
    if (!sizeof(_times)) _times = ([employee:0]);
    else if (!_times[employee]) _times += ([employee:0]);
    _times[employee] = time();
    remove_call_out(_call_times);
    _call_times = call_out((: save_times() :), PERS_DELAY);
}
/* set_emp_time() */
/**
 * @ignore yes 
 * View an employee's history or an applicant's application.
 * This method displays a formatted display of the employee's history
 * with a particular shop, and is viewable by managers of that shop.
 * If passed the name of an applicant, it will view the relevant application.
 * @param person The employee or applicant.
 */
void view_record(string person, string pattern)
{   
    if (pattern == VIEW_EMP)
    {
        string text = sprintf("Employment history of %s:\n\n", cap_name(person));
        load_history();
        if (!sizeof(_history) || !_history[person])
        {
            tell_object(this_player(), "There is no history for that person.\n");
            return;
        }
        for(int i = 0; i < sizeof(_history[person][0]); i++)
            text += sprintf("%s: %s\n", ctime(_history[person][0][i]),
              _history[person][1][i]); 
        tell_object(this_player(), sprintf("$P$%s's history$P$%s",
            cap_name(person), text));
        clear_history();
    }
    else
    {
        if (!query_applicant(person))
        {
            tell_object(this_player(), "That person has no application form "
              "on file.\n");
            return;
        }
        load_applicants();
        tell_object(this_player(),
          sprintf("$P$%s's history$P$Application of %s:\n\n%s", cap_name(person),
            cap_name(person), _applicants[person][APP_MESSAGE]));
        clear_applicants();
    }
}
/* view_record() */