#include "loadmgef.hpp"

#include <stdexcept>
#include <sstream>

#include <boost/lexical_cast.hpp>

#include "esmreader.hpp"
#include "esmwriter.hpp"
#include "defs.hpp"

namespace
{
    static const char *sIds[ESM::MagicEffect::Length] =
    {
        "WaterBreathing",
        "SwiftSwim",
        "WaterWalking",
        "Shield",
        "FireShield",
        "LightningShield",
        "FrostShield",
        "Burden",
        "Feather",
        "Jump",
        "Levitate",
        "SlowFall",
        "Lock",
        "Open",
        "FireDamage",
        "ShockDamage",
        "FrostDamage",
        "DrainAttribute",
        "DrainHealth",
        "DrainMagicka",
        "DrainFatigue",
        "DrainSkill",
        "DamageAttribute",
        "DamageHealth",
        "DamageMagicka",
        "DamageFatigue",
        "DamageSkill",
        "Poison",
        "WeaknessToFire",
        "WeaknessToFrost",
        "WeaknessToShock",
        "WeaknessToMagicka",
        "WeaknessToCommonDisease",
        "WeaknessToBlightDisease",
        "WeaknessToCorprusDisease",
        "WeaknessToPoison",
        "WeaknessToNormalWeapons",
        "DisintegrateWeapon",
        "DisintegrateArmor",
        "Invisibility",
        "Chameleon",
        "Light",
        "Sanctuary",
        "NightEye",
        "Charm",
        "Paralyze",
        "Silence",
        "Blind",
        "Sound",
        "CalmHumanoid",
        "CalmCreature",
        "FrenzyHumanoid",
        "FrenzyCreature",
        "DemoralizeHumanoid",
        "DemoralizeCreature",
        "RallyHumanoid",
        "RallyCreature",
        "Dispel",
        "Soultrap",
        "Telekinesis",
        "Mark",
        "Recall",
        "DivineIntervention",
        "AlmsiviIntervention",
        "DetectAnimal",
        "DetectEnchantment",
        "DetectKey",
        "SpellAbsorption",
        "Reflect",
        "CureCommonDisease",
        "CureBlightDisease",
        "CureCorprusDisease",
        "CurePoison",
        "CureParalyzation",
        "RestoreAttribute",
        "RestoreHealth",
        "RestoreMagicka",
        "RestoreFatigue",
        "RestoreSkill",
        "FortifyAttribute",
        "FortifyHealth",
        "FortifyMagicka",
        "FortifyFatigue",
        "FortifySkill",
        "FortifyMaximumMagicka",
        "AbsorbAttribute",
        "AbsorbHealth",
        "AbsorbMagicka",
        "AbsorbFatigue",
        "AbsorbSkill",
        "ResistFire",
        "ResistFrost",
        "ResistShock",
        "ResistMagicka",
        "ResistCommonDisease",
        "ResistBlightDisease",
        "ResistCorprusDisease",
        "ResistPoison",
        "ResistNormalWeapons",
        "ResistParalysis",
        "RemoveCurse",
        "TurnUndead",
        "SummonScamp",
        "SummonClannfear",
        "SummonDaedroth",
        "SummonDremora",
        "SummonAncestralGhost",
        "SummonSkeletalMinion",
        "SummonBonewalker",
        "SummonGreaterBonewalker",
        "SummonBonelord",
        "SummonWingedTwilight",
        "SummonHunger",
        "SummonGoldenSaint",
        "SummonFlameAtronach",
        "SummonFrostAtronach",
        "SummonStormAtronach",
        "FortifyAttack",
        "CommandCreature",
        "CommandHumanoid",
        "BoundDagger",
        "BoundLongsword",
        "BoundMace",
        "BoundBattleAxe",
        "BoundSpear",
        "BoundLongbow",
        "ExtraSpell",
        "BoundCuirass",
        "BoundHelm",
        "BoundBoots",
        "BoundShield",
        "BoundGloves",
        "Corprus",
        "Vampirism",
        "SummonCenturionSphere",
        "SunDamage",
        "StuntedMagicka",

        // Tribunal only
        "SummonFabricant",

        // Bloodmoon only
        "SummonWolf",
        "SummonBear",
        "SummonBonewolf",
        "SummonCreature04",
        "SummonCreature05"
    };

