//
//  CombatDamage.hpp
//  eagle0
//
//  Created by Dan Crosby on 11/24/14.
//  Copyright (c) 2014 none. All rights reserved.
//

#ifndef __eagle0__CombatDamage__
#define __eagle0__CombatDamage__

#include <string>
#include <vector>

namespace shardok {
// Normal and penetrating damage for each kind
enum DamageType {
    DamageType_slashing = 0,
    DamageType_puncturing,
    DamageType_stabbing,
    DamageType_crushing,
    DamageType_fire,
    DamageType_cold,
    DamageType_lightning,
    DamageType_aessence,
    DamageType_impact,
    NUM_DAMAGE_TYPES
};

static inline bool DamageTypeGetsTerrainDefense(const DamageType type) {
    switch (type) {
        case DamageType_slashing:
        case DamageType_puncturing:
        case DamageType_stabbing:
        case DamageType_crushing:
        case DamageType_impact: return true;

        case DamageType_fire:
        case DamageType_cold:
        case DamageType_lightning:
        case DamageType_aessence: return false;

        default: return true;
    }

    return false;
}

struct CombatDamage {
    const std::vector<double> damageByType;

    CombatDamage() : damageByType{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} {};
    CombatDamage(
            const double sl,
            const double pu,
            const double st,
            const double cr,
            const double fi,
            const double co,
            const double li,
            const double aes,
            const double im,
            const double psl,
            const double ppu,
            const double pst,
            const double pcr,
            const double pfi,
            const double pco,
            const double pli,
            const double paes,
            const double pim)
        : damageByType{
                  sl,
                  psl,
                  pu,
                  ppu,
                  st,
                  pst,
                  cr,
                  pcr,
                  fi,
                  pfi,
                  co,
                  pco,
                  li,
                  pli,
                  aes,
                  paes,
                  im,
                  pim} {}

    CombatDamage(const std::vector<double> byType) : damageByType(byType) {}

    CombatDamage(const CombatDamage& toCopy) : damageByType(toCopy.damageByType) {}

public:
    double GetNormalDamageOfType(const DamageType type) const { return damageByType[2 * type]; }
    double GetPenetratingDamageOfType(const DamageType type) const {
        return damageByType[2 * type + 1];
    };

    bool operator==(const CombatDamage& cmpr) const {
        for (size_t type = 0; type < 2 * NUM_DAMAGE_TYPES; type++) {
            if (damageByType[type] != cmpr.damageByType[type]) return false;
        }
        return true;
    }

    bool operator!=(const CombatDamage& cmpr) const { return !(*this == cmpr); }

    bool operator<=(const CombatDamage& cmpr) const {
        for (size_t type = 0; type < 2 * NUM_DAMAGE_TYPES; type++) {
            if (damageByType[type] > cmpr.damageByType[type]) return false;
        }
        return true;
    }

    bool operator>=(const CombatDamage& cmpr) const {
        for (size_t type = 0; type < 2 * NUM_DAMAGE_TYPES; type++) {
            if (damageByType[type] < cmpr.damageByType[type]) return false;
        }
        return true;
    }

    bool operator>(const CombatDamage& cmpr) const { return ((*this >= cmpr) && !(*this == cmpr)); }

    bool operator<(const CombatDamage& cmpr) const { return ((*this <= cmpr) && !(*this == cmpr)); }

    CombatDamage operator+(const CombatDamage& add) const {
        return Builder()
                .SetSlashing(
                        GetNormalDamageOfType(DamageType_slashing) +
                        add.GetNormalDamageOfType(DamageType_slashing))
                .SetPuncturing(
                        GetNormalDamageOfType(DamageType_puncturing) +
                        add.GetNormalDamageOfType(DamageType_puncturing))
                .SetStabbing(
                        GetNormalDamageOfType(DamageType_stabbing) +
                        add.GetNormalDamageOfType(DamageType_stabbing))
                .SetCrushing(
                        GetNormalDamageOfType(DamageType_crushing) +
                        add.GetNormalDamageOfType(DamageType_crushing))
                .SetFire(
                        GetNormalDamageOfType(DamageType_fire) +
                        add.GetNormalDamageOfType(DamageType_fire))
                .SetCold(
                        GetNormalDamageOfType(DamageType_cold) +
                        add.GetNormalDamageOfType(DamageType_cold))
                .SetLightning(
                        GetNormalDamageOfType(DamageType_lightning) +
                        add.GetNormalDamageOfType(DamageType_lightning))
                .SetAEssence(
                        GetNormalDamageOfType(DamageType_aessence) +
                        add.GetNormalDamageOfType(DamageType_aessence))
                .SetImpact(
                        GetNormalDamageOfType(DamageType_impact) +
                        add.GetNormalDamageOfType(DamageType_impact))

                .SetPenetratingSlashing(
                        GetPenetratingDamageOfType(DamageType_slashing) +
                        add.GetPenetratingDamageOfType(DamageType_slashing))
                .SetPenetratingPuncturing(
                        GetPenetratingDamageOfType(DamageType_puncturing) +
                        add.GetPenetratingDamageOfType(DamageType_puncturing))
                .SetPenetratingStabbing(
                        GetPenetratingDamageOfType(DamageType_stabbing) +
                        add.GetPenetratingDamageOfType(DamageType_stabbing))
                .SetPenetratingCrushing(
                        GetPenetratingDamageOfType(DamageType_crushing) +
                        add.GetPenetratingDamageOfType(DamageType_crushing))
                .SetPenetratingFire(
                        GetPenetratingDamageOfType(DamageType_fire) +
                        add.GetPenetratingDamageOfType(DamageType_fire))
                .SetPenetratingCold(
                        GetPenetratingDamageOfType(DamageType_cold) +
                        add.GetPenetratingDamageOfType(DamageType_cold))
                .SetPenetratingLightning(
                        GetPenetratingDamageOfType(DamageType_lightning) +
                        add.GetPenetratingDamageOfType(DamageType_lightning))
                .SetPenetratingAEssence(
                        GetPenetratingDamageOfType(DamageType_aessence) +
                        add.GetPenetratingDamageOfType(DamageType_aessence))
                .SetPenetratingImpact(
                        GetPenetratingDamageOfType(DamageType_impact) +
                        add.GetPenetratingDamageOfType(DamageType_impact))

                .Build();
    }

