/**
 * @file
 * @brief Rendering of map_cell to glyph and colour.
 *
 * This only needs the information within one object of type map_cell.
**/

#include "AppHdr.h"

#include "showsymb.h"

#include "colour.h"
#include "env.h"
#include "itemname.h"
#include "map_knowledge.h"
#include "mon-util.h"
#include "monster.h"
#include "options.h"
#include "religion.h"
#include "show.h"
#include "stash.h"
#include "state.h"
#include "stringutil.h"
#include "terrain.h"
#include "travel.h"
#include "viewchar.h"

static unsigned short _cell_feat_show_colour(const map_cell& cell,
                                             const coord_def& loc,
                                             bool coloured)
{
    dungeon_feature_type feat = cell.feat();
    unsigned short colour = BLACK;
    const feature_def &fdef = get_feature_def(feat);

    // These do not obey vault recolouring.
    const bool no_vault_recolour = feat > DNGN_OPEN_DOOR
                                 // unknown traps won't get here
                                 || feat == DNGN_MALIGN_GATEWAY;

    // These aren't shown mossy/bloody/slimy in console.
    const bool norecolour = feat_is_door(feat) || no_vault_recolour;

    if (is_stair_exclusion(loc))
        colour = Options.tc_excluded;
    else if (!coloured)
    {
        if (cell.flags & MAP_EMPHASIZE)
            colour = fdef.seen_em_colour;
        else
            colour = fdef.seen_colour;

        if (colour)
        {
            // Show trails even out of LOS.
            if (Options.show_travel_trail && travel_trail_index(loc) >= 0)
                colour |= COLFLAG_REVERSE;
            return colour;
        }
    }
    else if (feat >= DNGN_MINMOVE && cell.flags & MAP_WITHHELD)
    {
        // Colour grids that cannot be reached due to beholders
        // dark grey.
        colour = DARKGREY;
    }
    else if (feat >= DNGN_MINMOVE
             && (cell.flags & (MAP_SANCTUARY_1 | MAP_SANCTUARY_2)))
    {
        if (cell.flags & MAP_SANCTUARY_1)
            colour = YELLOW;
        else if (cell.flags & MAP_SANCTUARY_2)
        {
            if (!one_chance_in(4))
                colour = WHITE;     // 3/4
            else if (!one_chance_in(3))
                colour = LIGHTCYAN; // 1/6
            else
                colour = LIGHTGREY; // 1/12
        }
    }
    else if (cell.flags & MAP_BLOODY && !norecolour)
        colour = RED;
    else if (cell.flags & MAP_MOLDY && !norecolour)
        colour = (cell.flags & MAP_GLOWING_MOLDY) ? LIGHTRED : LIGHTGREEN;
    else if (cell.flags & MAP_CORRODING && !norecolour
             && !feat_is_wall(feat) && !feat_is_lava(feat)
             && !feat_is_water(feat))
    {
        colour = LIGHTGREEN;
    }
    else if (cell.feat_colour() && !no_vault_recolour)
        colour = cell.feat_colour();
    else
    {
        colour = fdef.colour;

        if (fdef.em_colour && fdef.em_colour != fdef.colour
            && cell.flags & MAP_EMPHASIZE)
        {
            colour = fdef.em_colour;
        }
    }

    if (feat == DNGN_SHALLOW_WATER && player_in_branch(BRANCH_SHOALS))
        colour = ETC_WAVES;

    if (feat == DNGN_FLOOR)
    {
        if (cell.flags & MAP_LIQUEFIED)
            colour = ETC_LIQUEFIED;
        else if (cell.flags & MAP_DISJUNCT)
            colour = ETC_DISJUNCTION;
        else if (cell.flags & MAP_HALOED)
        {
            if (cell.flags & MAP_SILENCED && cell.flags & MAP_UMBRAED)
                colour = CYAN; // Default for silence.
            else if (cell.flags & MAP_SILENCED)
                colour = LIGHTCYAN;
            else if (cell.flags & MAP_UMBRAED)
                colour = fdef.colour; // Cancels out!
            else
                colour = YELLOW;
        }
        else if (cell.flags & MAP_UMBRAED)
        {
            if (cell.flags & MAP_SILENCED)
                colour = BLUE; // Silence gets darker
            else
                colour = MAGENTA; // If no holy or silence
        }
        else if (cell.flags & MAP_SILENCED)
            colour = CYAN; // Silence but no holy/unholy
        else if (cell.flags & MAP_ORB_HALOED)
            colour = ETC_ORB_GLOW;
        else if (cell.flags & MAP_QUAD_HALOED)
            colour = BLUE;
#if TAG_MAJOR_VERSION == 34
        else if (cell.flags & MAP_HOT)
            colour = ETC_FIRE;
#endif
        else if (cell.flags & MAP_GOLDEN)
            colour = ETC_GOLD;
    }

    if (Options.show_travel_trail && travel_trail_index(loc) >= 0)
        colour |= COLFLAG_REVERSE;

    return colour;
}

