/**
 * @file
 * @brief Tracks monsters the player has killed.
**/

#pragma once

#include <cstdio>
#include <map>
#include <string>
#include <vector>

#include "enum.h"
#include "kill-dump-options-type.h"

class monster;
struct monster_info;
class reader;
class writer;

// Not intended for external use!
struct kill_monster_desc
{
    kill_monster_desc(const monster*);
    kill_monster_desc(const monster_info&);
    kill_monster_desc() { }

    void save(writer&) const;
    void load(reader&);

    enum name_modifier
    {
        M_NORMAL, M_ZOMBIE, M_DRAUGR, M_SIMULACRUM, M_SPECTRE,
        M_SHAPESHIFTER, // A shapeshifter pretending to be 'monnum'
    };

    monster_type monnum;        // Number of the beast
    name_modifier modifier;     // Nature of the beast

    struct less_than
    {
        bool operator () (const kill_monster_desc &m1,
                          const kill_monster_desc &m2) const
        {
            return m1.monnum < m2.monnum
                   || (m1.monnum == m2.monnum && m1.modifier < m2.modifier);
        }
    };
};

#define PLACE_LIMIT 5   // How many unique kill places we're prepared to track
class kill_def
{
public:
    kill_def(const monster* mon);
    kill_def() : kills(0), exp(0)
    {
        // This object just says to the world that it's uninitialised
    }

    void save(writer&) const;
    void load(reader&);

    void add_kill(const monster* mon, level_id place);
    void add_place(level_id place, bool force = false);

    void merge(const kill_def &k, bool unique_monster);

    string info(const kill_monster_desc &md) const;
    string base_name(const kill_monster_desc &md) const;

    unsigned short kills;    // How many kills does the player have?
    int            exp;      // Experience gained for slaying the beast.
                             // Only set *once*, even for shapeshifters.

    vector<level_id> places; // Places where we've killed the beast.
private:
    string append_places(const kill_monster_desc &md, const string &name) const;
};

// Ghosts and random Pandemonium demons.
class kill_ghost
{
public:
    kill_ghost(const monster* mon);
    kill_ghost() { }

    void save(writer&) const;
    void load(reader&);

    string info() const;

    string ghost_name;
    int exp;
    level_id place;
};

// This is the structure that Lua sees.
struct kill_exp
{
    int nkills;
    int exp;
    string base_name;
    string desc;

    monster_type monnum; // Number of the beast
    int modifier;        // Nature of the beast

    vector<level_id> places;

    kill_exp(const kill_def &k, const kill_monster_desc &md)
        : nkills(k.kills), exp(k.exp), base_name(k.base_name(md)),
          desc(k.info(md)),
          monnum(md.monnum), modifier(md.modifier), places(k.places)
    {
    }

    kill_exp(const kill_ghost &kg)
        : nkills(1), exp(kg.exp), base_name(), desc(kg.info()),
          monnum(MONS_PROGRAM_BUG), modifier(0)
    {
        places.push_back(kg.place);
    }

    // operator< is implemented for a descending sort.
    bool operator < (const kill_exp &b) const
    {
        return exp == b.exp? (base_name < b.base_name) : (exp > b.exp);
    }
};

class Kills
{
public:
    void record_kill(const monster* mon);
    void merge(const Kills &k);

    bool empty() const;
    void save(writer&) const;
    void load(reader&);

    int get_kills(vector<kill_exp> &v) const;
    int num_kills(const monster* mon) const;
    int num_kills(const monster_info& mon) const;
private:
    int num_kills(kill_monster_desc desc) const;

    typedef map<kill_monster_desc, kill_def,
                kill_monster_desc::less_than> kill_map;
    typedef vector<kill_ghost> ghost_vec;

    kill_map    kills;
    ghost_vec   ghosts;

    void record_ghost_kill(const monster* mon);
};

class KillMaster
{
public:
    void record_kill(const monster* mon, int killer, bool ispet);

    bool empty() const;
    void save(writer&) const;
    void load(reader&);

    // Number of kills, by category.
    int num_kills(const monster* mon, kill_category cat) const;
    int num_kills(const monster_info& mon, kill_category cat) const;
    // Number of kills, any category.
    int num_kills(const monster* mon) const;
    int num_kills(const monster_info& mon) const;

    int total_kills() const;

    string kill_info() const;
private:
    const char *category_name(kill_category kc) const;

    Kills categorized_kills[KC_NCATEGORIES];
private:
    void add_kill_info(string &, vector<kill_exp> &,
                       int count, const char *c, bool separator) const;
};
