/*Copyright (C) 2010-2023 Duncan Deveaux

This file is part of Hikou no mizu.

Hikou no mizu is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Hikou no mizu is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Hikou no mizu.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "DamageStatus.hpp"
#include "Player/Player.hpp"
#include "Fight/DamageDealer.hpp" ///< DamageDealer and TimedDamage

#include "Tools/ByteTools.hpp"
#include <algorithm>

DamageStatus::DamageStatus() : m_direction(HitDirection::FromLeft),
m_strength(0.f), m_sourcePlayer(0), m_targetPlayer(0)
{

}

bool DamageStatus::readFromDamage(const Timer::TimePoint &serverTime,
                                  const TimedDamage &damage,
                                  const std::vector<const Player*> &players)
{
    if (serverTime > damage.time || players.size() > 256)
        return false;

    m_timeToDamage = std::chrono::duration_cast<Timer::duration_uint64_us>(
        damage.time - serverTime);

    m_direction = damage.damage.direction;
    m_strength = damage.damage.strength;

    std::vector<const Player*>::const_iterator it_source =
        std::find(players.begin(), players.end(), damage.damage.source);
    if (it_source == players.end())
        return false;

    std::vector<const Player*>::const_iterator it_target =
        std::find(players.begin(), players.end(), damage.damage.target);
    if (it_target == players.end())
        return false;

    m_sourcePlayer = static_cast<std::uint8_t>
        (std::distance(players.begin(), it_source));
    m_targetPlayer = static_cast<std::uint8_t>
        (std::distance(players.begin(), it_target));

    return true;
}

bool DamageStatus::writeToDamage(const Timer::TimePoint &clientTime,
                                 const std::vector<Player*> &players,
                                 TimedDamage &dst) const
{
    if (m_sourcePlayer >= players.size() || m_targetPlayer >= players.size())
        return false;

    dst.time = clientTime + m_timeToDamage;
    dst.damage.direction = m_direction;
    dst.damage.strength = m_strength;
    dst.damage.source = players[m_sourcePlayer];
    dst.damage.target = players[m_targetPlayer];
    return true;
}

bool DamageStatus::readFromBuffer(std::list<std::uint8_t> &bytes)
{
    std::uint64_t ticks = 0;
    std::uint8_t direction = 0;

    if (!ByteTools::readUnsigned<std::uint64_t, 2>(bytes, ticks) ||
        !ByteTools::readUnsigned(bytes, direction))
        return false;

    m_timeToDamage = Timer::duration_uint64_us(ticks);
    m_direction = (direction == 1) ? HitDirection::FromLeft
                                   : HitDirection::FromRight;

    return (ByteTools::readUnsigned(bytes, m_sourcePlayer) &&
            ByteTools::readUnsigned(bytes, m_targetPlayer) &&
            ByteTools::readFloat(bytes, m_strength));
}

bool DamageStatus::writeToBuffer(std::list<std::uint8_t> &dst) const
{
    //Using 2 bytes, will overflow for values over 65ms
    //but enough to store damage delay which are 10ms or less
    ByteTools::writeUnsigned<std::uint64_t, 2>(m_timeToDamage.count(), dst);

    const std::uint8_t direction =
        (m_direction == HitDirection::FromLeft) ? 1 : 0;
    ByteTools::writeUnsigned(direction, dst);

    ByteTools::writeUnsigned(m_sourcePlayer, dst);
    ByteTools::writeUnsigned(m_targetPlayer, dst);
    ByteTools::writeFloat(m_strength, dst);

    return true;
}

std::vector<DamageStatus> DamageStatus::readDamageStatusList(
                                    const Timer::TimePoint &serverTime,
                                    const std::list<TimedDamage> &damages,
                                    const std::vector<const Player*> &players)
{
    std::vector<DamageStatus> statusList;
    statusList.reserve(damages.size());

    for (const TimedDamage &damage : damages)
    {
        DamageStatus status;
        if (status.readFromDamage(serverTime, damage, players))
            statusList.push_back(status);
    }

    return statusList;
}

void DamageStatus::applyDamageStatusList(DamageDealer &dealer,
                                    const Timer::TimePoint &clientTime,
                                    const std::vector<DamageStatus> &damages,
                                    const std::vector<Player*> &players)
{
    dealer.clear();
    for (const DamageStatus &status : damages)
    {
        TimedDamage damage;
        if (status.writeToDamage(clientTime, players, damage))
            dealer.push(damage);
    }
}
