/**
 * @file
 * @brief Weapon enchantment spells.
**/

#include "AppHdr.h"

#include "spl-wpnench.h"
#include "externs.h"

#include "areas.h"
#include "artefact.h"
#include "itemprop.h"
#include "makeitem.h"
#include "message.h"
#include "prompt.h"
#include "religion.h"
#include "shout.h"
#include "skills2.h"
#include "spl-miscast.h"

// We need to know what brands equate with what missile brands to know if
// we should disallow temporary branding or not.
static special_missile_type _convert_to_missile(brand_type which_brand)
{
    switch (which_brand)
    {
    case SPWPN_NORMAL: return SPMSL_NORMAL;
    case SPWPN_FLAMING: return SPMSL_FLAME;
    case SPWPN_FREEZING: return SPMSL_FROST;
    case SPWPN_VENOM: return SPMSL_POISONED;
    case SPWPN_CHAOS: return SPMSL_CHAOS;
    default: return SPMSL_NORMAL; // there are no equivalents for the rest
                                  // of the ammo brands.
    }
}

static bool _ok_for_launchers(brand_type which_brand)
{
    switch (which_brand)
    {
    case SPWPN_NORMAL:
    case SPWPN_FREEZING:
    case SPWPN_FLAMING:
    case SPWPN_VENOM:
    //case SPWPN_PAIN: -- no pain missile type yet
    case SPWPN_CHAOS:
    case SPWPN_VORPAL:
        return true;
    default:
        return false;
    }
}

/**
 * Gets the message for when a weapon is branded.
 *
 * @param which_brand       The type of brand.
 * @param is_range_weapon   Whether the weapon is ranged.
 * @return                  The message for the brand being applied.
 */
static string _get_brand_msg(brand_type which_brand, bool is_range_weapon)
{
    string msg; // for distortion
    switch (which_brand)
    {
    case SPWPN_FLAMING:
        return " bursts into flame!";
    case SPWPN_FREEZING:
        return is_range_weapon ? " frosts over!" : " glows blue.";
    case SPWPN_VENOM:
        return " starts dripping with poison.";
    case SPWPN_DRAINING:
        return " crackles with unholy energy.";
    case SPWPN_VORPAL:
        return " glows silver and looks extremely dangerous.";
    case SPWPN_DISTORTION:
        msg = " seems to ";
        msg += random_choose("twist", "bend", "vibrate",
                             "flex", "wobble", "twang", NULL);
        msg += (coinflip() ? " oddly." : " strangely.");
        return msg;
    case SPWPN_PAIN:
        return silenced(you.pos()) ? " writhes in agony." :
                                        " shrieks in agony.";
    case SPWPN_HOLY_WRATH:
        return " shines with holy light.";
    case SPWPN_ELECTROCUTION:
        return " starts to spark.";
    case SPWPN_ANTIMAGIC:
        return " depletes magic around it.";
    case SPWPN_CHAOS:
        return " glistens with random hues.";
    default:
        return " wibbles buggily.";
    }
}

/** End your weapon branding spell.
 *
 * Returns the weapon to the previous brand, and ends the
 * DUR_WEAPON_BRAND.
 * @param weapon The item in question (which may have just been unwielded).
 * @param verbose whether to print a message about expiration.
 */
void end_weapon_brand(item_def &weapon, bool verbose)
{
    ASSERT(you.duration[DUR_WEAPON_BRAND]);

    const int temp_effect = get_weapon_brand(weapon);
    set_item_ego_type(weapon, OBJ_WEAPONS, you.props[ORIGINAL_BRAND_KEY]);
    you.wield_change = true;
    you.props.erase(ORIGINAL_BRAND_KEY);
    you.duration[DUR_WEAPON_BRAND] = 0;
    if (verbose)
    {
        const char *msg = nullptr;

        switch (temp_effect)
        {
        case SPWPN_VORPAL:
            if (get_vorpal_type(weapon) == DVORP_SLICING)
                msg = " seems blunter.";
            else
                msg = " feels lighter.";
            break;
        case SPWPN_ANTIMAGIC:
            msg = " stops repelling magic.";
            calc_mp();
            break;
        case SPWPN_FLAMING:       msg = " goes out."; break;
        case SPWPN_FREEZING:      msg = " stops glowing."; break;
        case SPWPN_VENOM:         msg = " stops dripping with poison."; break;
        case SPWPN_DRAINING:      msg = " stops crackling."; break;
        case SPWPN_DISTORTION:    msg = " seems straighter."; break;
        case SPWPN_PAIN:          msg = " seems less pained."; break;
        case SPWPN_CHAOS:         msg = " seems more stable."; break;
        case SPWPN_ELECTROCUTION: msg = " stops emitting sparks."; break;
        case SPWPN_HOLY_WRATH:    msg = "'s light goes out."; break;
        default: msg = " seems inexplicably less special."; break;
        }

        mprf(MSGCH_DURATION, "%s%s", weapon.name(DESC_YOUR).c_str(), msg);
    }
}

/**
 *  Gets the duration modifier for a given brand type.
 *
 * @param which_brand       The type of brand.
 * @return                  The duration modifier, default 10.
 */