    CombatDamage operator*(const double factor) const {
        return Builder()
                .SetSlashing(GetNormalDamageOfType(DamageType_slashing) * factor)
                .SetPuncturing(GetNormalDamageOfType(DamageType_puncturing) * factor)
                .SetStabbing(GetNormalDamageOfType(DamageType_stabbing) * factor)
                .SetCrushing(GetNormalDamageOfType(DamageType_crushing) * factor)
                .SetFire(GetNormalDamageOfType(DamageType_fire) * factor)
                .SetCold(GetNormalDamageOfType(DamageType_cold) * factor)
                .SetLightning(GetNormalDamageOfType(DamageType_lightning) * factor)
                .SetAEssence(GetNormalDamageOfType(DamageType_aessence) * factor)
                .SetImpact(GetNormalDamageOfType(DamageType_impact) * factor)

                .SetPenetratingSlashing(GetPenetratingDamageOfType(DamageType_slashing) * factor)
                .SetPenetratingPuncturing(
                        GetPenetratingDamageOfType(DamageType_puncturing) * factor)
                .SetPenetratingStabbing(GetPenetratingDamageOfType(DamageType_stabbing) * factor)
                .SetPenetratingCrushing(GetPenetratingDamageOfType(DamageType_crushing) * factor)
                .SetPenetratingFire(GetPenetratingDamageOfType(DamageType_fire) * factor)
                .SetPenetratingCold(GetPenetratingDamageOfType(DamageType_cold) * factor)
                .SetPenetratingLightning(GetPenetratingDamageOfType(DamageType_lightning) * factor)
                .SetPenetratingAEssence(GetPenetratingDamageOfType(DamageType_aessence) * factor)
                .SetPenetratingImpact(GetPenetratingDamageOfType(DamageType_impact) * factor)
                .Build();
    }

    std::string ToString() const {
        char buffer[1024];
        snprintf(
                buffer,
                1024,
                "slashing=%f / %f, puncturing=%f / %f, stabbing=%f / %f, crushing=%f / %f, "
                "fire=%f / "
                "%f, cold=%f / %f, lightning=%f / %f, aessence=%f / %f, impact=%f / %f",
                GetNormalDamageOfType(DamageType_slashing),
                GetPenetratingDamageOfType(DamageType_slashing),
                GetNormalDamageOfType(DamageType_puncturing),
                GetPenetratingDamageOfType(DamageType_puncturing),
                GetNormalDamageOfType(DamageType_stabbing),
                GetPenetratingDamageOfType(DamageType_stabbing),
                GetNormalDamageOfType(DamageType_crushing),
                GetPenetratingDamageOfType(DamageType_crushing),
                GetNormalDamageOfType(DamageType_fire),
                GetPenetratingDamageOfType(DamageType_fire),
                GetNormalDamageOfType(DamageType_cold),
                GetPenetratingDamageOfType(DamageType_cold),
                GetNormalDamageOfType(DamageType_lightning),
                GetPenetratingDamageOfType(DamageType_lightning),
                GetNormalDamageOfType(DamageType_aessence),
                GetPenetratingDamageOfType(DamageType_aessence),
                GetNormalDamageOfType(DamageType_impact),
                GetPenetratingDamageOfType(DamageType_impact));

        return std::string(buffer);
    }

    class Builder {
    private:
        double slashing = 0.0;
        double puncturing = 0.0;
        double stabbing = 0.0;
        double crushing = 0.0;
        double fire = 0.0;
        double cold = 0.0;
        double lightning = 0.0;
        double aessence = 0.0;
        double impact = 0.0;