static monster_type _show_mons_type(const monster_info& mi)
{
    if (mi.type == MONS_SLIME_CREATURE && mi.number > 1)
        return MONS_MERGED_SLIME_CREATURE;
    else if (mi.type == MONS_ZOMBIE)
    {
        return mons_zombie_size(mi.base_type) == Z_BIG ?
            MONS_ZOMBIE_LARGE : MONS_ZOMBIE_SMALL;
    }
    else if (mi.type == MONS_SKELETON)
    {
        return mons_zombie_size(mi.base_type) == Z_BIG ?
            MONS_SKELETON_LARGE : MONS_SKELETON_SMALL;
    }
    else if (mi.type == MONS_SIMULACRUM)
    {
        return mons_zombie_size(mi.base_type) == Z_BIG ?
            MONS_SIMULACRUM_LARGE : MONS_SIMULACRUM_SMALL;
    }
    else if (mi.type == MONS_SENSED)
        return mi.base_type;

    return mi.type;
}

static int _get_mons_colour(const monster_info& mi)
{
    // Show hp directly on the monster, except for irrelevant ones.
    // Fedhas worshippers might be interested in their plants however.
    if (crawl_state.viewport_monster_hp
        && (you_worship(GOD_FEDHAS) || !mons_class_is_firewood(mi.type)))
    {
        return dam_colour(mi) | COLFLAG_ITEM_HEAP;
    }

    int col = mi.colour;

    // We really shouldn't store unmodified colour.  This hack compares
    // effective type, but really, all redefinitions should work instantly,
    // rather than for newly spawned monsters only.
    monster_type stype = _show_mons_type(mi);
    if (stype != mi.type && mi.type != MONS_SENSED)
        col = mons_class_colour(stype);

    if (mi.is(MB_ROLLING))
        col = ETC_BONE;

    if (mi.is(MB_BERSERK))
        col = RED;

    if (mi.is(MB_MIRROR_DAMAGE))
        col = ETC_DEATH;

    if (mi.is(MB_INNER_FLAME))
        col = ETC_FIRE;

    if (mi.attitude == ATT_FRIENDLY)
        col |= COLFLAG_FRIENDLY_MONSTER;
    else if (mi.attitude != ATT_HOSTILE)
        col |= COLFLAG_NEUTRAL_MONSTER;
    else if (Options.stab_brand != CHATTR_NORMAL
             && mi.is(MB_STABBABLE))
    {
        col |= COLFLAG_WILLSTAB;
    }
    else if (Options.may_stab_brand != CHATTR_NORMAL
             && mi.is(MB_DISTRACTED))
    {
        col |= COLFLAG_MAYSTAB;
    }
    else if (mons_class_is_stationary(mi.type))
    {
        if (Options.feature_item_brand != CHATTR_NORMAL
            && feat_stair_direction(grd(mi.pos)) != CMD_NO_CMD)
        {
            col |= COLFLAG_FEATURE_ITEM;
        }
        else if (Options.heap_brand != CHATTR_NORMAL
                 && you.visible_igrd(mi.pos) != NON_ITEM
                 && !crawl_state.game_is_arena())
        {
            col |= COLFLAG_ITEM_HEAP;
        }
    }

    // Backlit monsters are fuzzy and override brands.
    if (!crawl_state.game_is_arena()
        && !you.can_see_invisible()
        && mi.is(MB_INVISIBLE)
        && mi.attitude != ATT_FRIENDLY)
    {
        col = DARKGREY;
    }

    return col;
}