    const int NumberOfHardcodedFlags = 143;
    const int HardcodedFlags[NumberOfHardcodedFlags] = {
        0x11c8, 0x11c0, 0x11c8, 0x11e0, 0x11e0, 0x11e0, 0x11e0, 0x11d0,
        0x11c0, 0x11c0, 0x11e0, 0x11c0, 0x11184, 0x11184, 0x1f0, 0x1f0,
        0x1f0, 0x11d2, 0x11f0, 0x11d0, 0x11d0, 0x11d1, 0x1d2, 0x1f0,
        0x1d0, 0x1d0, 0x1d1, 0x1f0, 0x11d0, 0x11d0, 0x11d0, 0x11d0,
        0x11d0, 0x11d0, 0x11d0, 0x11d0, 0x11d0, 0x1d0, 0x1d0, 0x11c8,
        0x31c0, 0x11c0, 0x11c0, 0x11c0, 0x1180, 0x11d8, 0x11d8, 0x11d0,
        0x11d0, 0x11180, 0x11180, 0x11180, 0x11180, 0x11180, 0x11180, 0x11180,
        0x11180, 0x11c4, 0x111b8, 0x1040, 0x104c, 0x104c, 0x104c, 0x104c,
        0x1040, 0x1040, 0x1040, 0x11c0, 0x11c0, 0x1cc, 0x1cc, 0x1cc,
        0x1cc, 0x1cc, 0x1c2, 0x1c0, 0x1c0, 0x1c0, 0x1c1, 0x11c2,
        0x11c0, 0x11c0, 0x11c0, 0x11c1, 0x11c0, 0x21192, 0x20190, 0x20190,
        0x20190, 0x21191, 0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x11c0,
        0x11c0, 0x11c0, 0x11c0, 0x11c0, 0x1c0, 0x11190, 0x9048, 0x9048,
        0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x9048,
        0x9048, 0x9048, 0x9048, 0x9048, 0x9048, 0x11c0, 0x1180, 0x1180,
        0x5048, 0x5048, 0x5048, 0x5048, 0x5048, 0x5048, 0x1188, 0x5048,
        0x5048, 0x5048, 0x5048, 0x5048, 0x1048, 0x104c, 0x1048, 0x40,
        0x11c8, 0x1048, 0x1048, 0x1048, 0x1048, 0x1048, 0x1048
    };
}