        double penetrating_slashing = 0.0;
        double penetrating_puncturing = 0.0;
        double penetrating_stabbing = 0.0;
        double penetrating_crushing = 0.0;
        double penetrating_fire = 0.0;
        double penetrating_cold = 0.0;
        double penetrating_lightning = 0.0;
        double penetrating_aessence = 0.0;
        double penetrating_impact = 0.0;

    public:
        Builder() {}
        Builder& SetSlashing(const double sl) {
            slashing = sl;
            return *this;
        }
        Builder& SetPuncturing(const double pu) {
            puncturing = pu;
            return *this;
        }
        Builder& SetStabbing(const double st) {
            stabbing = st;
            return *this;
        }
        Builder& SetCrushing(const double cr) {
            crushing = cr;
            return *this;
        }
        Builder& SetFire(const double fi) {
            fire = fi;
            return *this;
        }
        Builder& SetCold(const double co) {
            cold = co;
            return *this;
        }
        Builder& SetLightning(const double li) {
            lightning = li;
            return *this;
        }
        Builder& SetAEssence(const double aes) {
            aessence = aes;
            return *this;
        }
        Builder& SetImpact(const double im) {
            impact = im;
            return *this;
        }

        Builder& SetPenetratingSlashing(const double sl) {
            penetrating_slashing = sl;
            return *this;
        }
        Builder& SetPenetratingPuncturing(const double pu) {
            penetrating_puncturing = pu;
            return *this;
        }
        Builder& SetPenetratingStabbing(const double st) {
            penetrating_stabbing = st;
            return *this;
        }
        Builder& SetPenetratingCrushing(const double cr) {
            penetrating_crushing = cr;
            return *this;
        }
        Builder& SetPenetratingFire(const double fi) {
            penetrating_fire = fi;
            return *this;
        }
        Builder& SetPenetratingCold(const double co) {
            penetrating_cold = co;
            return *this;
        }
        Builder& SetPenetratingLightning(const double li) {
            penetrating_lightning = li;
            return *this;
        }
        Builder& SetPenetratingAEssence(const double aes) {
            penetrating_aessence = aes;
            return *this;
        }
        Builder& SetPenetratingImpact(const double im) {
            penetrating_impact = im;
            return *this;
        }

        Builder&
        SetDamageByType(const DamageType type, const bool isPenetrating, const double amt) {
            if (isPenetrating) {
                switch (type) {
                    case DamageType_slashing: SetPenetratingSlashing(amt); break;

                    case DamageType_puncturing: SetPenetratingPuncturing(amt); break;

                    case DamageType_stabbing: SetPenetratingStabbing(amt); break;

                    case DamageType_crushing: SetPenetratingCrushing(amt); break;

                    case DamageType_fire: SetPenetratingFire(amt); break;

                    case DamageType_cold: SetPenetratingCold(amt); break;

                    case DamageType_lightning: SetPenetratingLightning(amt); break;

                    case DamageType_aessence: SetPenetratingAEssence(amt); break;

                    case DamageType_impact: SetPenetratingImpact(amt); break;

                    default: throw 1;
                }

            } else {
                switch (type) {
                    case DamageType_slashing: SetSlashing(amt); break;

                    case DamageType_puncturing: SetPuncturing(amt); break;

                    case DamageType_stabbing: SetStabbing(amt); break;

                    case DamageType_crushing: SetCrushing(amt); break;

                    case DamageType_fire: SetFire(amt); break;

                    case DamageType_cold: SetCold(amt); break;

                    case DamageType_lightning: SetLightning(amt); break;

                    case DamageType_aessence: SetAEssence(amt); break;

                    case DamageType_impact: SetImpact(amt); break;

                    default: throw 1;
                }
            }

            return *this;
        }

        CombatDamage Build() {
            return CombatDamage(
                    slashing,
                    puncturing,
                    stabbing,
                    crushing,
                    fire,
                    cold,
                    lightning,
                    aessence,
                    impact,
                    penetrating_slashing,
                    penetrating_puncturing,
                    penetrating_stabbing,
                    penetrating_crushing,
                    penetrating_fire,
                    penetrating_cold,
                    penetrating_lightning,
                    penetrating_aessence,
                    penetrating_impact);
        }
    };

    Builder ToBuilder() {
        Builder builder = Builder();
        for (int i = 0; i < (int)NUM_DAMAGE_TYPES; i++) {
            builder.SetDamageByType(
                    (DamageType)i,
                    false,
                    this->GetNormalDamageOfType((DamageType)i));
            builder.SetDamageByType(
                    (DamageType)i,
                    true,
                    this->GetPenetratingDamageOfType((DamageType)i));
        }
        return builder;
    }
};

inline CombatDamage operator*(const double factor, const CombatDamage& dmg) { return dmg * factor; }
}  // namespace shardok

#endif /* defined(__eagle0__CombatDamage__) */
