/*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 "PeerInfoLayer.hpp"
#include "Network/Serialization/PeerData.hpp"
#include "Engines/Resources/TextureManager.hpp"
#include "Engines/Resources/FontManager.hpp"
#include "Structs/Vector.hpp"

#include "PlayerIcon.hpp"
#include "Tools/ScaledPixel.hpp"
#include "Tools/Log.hpp"
#include "Tools/I18n.hpp" ///< _ macro for i18n
#include <cstddef>

namespace
{
    void initializeText(Text &text, Vector position)
    {
        text.setColor(Color(255, 255, 255));
        text.setPosition(position.x, position.y - text.getOriginHeight());
    }
}

////////////////////
///PeerInfoHeader///
////////////////////
PeerInfoHeader::PeerInfoHeader(const RowLayout &layout, Font &font) :
Drawable(),
m_background(Polygon::rectangle(layout.width, layout.height,
                                Color(66, 112, 174, 200))),
m_character(_(PeerInfoCharacter), font),
m_username(_(PeerInfoUsername), font),
m_ping(_(PeerInfoPing), font),
m_width(layout.width), m_height(layout.height)
{
    m_background.setBorderSize(0.f);

    const float yBase = layout.height * .7f;
    initializeText(m_character, Vector(layout.icons, yBase));
    initializeText(m_username, Vector(layout.usernames, yBase));
    initializeText(m_ping, Vector(layout.pings, yBase));
}

void PeerInfoHeader::draw()
{
    //Update matrix
    Drawable::pushMatrix();
    Drawable::updateMatrix();

    m_background.draw();
    m_character.draw();
    m_username.draw();
    m_ping.draw();

    Drawable::popMatrix();
}

float PeerInfoHeader::getWidth() const
{
    return m_width;
}

float PeerInfoHeader::getHeight() const
{
    return m_height;
}

/////////////////
///PeerInfoRow///
/////////////////
PeerInfoRow::PeerInfoRow(const RowLayout &layout, const PeerData &peerData,
                         TextureManager &textureManager, Font &font) :
Drawable(),
m_background(Polygon::rectangle(layout.width, layout.height,
                                Color(0, 0, 0, 50))),
m_icon(PlayerIcon::load(peerData.character, textureManager)),
m_username(peerData.username, font),
m_ping("0", font),
m_height(layout.height)
{
    m_background.setBorderSize(0.f);

    if (m_icon.getHeight() > 0.f)
        m_icon.setScale(1.3f * layout.height / m_icon.getHeight());

    m_icon.setPosition(layout.icons,
        (layout.height - m_icon.getHeight() * m_icon.getYScale()) / 2.f);

    const float yBase = layout.height * .6f;
    initializeText(m_username, Vector(layout.usernames, yBase));
    initializeText(m_ping, Vector(layout.pings, yBase));
}

void PeerInfoRow::draw()
{
    //Update matrix
    Drawable::pushMatrix();
    Drawable::updateMatrix();

    m_background.draw();
    m_icon.draw();
    m_username.draw();
    m_ping.draw();

    Drawable::popMatrix();
}

void PeerInfoRow::setPing(std::uint32_t ping)
{
    m_ping.setText(std::to_string(ping));
}

float PeerInfoRow::getHeight() const
{
    return m_height;
}

/////////////////////
///SpectatorHeader///
/////////////////////
namespace
{
    /// Computes the width of the spectator header based on a requested
    /// width \p width excluding \p padding,
    /// and the text \p text which must be covered
    float spectatorHeaderWidth(float width, float padding, const Text &text)
    {
        return width < text.getWidth() + 2 * padding ?
                       text.getWidth() + 2 * padding : width;
    }
}

SpectatorHeader::SpectatorHeader(float width, float height, float padding,
                                 Font &font) :
Drawable(),
m_text(_(PeerInfoSpectators), font),
m_background(Polygon::rectangle(spectatorHeaderWidth(width, padding, m_text),
                                height, Color(66, 112, 174, 200))),
m_height(height), m_padding(padding)
{
    m_background.setBorderSize(0.f);
    m_text.setColor(Color(255, 255, 255));
    m_text.setPosition(padding, m_height * .7f - m_text.getOriginHeight());
}

void SpectatorHeader::draw()
{
    //Update matrix
    Drawable::pushMatrix();
    Drawable::updateMatrix();

    m_background.draw();
    m_text.draw();

    Drawable::popMatrix();
}

void SpectatorHeader::setWidth(float width)
{
    m_background = Polygon::rectangle(
        spectatorHeaderWidth(width, m_padding, m_text),
        m_height, Color(66, 112, 174, 200));

    m_background.setBorderSize(0.f);
}

float SpectatorHeader::getHeight() const
{
    return m_height;
}

////////////////////
///SpectatorBadge///
////////////////////
SpectatorBadge::SpectatorBadge(float height, float padding,
                               const PeerData &peerData,
                               TextureManager &textureManager, Font &font) :
Drawable(),
m_icon(PlayerIcon::load(peerData.character, textureManager)),
m_username(peerData.username, font),
m_width(m_icon.getWidth() * height / m_icon.getHeight() +
        m_username.getWidth() + padding * 3.f),
m_height(height),
m_background(Polygon::rectangle(m_width, m_height, Color(0, 0, 0, 50)))
{
    m_icon.setScale(height / m_icon.getHeight());
    m_icon.setXPosition(padding);

    m_username.setColor(Color(255, 255, 255));
    m_username.setPosition(
        padding * 2.f + m_icon.getWidth() * m_icon.getXScale(),
        height * .7f - m_username.getOriginHeight());

    m_background.setBorderSize(0.f);
}

void SpectatorBadge::draw()
{
    //Update matrix
    Drawable::pushMatrix();
    Drawable::updateMatrix();

    m_background.draw();
    m_icon.draw();
    m_username.draw();

    Drawable::popMatrix();
}

float SpectatorBadge::getWidth() const
{
    return m_width;
}

float SpectatorBadge::getHeight() const
{
    return m_height;
}

///////////////////
///PeerInfoLayer///
///////////////////
PeerInfoLayer::PeerInfoLayer() : Drawable(), m_width(0.f), m_height(0.f) {}

void PeerInfoLayer::draw()
{
    //Update matrix
    Drawable::pushMatrix();
    Drawable::updateMatrix();

    m_header.draw();

    for (PeerInfoRow &player : m_players)
        player.draw();

    if (!m_spectators.empty())
    {
        m_spectatorHeader.draw();
        for (SpectatorBadge &spectator : m_spectators)
            spectator.draw();
    }

    Drawable::popMatrix();
}

void PeerInfoLayer::reset()
{
    m_players.clear();
    m_spectators.clear();
}

void PeerInfoLayer::setPeers(const std::vector<PeerData> &players,
                             const std::vector<PeerData> &spectators,
                             const Vector &viewSize,
                             TextureManager &textureManager,
                             FontManager &fontManager)
{
    const ScaledPixel sp(viewSize.x);

    m_width = viewSize.x * .5f;

    const RowLayout headerLayout = {m_width, sp(25.f), m_width * .05f,
                                    m_width * .2f, m_width * .9f};
    const RowLayout rowLayout = {headerLayout.width, sp(60.f),
                                 headerLayout.icons * .8f,
                                 headerLayout.usernames,
                                 headerLayout.pings};



    Font &headerFont = fontManager.getDefault(sp(16));
    Font &rowFont = fontManager.getDefault(sp(20));

    //Header information
    m_header = PeerInfoHeader(headerLayout, headerFont);

    //Player rows
    m_players.clear();
    for (const PeerData &data : players)
        m_players.emplace_back(rowLayout, data, textureManager, rowFont);

    //Spectator header
    m_spectatorHeader = SpectatorHeader(headerLayout.width,
                                        headerLayout.height,
                                        m_width * .05f,
                                        headerFont);
    //Spectator badges
    const float badgeHeight = sp(40.f); ///< Height of spectator badges

    m_spectators.clear();
    for (const PeerData &data : spectators)
        m_spectators.emplace_back(badgeHeight, sp(5.f),
                                  data, textureManager, rowFont);

    updatePositions(viewSize.x);
}

void PeerInfoLayer::updatePositions(float viewWidth)
{
    const float space = ScaledPixel{viewWidth}(6.f);
    float yOffset = m_header.getHeight() + space;

    //Player rows
    for (PeerInfoRow &player : m_players)
    {
        player.setYPosition(yOffset + space);
        yOffset += player.getHeight() + space;
    }

    //Spectator header
    m_spectatorHeader.setYPosition(yOffset + space * 2.f);
    yOffset += m_spectatorHeader.getHeight() + space * 4.f;

    //Spectator badges
    float xOffset = 0.f;
    float spectatorWidth = 0.f;

    for (SpectatorBadge &spectator : m_spectators)
    {
        if (xOffset + spectator.getWidth() > m_header.getWidth())
        {
            xOffset = 0.f;
            yOffset += spectator.getHeight() + space;
        }

        spectator.setPosition(xOffset, yOffset);

        if (spectatorWidth < xOffset + spectator.getWidth())
            spectatorWidth = xOffset + spectator.getWidth();

        xOffset += spectator.getWidth() + space;
    }

    //Update spectator header width
    m_spectatorHeader.setWidth(spectatorWidth);

    //Update peer info layer height
    m_height = m_spectators.empty() ? yOffset - space
                                    : yOffset + m_spectators.back().getHeight();
}

void PeerInfoLayer::updatePings(const std::vector<std::uint32_t> &pings)
{
    if (pings.size() != m_players.size())
    {
        Log::err("Cannot update pings: player numbers differ");
        return;
    }

    for (std::size_t i = 0; i < m_players.size(); i++)
        m_players[i].setPing(pings[i]);
}

float PeerInfoLayer::getWidth() const
{
    return m_width;
}

float PeerInfoLayer::getHeight() const
{
    return m_height;
}
