//
//  TileModifier.hpp
//  eagle0_tests
//
//  Created by Dan Crosby on 3/6/18.
//  Copyright © 2018 none. All rights reserved.
//

#ifndef TileModifiers_hpp
#define TileModifiers_hpp

#include <algorithm>
#include <optional>

#include "src/main/flatbuffer/net/eagle0/shardok/storage/tile_modifier.hpp"
#include "src/main/protobuf/net/eagle0/shardok/common/tile_modifier.pb.h"

namespace shardok {

using SingleModifier = net::eagle0::shardok::common::TileModifier_SingleModifier;
using TileModifierProto = net::eagle0::shardok::common::TileModifier;

SingleModifier MakeSingleModifier(const std::optional<double>& integ = std::optional<double>());

bool operator==(const SingleModifier& lhs, const SingleModifier& rhs);

TileModifierProto MakeTileModifier(bool onFire = false);

static inline bool IsOnFire(const TileModifierProto& tm) { return tm.has_fire(); }

static inline TileModifierProto& MutatingSetFire(TileModifierProto& tm, const bool f = true) {
    if (f) {
        tm.mutable_fire();
    } else {
        tm.clear_fire();
    }
    return tm;
}

static inline bool IsFrozen(const TileModifierProto& tm) { return tm.has_ice(); }

static inline bool HasBridge(const TileModifierProto& tm) { return tm.has_bridge(); }

static inline bool HasSnow(const TileModifierProto& tm) { return tm.has_snow(); }

static inline bool HasCastle(const TileModifierProto& tm) { return tm.has_castle(); }

static inline bool IsTraversible(const TileModifierProto& tm) {
    return IsFrozen(tm) || HasBridge(tm);
}

static inline TileModifierProto& IncreaseFrozen(TileModifierProto& tm, const double integrity) {
    const double existingIntegrity =
            (tm.has_ice() && tm.ice().has_integrity()) ? tm.ice().integrity().value() : 0.0;
    double newIntegrity = std::min(100.0, integrity + existingIntegrity);

    *tm.mutable_ice() = MakeSingleModifier(newIntegrity);
    return tm;
}

// Removes bridge if integrity <= 0.0
static inline TileModifierProto& SetBridge(TileModifierProto& tm, double integrity) {
    if (integrity > 100.0) { integrity = 100.0; }
    if (integrity > 0.0) {
        *tm.mutable_bridge() = MakeSingleModifier(integrity);
    } else {
        tm.clear_bridge();
    }
    return tm;
}

static inline auto SetBridge(net::eagle0::shardok::storage::fb::TileModifier& tm, double integrity)
        -> net::eagle0::shardok::storage::fb::TileModifier& {
    if (integrity > 100.0) { integrity = 100.0; }
    if (integrity > 0.0) {
        tm.mutable_bridge().mutate_present(true);
        tm.mutable_bridge().mutate_integrity(integrity);
    } else {
        tm.mutable_bridge().mutate_present(false);
    }
    return tm;
}

// Removes bridge if new integrity <= 0.0. Does nothing if there is no bridge.
static inline void ReduceBridgeIntegrity(TileModifierProto& tm, const double damage) {
    if (HasBridge(tm)) { SetBridge(tm, tm.bridge().integrity().value() - damage); }
}

static inline void ReduceBridgeIntegrity(
        net::eagle0::shardok::storage::fb::TileModifier& tm,
        const double damage) {
    if (tm.bridge().present()) { SetBridge(tm, tm.bridge().integrity() - damage); }
}

static inline net::eagle0::shardok::storage::fb::TileModifier WithReducedBridgeIntegrity(
        net::eagle0::shardok::storage::fb::TileModifier tm,
        const double damage) {
    if (tm.bridge().present()) { SetBridge(tm, tm.bridge().integrity() - damage); }

    return tm;
}

static inline TileModifierProto WithReducedBridgeIntegrity(
        TileModifierProto tm,
        const double damage) {
    if (HasBridge(tm)) { SetBridge(tm, tm.bridge().integrity().value() - damage); }

    return tm;
}

static inline TileModifierProto& SetCastle(TileModifierProto& tm, double integrity) {
    if (integrity > 100.0) { integrity = 100.0; }
    if (integrity < 0.0) { integrity = 0.0; }
    *tm.mutable_castle() = MakeSingleModifier(integrity);
    return tm;
}

static inline net::eagle0::shardok::storage::fb::TileModifier& SetCastle(
        net::eagle0::shardok::storage::fb::TileModifier& tm,
        double integrity) {
    if (integrity > 100.0) { integrity = 100.0; }
    if (integrity < 0.0) { integrity = 0.0; }
    tm.mutable_castle().mutate_integrity(integrity);
    tm.mutable_castle().mutate_present(true);
    return tm;
}

// Castle remains even if at integrity 0.0. Does nothing if there is no castle.
static inline void ReduceCastleIntegrity(TileModifierProto& tm, const double damage) {
    if (HasCastle(tm)) {
        double newIntegrity = tm.castle().integrity().value() - damage;
        if (newIntegrity < 0.0) newIntegrity = 0.0;
        SetCastle(tm, newIntegrity);
    }
}

static inline void ReduceCastleIntegrity(
        net::eagle0::shardok::storage::fb::TileModifier& tm,
        const double damage) {
    if (tm.castle().present()) { SetCastle(tm, tm.castle().integrity() - damage); }
}

static inline net::eagle0::shardok::storage::fb::TileModifier WithReducedCastleIntegrity(
        net::eagle0::shardok::storage::fb::TileModifier tm,
        const double damage) {
    if (tm.castle().present()) { SetCastle(tm, tm.castle().integrity() - damage); }

    return tm;
}

static inline TileModifierProto WithReducedCastleIntegrity(
        TileModifierProto tm,
        const double damage) {
    if (HasCastle(tm)) {
        double newIntegrity = tm.castle().integrity().value() - damage;
        if (newIntegrity < 0.0) newIntegrity = 0.0;
        SetCastle(tm, newIntegrity);
    }

    return tm;
}

static inline auto SetIce(net::eagle0::shardok::storage::fb::TileModifier& tm, double integrity)
        -> net::eagle0::shardok::storage::fb::TileModifier& {
    if (integrity > 100.0) { integrity = 100.0; }
    if (integrity > 0.0) {
        tm.mutable_ice().mutate_present(true);
        tm.mutable_ice().mutate_integrity(integrity);
    } else {
        tm.mutable_ice().mutate_present(false);
    }
    return tm;
}

static inline auto SetSnow(net::eagle0::shardok::storage::fb::TileModifier& tm, double integrity)
        -> net::eagle0::shardok::storage::fb::TileModifier& {
    if (integrity > 100.0) { integrity = 100.0; }
    if (integrity > 0.0) {
        tm.mutable_snow().mutate_present(true);
        tm.mutable_snow().mutate_integrity(integrity);
    } else {
        tm.mutable_snow().mutate_present(false);
    }
    return tm;
}

void MutatingAdjustBridgeIntegrity(TileModifierProto* tm, double adjustment);
void MutatingAdjustCastleIntegrity(TileModifierProto* tm, double adjustment);
void MutatingAdjustIceIntegrity(TileModifierProto* tm, double adjustment);
void MutatingAdjustSnowIntegrity(TileModifierProto* tm, double adjustment);

static inline auto operator==(const TileModifierProto& lhs, const TileModifierProto& rhs) -> bool {
    return lhs.fire() == rhs.fire() && lhs.bridge() == rhs.bridge() && lhs.ice() == rhs.ice();
}
}  // namespace shardok

#endif /* TileModifiers_hpp */