static cglyph_t _get_item_override(const item_def &item)
{
    cglyph_t g;
    g.ch = 0;
    g.col = 0;

    // Skip costly name calculations if not needed.
    if (Options.item_glyph_overrides.empty())
        return g;

    string name = stash_annotate_item(STASH_LUA_SEARCH_ANNOTATE, &item)
                + " {" + item_prefix(item, false) + "} " + item.name(DESC_PLAIN);

    {
        // Check the cache...
        map<string, cglyph_t>::const_iterator ir = Options.item_glyph_cache.find(name);
        if (ir != Options.item_glyph_cache.end())
            return ir->second;
    }

    for (vector<pair<string, cglyph_t> >::const_iterator ir =
         Options.item_glyph_overrides.begin();
         ir != Options.item_glyph_overrides.end(); ++ir)
    {
        text_pattern tpat(ir->first);
        if (tpat.matches(name))
        {
            // You may have a rule that sets the glyph but not colour for
            // axes, then another that sets colour only for artefacts
            // (useless items, etc).  Thus, apply only parts that apply.
            if (ir->second.ch)
                g.ch = ir->second.ch;
            if (ir->second.col)
                g.col = ir->second.col;
        }
    }

    // Matching against a list of regexps can be costly, save up to 1000
    // last matches.
    if (Options.item_glyph_cache.size() >= 1000)
        Options.item_glyph_cache.clear();
    Options.item_glyph_cache[name] = g;

    return g;
}

show_class get_cell_show_class(const map_cell& cell,
                               bool only_stationary_monsters)
{
    if (cell.invisible_monster())
        return SH_INVIS_EXPOSED;

    if (cell.monster() != MONS_NO_MONSTER
        && (!only_stationary_monsters
            || mons_class_is_stationary(cell.monster())))
    {
        return SH_MONSTER;
    }

    if (cell.cloud() != CLOUD_NONE)
        return SH_CLOUD;

    const dungeon_feature_type feat = cell.feat();
    if (feat && feat < DNGN_MINMOVE
        || feat > DNGN_OPEN_DOOR
           && feat != DNGN_ABANDONED_SHOP
           && feat != DNGN_STONE_ARCH
           && feat != DNGN_EXPIRED_PORTAL
           && (feat < DNGN_FOUNTAIN_BLUE || feat > DNGN_DRY_FOUNTAIN))
    {
        return SH_FEATURE;
    }

    if (cell.item())
        return SH_ITEM;

    if (cell.feat())
        return SH_FEATURE;

    return SH_NOTHING;
}

static const unsigned short ripple_table[] =
{
     BLUE,          // BLACK        => BLUE (default)
     BLUE,          // BLUE         => BLUE
     GREEN,         // GREEN        => GREEN
     CYAN,          // CYAN         => CYAN
     RED,           // RED          => RED
     MAGENTA,       // MAGENTA      => MAGENTA
     BROWN,         // BROWN        => BROWN
     DARKGREY,      // LIGHTGREY    => DARKGREY
     DARKGREY,      // DARKGREY     => DARKGREY
     BLUE,          // LIGHTBLUE    => BLUE
     GREEN,         // LIGHTGREEN   => GREEN
     BLUE,          // LIGHTCYAN    => BLUE
     RED,           // LIGHTRED     => RED
     MAGENTA,       // LIGHTMAGENTA => MAGENTA
     BROWN,         // YELLOW       => BROWN
     LIGHTGREY,     // WHITE        => LIGHTGREY
};