static int _get_brand_duration(brand_type which_brand)
{
    switch (which_brand)
    {
    case SPWPN_FLAMING:
    case SPWPN_FREEZING:
        return 7;
    case SPWPN_VENOM:
        return 15;
    case SPWPN_DRAINING:
        return 12;
    case SPWPN_DISTORTION:
        return 5;
    case SPWPN_PAIN:
        return 8;
    default:
        return 10;
    }
 }

/**
 * Temporarily brand a weapon.
 *
 * @param[in] which_brand   The brand type to apply.
 * @param[in] power         Spellpower.
 * @param[in] fail          Whether you've already failed to cast.
 * @return                  Success, fail, or abort.
 */
spret_type brand_weapon(brand_type which_brand, int power, bool fail)
{
    if (!you.weapon())
    {
        mpr("You aren't wielding a weapon.");
        return SPRET_ABORT;
    }

    item_def& weapon = *you.weapon();

    if (!is_brandable_weapon(weapon, true))
    {
        if (weapon.base_type != OBJ_WEAPONS)
            mpr("This isn't a weapon.");
        else
            mpr("You cannot enchant this weapon.");
        return SPRET_ABORT;
    }

    bool has_temp_brand = you.duration[DUR_WEAPON_BRAND];
    // No need to brand with a brand it's already branded with.
    if (!has_temp_brand && get_weapon_brand(weapon) == which_brand)
    {
        mpr("This weapon is already enchanted with that brand.");
        return SPRET_ABORT;
    }

    // Can only brand launchers with sensible brands.
    if (is_range_weapon(weapon))
    {
        // If the new missile type wouldn't match the launcher, say no.
        missile_type missile = fires_ammo_type(weapon);

        // XXX: To deal with the fact that is_missile_brand_ok will be
        // unhappy if we attempt to brand stones, tell it we're using
        // sling bullets instead.
        if (range_skill(weapon) == SK_SLINGS)
            missile = MI_SLING_BULLET;

        if (!is_missile_brand_ok(missile, _convert_to_missile(which_brand), true))
        {
            mpr("You cannot enchant this weapon with this spell.");
            return SPRET_ABORT;
        }

        // If the brand isn't appropriate for that launcher, also say no.
        if (!_ok_for_launchers(which_brand))
        {
            mpr("You cannot enchant this weapon with this spell.");
            return SPRET_ABORT;
        }
    }

    // Can't get out of it that easily...
    if (get_weapon_brand(weapon) == SPWPN_DISTORTION
        && !has_temp_brand
        && !you_worship(GOD_LUGONU))
    {
        const string prompt =
              "Really brand " + weapon.name(DESC_INVENTORY) + "?";
        if (!yesno(prompt.c_str(), false, 'n'))
        {
            canned_msg(MSG_OK);
            return SPRET_ABORT;
        }
        MiscastEffect(&you, WIELD_MISCAST, SPTYP_TRANSLOCATION,
                      9, 90, "a distortion unwield");
    }

    fail_check();

    string msg = weapon.name(DESC_YOUR);

    bool extending = has_temp_brand && get_weapon_brand(weapon) == which_brand;
    bool emit_special_message = !extending;
    int duration_affected = _get_brand_duration(which_brand);
    msg += _get_brand_msg(which_brand, is_range_weapon(weapon));

    if (which_brand == SPWPN_DISTORTION)
        power /= 2;
    else if (which_brand == SPWPN_PAIN && !silenced(you.pos()))
    {
        noisy(15, you.pos());
        // We must repeat the special message here (as there's a side effect.)
        emit_special_message = true;
    }

    if (!extending)
    {
        if (has_temp_brand)
            end_weapon_brand(weapon);
        you.props[ORIGINAL_BRAND_KEY] = get_weapon_brand(weapon);
        set_item_ego_type(weapon, OBJ_WEAPONS, which_brand);
        you.wield_change = true;
    }

    if (emit_special_message)
        mpr(msg.c_str());
    else
        mprf("%s flashes.", weapon.name(DESC_YOUR).c_str());

    you.increase_duration(DUR_WEAPON_BRAND,
                          duration_affected + roll_dice(2, power), 50);
    if (which_brand == SPWPN_ANTIMAGIC)
        calc_mp();

    return SPRET_SUCCESS;
}

spret_type cast_confusing_touch(int power, bool fail)
{
    fail_check();
    msg::stream << "Your " << you.hand_name(true) << " begin to glow "
                << (you.duration[DUR_CONFUSING_TOUCH] ? "brighter" : "red")
                << "." << endl;

    you.set_duration(DUR_CONFUSING_TOUCH,
                     max(10 + random2(power) / 5,
                         you.duration[DUR_CONFUSING_TOUCH]),
                     20, NULL);

    return SPRET_SUCCESS;
}

spret_type cast_sure_blade(int power, bool fail)
{
    if (!you.weapon())
        mpr("You aren't wielding a weapon!");
    else if (melee_skill(you.weapon()->base_type,
                          you.weapon()->sub_type) != SK_SHORT_BLADES)
    {
        mpr("You cannot bond with this weapon.");
    }
    else
    {
        fail_check();
        if (!you.duration[DUR_SURE_BLADE])
            mpr("You become one with your weapon.");
        else if (you.duration[DUR_SURE_BLADE] < 25 * BASELINE_DELAY)
            mpr("Your bond becomes stronger.");

        you.increase_duration(DUR_SURE_BLADE, 8 + (random2(power) / 10),
                              25, NULL);
        return SPRET_SUCCESS;
    }

    return SPRET_ABORT;
}
