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

#include "src/main/cpp/net/eagle0/shardok/library/unit/Battalion.hpp"

#include <algorithm>

namespace shardok {

auto MutatingMaybeSetMorale(Battalion *battalion, double newVal, const BattalionTypeSPtr &type)
        -> double {
    if (type->adjustsMorale) {
        if (newVal < 0.0) newVal = 0.0;
        else if (newVal > 100.0)
            newVal = 100.0;

        battalion->mutate_morale(newVal);
    }

    return newVal;
}

auto MutatingMaybeAdjustMorale(
        Battalion *battalion,
        const double incr,
        const double minimum,
        const BattalionTypeSPtr &type) -> double {
    const double newMorale = battalion->morale() + incr;
    return MutatingMaybeSetMorale(battalion, std::max(minimum, newMorale), type);
}

auto MutatingAdjustMorale(
        net::eagle0::shardok::storage::fb::Battalion &battalion,
        const double incr,
        const double minimum) -> double {
    const double newMorale = battalion.morale() + incr;
    return MutatingSetMorale(battalion, std::max(minimum, newMorale));
}

auto MutatingSetMorale(net::eagle0::shardok::storage::fb::Battalion &battalion, double newVal)
        -> double {
    if (newVal < 0.0) newVal = 0.0;
    else if (newVal > 100.0)
        newVal = 100.0;

    battalion.mutate_morale(newVal);

    return newVal;
}

inline double factorIntoAdjustment(const double factorPercentile, const double swingPercentile) {
    return 1.0 + ((factorPercentile - 50.0) * swingPercentile / 100.0) / 100.0;
}

int32_t MutatingInternalTakeDamage(
        Battalion *battalion,
        const CombatDamage &damage,
        const Terrain &terrain,
        double braveryAdjustment,
        double flankingAdjustment,
        double moralePerCasualty,
        double baseDeadliness,
        const BattalionTypeSPtr type);

int32_t MutatingTakeDamageFrom(
        Battalion *battalion,
        Battalion *from,
        const CombatDamage &damage,
        const Terrain &terrain,
        const AttackOrientation direction,
        const double cmdrBravery,
        const double moralePerCasualty,
        const double baseDeadliness,
        const BattalionTypeSPtr &recipientType,
        const BattalionTypeSPtr &fromType) {
    const double braveryAdjustment = factorIntoAdjustment(cmdrBravery, recipientType->braverySwing);
    double flankingAdjustment = 1.0;
    if (direction == flank) flankingAdjustment = recipientType->flankDamageMultiplier;
    else if (direction == rear)
        flankingAdjustment = recipientType->rearDamageMultiplier;

    const int32_t casualties = MutatingInternalTakeDamage(
            battalion,
            damage,
            terrain,
            braveryAdjustment,
            flankingAdjustment,
            moralePerCasualty,
            baseDeadliness,
            recipientType);
    MutatingMaybeAdjustMorale(from, casualties * moralePerCasualty, 1.0, fromType);
    return casualties;
}

int32_t MutatingTakeEnvironmentalDamage(
        Battalion *battalion,
        const CombatDamage &damage,
        const Terrain &terrain,
        const double moralePerCasualty,
        const double baseDeadliness,
        const BattalionTypeSPtr &type) {
    return MutatingInternalTakeDamage(
            battalion,
            damage,
            terrain,
            1.0,
            1.0,
            moralePerCasualty,
            baseDeadliness,
            type);
}

int32_t MutatingInternalTakeDamage(
        Battalion *battalion,
        const CombatDamage &damage,
        const Terrain &terrain,
        const double braveryAdjustment,
        const double flankingAdjustment,
        const double moralePerCasualty,
        const double baseDeadliness,
        const BattalionTypeSPtr type) {
    const double terrainAdjustment = type->GetDamageTakenMultiplierForTerrain(&terrain);

    double takenDamage = 0.0;

    for (int i = 0; i < (int)NUM_DAMAGE_TYPES; i++) {
        const double armamentAdjustment =
                1.0 - (type->baseResistanceByType[i] +
                       (battalion->armament() / 100.0) * type->maxAugmentResistanceByType[i]) /
                              100.0;
        double newDamage = damage.GetNormalDamageOfType((DamageType)i) * braveryAdjustment *
                           flankingAdjustment * armamentAdjustment;

        newDamage += damage.GetPenetratingDamageOfType((DamageType)i) * braveryAdjustment *
                     flankingAdjustment;

        if (DamageTypeGetsTerrainDefense((DamageType)i)) { newDamage *= terrainAdjustment; }

        takenDamage += newDamage;
    }

    int32_t newNumber;
    const int32_t casualties = (int32_t)(takenDamage * baseDeadliness);

    if (battalion->size() > casualties) {
        newNumber = battalion->size() - casualties;
    } else {
        newNumber = 0;
    }

    battalion->mutate_size(newNumber);

    MutatingMaybeAdjustMorale(battalion, -casualties * moralePerCasualty, 0.0, type);
    return casualties;
}

}  // namespace shardok