namespace ESM
{
    unsigned int MagicEffect::sRecordId = REC_MGEF;

void MagicEffect::load(ESMReader &esm)
{
    esm.getHNT(mIndex, "INDX");

    mId = indexToId (mIndex);

    esm.getHNT(mData, "MEDT", 36);
    if (esm.getFormat() == 0)
    {
        // don't allow mods to change fixed flags in the legacy format
        mData.mFlags &= (AllowSpellmaking | AllowEnchanting | NegativeLight);
        if (mIndex>=0 && mIndex<NumberOfHardcodedFlags)
        mData.mFlags |= HardcodedFlags[mIndex];
    }

    // vanilla MW accepts the _SND subrecords before or after DESC... I hope
    // this isn't true for other records, or we have to do a mass-refactor
    while (esm.hasMoreSubs())
    {
        esm.getSubName();
        uint32_t name = esm.retSubName().val;
        switch (name)
        {
            case ESM::FourCC<'I','T','E','X'>::value:
                mIcon = esm.getHString();
                break;
            case ESM::FourCC<'P','T','E','X'>::value:
                mParticle = esm.getHString();
                break;
            case ESM::FourCC<'B','S','N','D'>::value:
                mBoltSound = esm.getHString();
                break;
            case ESM::FourCC<'C','S','N','D'>::value:
                mCastSound = esm.getHString();
                break;
            case ESM::FourCC<'H','S','N','D'>::value:
                mHitSound = esm.getHString();
                break;
            case ESM::FourCC<'A','S','N','D'>::value:
                mAreaSound = esm.getHString();
                break;
            case ESM::FourCC<'C','V','F','X'>::value:
                mCasting = esm.getHString();
                break;
            case ESM::FourCC<'B','V','F','X'>::value:
                mBolt = esm.getHString();
                break;
            case ESM::FourCC<'H','V','F','X'>::value:
                mHit = esm.getHString();
                break;
            case ESM::FourCC<'A','V','F','X'>::value:
                mArea = esm.getHString();
                break;
            case ESM::FourCC<'D','E','S','C'>::value:
                mDescription = esm.getHString();
                break;
            default:
                esm.fail("Unknown subrecord");
        }
    }
}
void MagicEffect::save(ESMWriter &esm) const
{
    esm.writeHNT("INDX", mIndex);

    esm.writeHNT("MEDT", mData, 36);

    esm.writeHNOCString("ITEX", mIcon);
    esm.writeHNOCString("PTEX", mParticle);
    esm.writeHNOCString("BSND", mBoltSound);
    esm.writeHNOCString("CSND", mCastSound);
    esm.writeHNOCString("HSND", mHitSound);
    esm.writeHNOCString("ASND", mAreaSound);

    esm.writeHNOCString("CVFX", mCasting);
    esm.writeHNOCString("BVFX", mBolt);
    esm.writeHNOCString("HVFX", mHit);
    esm.writeHNOCString("AVFX", mArea);

    esm.writeHNOString("DESC", mDescription);
}

short MagicEffect::getResistanceEffect(short effect)
{
    // Source https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attribute

    // <Effect, Effect providing resistance against first effect>
    std::map<short, short> effects;
    effects[DisintegrateArmor] = Sanctuary;
    effects[DisintegrateWeapon] = Sanctuary;

    for (int i=0; i<5; ++i)
        effects[DrainAttribute+i] = ResistMagicka;
    for (int i=0; i<5; ++i)
        effects[DamageAttribute+i] = ResistMagicka;
    for (int i=0; i<5; ++i)
        effects[AbsorbAttribute+i] = ResistMagicka;
    for (int i=0; i<10; ++i)
        effects[WeaknessToFire+i] = ResistMagicka;

    effects[Burden] = ResistMagicka;
    effects[Charm] = ResistMagicka;
    effects[Silence] = ResistMagicka;
    effects[Blind] = ResistMagicka;
    effects[Sound] = ResistMagicka;

    for (int i=0; i<2; ++i)
    {
        effects[CalmHumanoid] = ResistMagicka;
        effects[FrenzyHumanoid] = ResistMagicka;
        effects[DemoralizeHumanoid] = ResistMagicka;
        effects[RallyHumanoid] = ResistMagicka;
    }

    effects[TurnUndead] = ResistMagicka;

    effects[FireDamage] = ResistFire;
    effects[FrostDamage] = ResistFrost;
    effects[ShockDamage] = ResistShock;
    effects[Vampirism] = ResistCommonDisease;
    effects[Corprus] = ResistCorprusDisease;
    effects[Poison] = ResistPoison;
    effects[Paralyze] = ResistParalysis;

    if (effects.find(effect) != effects.end())
        return effects[effect];
    else
        return -1;
}

short MagicEffect::getWeaknessEffect(short effect)
{
    std::map<short, short> effects;

    for (int i=0; i<5; ++i)
        effects[DrainAttribute+i] = WeaknessToMagicka;
    for (int i=0; i<5; ++i)
        effects[DamageAttribute+i] = WeaknessToMagicka;
    for (int i=0; i<5; ++i)
        effects[AbsorbAttribute+i] = WeaknessToMagicka;
    for (int i=0; i<10; ++i)
        effects[WeaknessToFire+i] = WeaknessToMagicka;

    effects[Burden] = WeaknessToMagicka;
    effects[Charm] = WeaknessToMagicka;
    effects[Silence] = WeaknessToMagicka;
    effects[Blind] = WeaknessToMagicka;
    effects[Sound] = WeaknessToMagicka;

    for (int i=0; i<2; ++i)
    {
        effects[CalmHumanoid] = WeaknessToMagicka;
        effects[FrenzyHumanoid] = WeaknessToMagicka;
        effects[DemoralizeHumanoid] = WeaknessToMagicka;
        effects[RallyHumanoid] = WeaknessToMagicka;
    }

    effects[TurnUndead] = WeaknessToMagicka;

    effects[FireDamage] = WeaknessToFire;
    effects[FrostDamage] = WeaknessToFrost;
    effects[ShockDamage] = WeaknessToShock;
    effects[Vampirism] = WeaknessToCommonDisease;
    effects[Corprus] = WeaknessToCorprusDisease;
    effects[Poison] = WeaknessToPoison;

    // Weakness to magicka or -1 ?
    effects[Paralyze] = WeaknessToMagicka;

    if (effects.find(effect) != effects.end())
        return effects[effect];
    else
        return -1;
}

static std::map<short,std::string> genNameMap()
{
    // Map effect ID to GMST name
    // http://www.uesp.net/morrow/hints/mweffects.shtml
    std::map<short, std::string> names;
    names[85] ="sEffectAbsorbAttribute";
    names[88] ="sEffectAbsorbFatigue";
    names[86] ="sEffectAbsorbHealth";
    names[87] ="sEffectAbsorbSpellPoints";
    names[89] ="sEffectAbsorbSkill";
    names[63] ="sEffectAlmsiviIntervention";
    names[47] ="sEffectBlind";
    names[123] ="sEffectBoundBattleAxe";
    names[129] ="sEffectBoundBoots";
    names[127] ="sEffectBoundCuirass";
    names[120] ="sEffectBoundDagger";
    names[131] ="sEffectBoundGloves";
    names[128] ="sEffectBoundHelm";
    names[125] ="sEffectBoundLongbow";
    names[121] ="sEffectBoundLongsword";
    names[122] ="sEffectBoundMace";
    names[130] ="sEffectBoundShield";
    names[124] ="sEffectBoundSpear";
    names[7] ="sEffectBurden";
    names[50] ="sEffectCalmCreature";
    names[49] ="sEffectCalmHumanoid";
    names[40] ="sEffectChameleon";
    names[44] ="sEffectCharm";
    names[118] ="sEffectCommandCreatures";
    names[119] ="sEffectCommandHumanoids";
    names[132] ="sEffectCorpus"; // NB this typo. (bethesda made it)
    names[70] ="sEffectCureBlightDisease";
    names[69] ="sEffectCureCommonDisease";
    names[71] ="sEffectCureCorprusDisease";
    names[73] ="sEffectCureParalyzation";
    names[72] ="sEffectCurePoison";
    names[22] ="sEffectDamageAttribute";
    names[25] ="sEffectDamageFatigue";
    names[23] ="sEffectDamageHealth";
    names[24] ="sEffectDamageMagicka";
    names[26] ="sEffectDamageSkill";
    names[54] ="sEffectDemoralizeCreature";
    names[53] ="sEffectDemoralizeHumanoid";
    names[64] ="sEffectDetectAnimal";
    names[65] ="sEffectDetectEnchantment";
    names[66] ="sEffectDetectKey";
    names[38] ="sEffectDisintegrateArmor";
    names[37] ="sEffectDisintegrateWeapon";
    names[57] ="sEffectDispel";
    names[62] ="sEffectDivineIntervention";
    names[17] ="sEffectDrainAttribute";
    names[20] ="sEffectDrainFatigue";
    names[18] ="sEffectDrainHealth";
    names[19] ="sEffectDrainSpellpoints";
    names[21] ="sEffectDrainSkill";
    names[8] ="sEffectFeather";
    names[14] ="sEffectFireDamage";
    names[4] ="sEffectFireShield";
    names[117] ="sEffectFortifyAttackBonus";
    names[79] ="sEffectFortifyAttribute";
    names[82] ="sEffectFortifyFatigue";
    names[80] ="sEffectFortifyHealth";
    names[81] ="sEffectFortifySpellpoints";
    names[84] ="sEffectFortifyMagickaMultiplier";
    names[83] ="sEffectFortifySkill";
    names[52] ="sEffectFrenzyCreature";
    names[51] ="sEffectFrenzyHumanoid";
    names[16] ="sEffectFrostDamage";
    names[6] ="sEffectFrostShield";
    names[39] ="sEffectInvisibility";
    names[9] ="sEffectJump";
    names[10] ="sEffectLevitate";
    names[41] ="sEffectLight";
    names[5] ="sEffectLightningShield";
    names[12] ="sEffectLock";
    names[60] ="sEffectMark";
    names[43] ="sEffectNightEye";
    names[13] ="sEffectOpen";
    names[45] ="sEffectParalyze";
    names[27] ="sEffectPoison";
    names[56] ="sEffectRallyCreature";
    names[55] ="sEffectRallyHumanoid";
    names[61] ="sEffectRecall";
    names[68] ="sEffectReflect";
    names[100] ="sEffectRemoveCurse";
    names[95] ="sEffectResistBlightDisease";
    names[94] ="sEffectResistCommonDisease";
    names[96] ="sEffectResistCorprusDisease";
    names[90] ="sEffectResistFire";
    names[91] ="sEffectResistFrost";
    names[93] ="sEffectResistMagicka";
    names[98] ="sEffectResistNormalWeapons";
    names[99] ="sEffectResistParalysis";
    names[97] ="sEffectResistPoison";
    names[92] ="sEffectResistShock";
    names[74] ="sEffectRestoreAttribute";
    names[77] ="sEffectRestoreFatigue";
    names[75] ="sEffectRestoreHealth";
    names[76] ="sEffectRestoreSpellPoints";
    names[78] ="sEffectRestoreSkill";
    names[42] ="sEffectSanctuary";
    names[3] ="sEffectShield";
    names[15] ="sEffectShockDamage";
    names[46] ="sEffectSilence";
    names[11] ="sEffectSlowFall";
    names[58] ="sEffectSoultrap";
    names[48] ="sEffectSound";
    names[67] ="sEffectSpellAbsorption";
    names[136] ="sEffectStuntedMagicka";
    names[106] ="sEffectSummonAncestralGhost";
    names[110] ="sEffectSummonBonelord";
    names[108] ="sEffectSummonLeastBonewalker";
    names[134] ="sEffectSummonCenturionSphere";
    names[103] ="sEffectSummonClannfear";
    names[104] ="sEffectSummonDaedroth";
    names[105] ="sEffectSummonDremora";
    names[114] ="sEffectSummonFlameAtronach";
    names[115] ="sEffectSummonFrostAtronach";
    names[113] ="sEffectSummonGoldenSaint";
    names[109] ="sEffectSummonGreaterBonewalker";
    names[112] ="sEffectSummonHunger";
    names[102] ="sEffectSummonScamp";
    names[107] ="sEffectSummonSkeletalMinion";
    names[116] ="sEffectSummonStormAtronach";
    names[111] ="sEffectSummonWingedTwilight";
    names[135] ="sEffectSunDamage";
    names[1] ="sEffectSwiftSwim";
    names[59] ="sEffectTelekinesis";
    names[101] ="sEffectTurnUndead";
    names[133] ="sEffectVampirism";
    names[0] ="sEffectWaterBreathing";
    names[2] ="sEffectWaterWalking";
    names[33] ="sEffectWeaknesstoBlightDisease";
    names[32] ="sEffectWeaknesstoCommonDisease";
    names[34] ="sEffectWeaknesstoCorprusDisease";
    names[28] ="sEffectWeaknesstoFire";
    names[29] ="sEffectWeaknesstoFrost";
    names[31] ="sEffectWeaknesstoMagicka";
    names[36] ="sEffectWeaknesstoNormalWeapons";
    names[35] ="sEffectWeaknesstoPoison";
    names[30] ="sEffectWeaknesstoShock";

    // bloodmoon
    names[138] ="sEffectSummonCreature01";
    names[139] ="sEffectSummonCreature02";
    names[140] ="sEffectSummonCreature03";
    names[141] ="sEffectSummonCreature04";
    names[142] ="sEffectSummonCreature05";

    // tribunal
    names[137] ="sEffectSummonFabricant";

    return names;
}
const std::map<short,std::string> MagicEffect::sNames = genNameMap();

const std::string &MagicEffect::effectIdToString(short effectID)
{
    std::map<short,std::string>::const_iterator name = sNames.find(effectID);
    if(name == sNames.end())
        throw std::runtime_error(std::string("Unimplemented effect ID ")+boost::lexical_cast<std::string>(effectID));

    return name->second;
}

class FindSecond {
    const std::string &mName;

public:
    FindSecond(const std::string &name) : mName(name) { }