static cglyph_t _get_cell_glyph_with_class(const map_cell& cell,
                                           const coord_def& loc,
                                           const show_class cls,
                                           int colour_mode)
{
    const bool coloured = colour_mode == 0 ? cell.visible() : (colour_mode > 0);
    cglyph_t g;
    show_type show;

    g.ch = 0;
    const cloud_type cell_cloud = cell.cloud();

    switch (cls)
    {
    case SH_INVIS_EXPOSED:
        ASSERT(cell.invisible_monster());

        show.cls = SH_INVIS_EXPOSED;
        if (cell_cloud != CLOUD_NONE)
            g.col = cell.cloud_colour();
        else
            g.col = ripple_table[cell.feat_colour() & 0xf];
        break;

    case SH_MONSTER:
    {
        show = cell.monster();
        const monster_info* mi = cell.monsterinfo();
        ASSERT(mi);

        if (cell.detected_monster())
        {
            ASSERT(mi->type == MONS_SENSED);
            if (mons_is_sensed(mi->base_type))
                g.col = mons_class_colour(mi->base_type);
            else
                g.col = Options.detected_monster_colour;
        }
        else if (!coloured)
            g.col = Options.remembered_monster_colour;
        else
            g.col = _get_mons_colour(*mi);

        monster_type stype = _show_mons_type(*mi);
        const bool override = Options.mon_glyph_overrides.find(stype)
                              != Options.mon_glyph_overrides.end();
        if (mi->props.exists("glyph") && !override)
            g.ch = mi->props["glyph"].get_int();
        else if (show.mons == MONS_SENSED)
            g.ch = mons_char(mi->base_type);
        else
            g.ch = mons_char(stype);

        if (mi->props.exists("glyph") && override)
            g.col = mons_class_colour(stype);

        // If we want to show weapons, overwrite all of that.
        item_def* weapon = mi->inv[MSLOT_WEAPON].get();
        if (crawl_state.viewport_weapons && weapon)
        {
            show = *weapon;
            g = _get_item_override(*weapon);
            if (!g.col)
                g.col = weapon->colour;
        }

        break;
    }

    case SH_CLOUD:
        ASSERT(cell_cloud);

        show.cls = SH_CLOUD;
        if (coloured)
            g.col = cell.cloud_colour();
        else
            g.col = DARKGRAY;
        break;

    case SH_FEATURE:
        show = cell.feat();
        ASSERT(show);

        g.col = _cell_feat_show_colour(cell, loc, coloured);

        if (cell.item())
        {
            if (Options.feature_item_brand
                && (is_critical_feature(cell.feat())
                 || cell.feat() < DNGN_MINMOVE))
            {
                g.col |= COLFLAG_FEATURE_ITEM;
            }
            else if (Options.trap_item_brand && feat_is_trap(cell.feat()))
                g.col |= COLFLAG_TRAP_ITEM;
        }
        break;

    case SH_ITEM:
    {
        const item_info* eitem = cell.item();
        ASSERT(eitem);
        show = *eitem;

        g = _get_item_override(*eitem);

        if (feat_is_water(cell.feat()))
            g.col = _cell_feat_show_colour(cell, loc, coloured);
        else if (!g.col)
            g.col = eitem->colour;

        // monster(mimic)-owned items have link = NON_ITEM+1+midx
        if (cell.flags & MAP_MORE_ITEMS)
            g.col |= COLFLAG_ITEM_HEAP;
        break;
    }

    case SH_NOTHING:
    case NUM_SHOW_CLASSES:
        // blackness
        g.ch = ' ';
        return g;
    }

    if (!g.ch)
    {
        const feature_def &fdef = get_feature_def(show);
        g.ch = cell.seen() ? fdef.symbol : fdef.magic_symbol;
    }

    if (g.col)
        g.col = real_colour(g.col, loc);

    return g;
}

cglyph_t get_cell_glyph(const coord_def& loc, bool only_stationary_monsters,
                        int colour_mode)
{
    // note: this does NOT determine output of the player glyph;
    // that's handled by itself in _draw_player() in view.cc
    const map_cell& cell = env.map_knowledge(loc);
    const show_class cell_show_class =
        get_cell_show_class(cell, only_stationary_monsters);
    return _get_cell_glyph_with_class(cell, loc, cell_show_class, colour_mode);
}

ucs_t get_feat_symbol(dungeon_feature_type feat)
{
    return get_feature_def(feat).symbol;
}

ucs_t get_item_symbol(show_item_type it)
{
    return get_feature_def(show_type(it)).symbol;
}

cglyph_t get_item_glyph(const item_def *item)
{
    cglyph_t g = _get_item_override(*item);
    if (!g.ch)
        g.ch = get_feature_def(show_type(*item)).symbol;
    if (!g.col)
        g.col = item->colour;
    return g;
}

cglyph_t get_mons_glyph(const monster_info& mi)
{
    monster_type stype = _show_mons_type(mi);
    cglyph_t g;
    const bool override = Options.mon_glyph_overrides.find(stype)
                          != Options.mon_glyph_overrides.end();
    if (mi.props.exists("glyph") && !override)
        g.ch = mi.props["glyph"].get_int();
    else
        g.ch = mons_char(stype);

    if (mi.props.exists("glyph") && override)
        g.col = mons_class_colour(stype);
    else
        g.col = _get_mons_colour(mi);
    g.col = real_colour(g.col);
    return g;
}

string glyph_to_tagstr(const cglyph_t& g)
{
    string col = colour_to_str(g.col);
    string ch = stringize_glyph(g.ch);
    if (g.ch == '<')
        ch += "<";
    return make_stringf("<%s>%s</%s>", col.c_str(), ch.c_str(), col.c_str());
}