    bool operator()(const std::pair<short,std::string> &item) const
    {
        if(Misc::StringUtils::ciEqual(item.second, mName))
            return true;
        return false;
    }
};

short MagicEffect::effectStringToId(const std::string &effect)
{
    std::map<short,std::string>::const_iterator name;

    name = std::find_if(sNames.begin(), sNames.end(), FindSecond(effect));
    if(name == sNames.end())
        throw std::runtime_error(std::string("Unimplemented effect ")+effect);

    return name->first;
}

MagicEffect::MagnitudeDisplayType MagicEffect::getMagnitudeDisplayType() const {
    if ( mData.mFlags & NoMagnitude )
        return MDT_None;
    if ( mIndex == 84 )
        return MDT_TimesInt;
    if ( mIndex == 59 ||
        ( mIndex >= 64 && mIndex <= 66) )
        return MDT_Feet;
    if ( mIndex == 118 || mIndex == 119 )
        return MDT_Level;
    if (   ( mIndex >= 28 && mIndex <= 36 )
        || ( mIndex >= 90 && mIndex <= 99 )
        ||   mIndex == 40 || mIndex == 47
        ||   mIndex == 57 || mIndex == 68 )
        return MDT_Percentage;

    return MDT_Points;
}

    void MagicEffect::blank()
    {
        mData.mSchool = 0;
        mData.mBaseCost = 0;
        mData.mFlags = 0;
        mData.mRed = 0;
        mData.mGreen = 0;
        mData.mBlue = 0;
        mData.mSpeed = 0;

        mIcon.clear();
        mParticle.clear();
        mCasting.clear();
        mHit.clear();
        mArea.clear();
        mBolt.clear();
        mCastSound.clear();
        mBoltSound.clear();
        mHitSound.clear();
        mAreaSound.clear();
        mDescription.clear();
    }

    std::string MagicEffect::indexToId (int index)
    {
        std::ostringstream stream;

        if (index!=-1)
        {
            stream << "#";

            if (index<100)
            {
                stream << "0";

                if (index<10)
                    stream << "0";
            }

            stream << index;

            if (index>=0 && index<Length)
                stream << sIds[index];
        }

        return stream.str();
    }
}
