/*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 "ClientLobby.hpp"
#include "Configuration/Configuration.hpp"
#include "Engines/Resources/TextureManager.hpp"
#include "Engines/Resources/FontManager.hpp"
#include "Engines/Sound/SoundEngine.hpp"
#include "Graphics/Viewport.hpp"
#include "Structs/Vector.hpp"

#include "Network/Serialization/PeerData.hpp"
#include "Structs/Character.hpp"
#include "Engines/Sound/PlaylistPlayer.hpp"
#include "Tools/ScaledPixel.hpp"
#include "Tools/Log.hpp"
#include "Tools/Format.hpp"
#include "Tools/I18n.hpp" ///< _ macro for i18n

ClientLobby::ClientLobby(Configuration &configuration,
                         const Viewport &viewport,
                         TextureManager &textureManager,
                         FontManager &fontManager,
                         SoundEngine &soundEngine) : DialogCallbackReceiver(),
m_configuration(&configuration),
m_callbackReceiver(nullptr),
m_header(_(ClientLobbyTitle), _(ClientLobbySubtitle),
         textureManager, fontManager, soundEngine, viewport.getTargetSize()),

m_characterChooser(Character::fetchNames(), configuration.getNetworkCharacter(),
                   textureManager, soundEngine, viewport.getTarget().width),

m_addressInput(TextInput::Mode::AlphaNumPunct, Configuration::MaxAddressLength,
    ScaledPixel{viewport.getTarget().width}(420.f),
    configuration.getNetworkAddress(), _(ClientLobbyAddress),
    fontManager.getDefault(ScaledPixel{viewport.getTarget().width}(24)),
    viewport.getTarget().width),

m_portInput(TextInput::Mode::Digits, 5,
    ScaledPixel{viewport.getTarget().width}(86.f),
    std::to_string(configuration.getNetworkPort()), _(ClientLobbyPort),
    fontManager.getDefault(ScaledPixel{viewport.getTarget().width}(24)),
    viewport.getTarget().width),

m_usernameInput(TextInput::Mode::Visible, PeerData::MaxUsernameLength,
    ScaledPixel{viewport.getTarget().width}(198.f),
    configuration.getNetworkUsername().get(), _(ClientLobbyUsername),
    fontManager.getDefault(ScaledPixel{viewport.getTarget().width}(24)),
    viewport.getTarget().width),

m_inputEnabler({&m_addressInput, &m_portInput, &m_usernameInput}, viewport),

m_connectionDialog(
    DialogSkin::getDefault(textureManager, fontManager, soundEngine),
    _(DialogCancel), viewport.getTargetSize()),

m_infoDialog(
    DialogSkin::getDefault(textureManager, fontManager, soundEngine),
    _(DialogOK), viewport.getTargetSize(), true),

m_notifier(viewport.getTargetSize().x),
m_addressPortGap(ScaledPixel{viewport.getTarget().width}(20.f))
{
    const Vector viewSize = viewport.getTargetSize();
    const ScaledPixel sp(viewSize.x);

    //Background and titles
    m_background.setTexture(textureManager.getTexture("gfx/ui/himeji.png"));
    m_background.setSubRect(Box(0.f, 0.f, 1920.f / 2048.f, 1080.f / 2048.f));
    m_background.setScale(viewSize.x / m_background.getWidth(),
                          viewSize.y / m_background.getHeight());

    //Character chooser and username input
    const float gap = m_addressPortGap;

    const float //Width and height of the address and connection section
        addrWidth = m_addressInput.getWidth() + gap + m_portInput.getWidth(),
        addrHeight = m_addressInput.getHeight() + gap + m_connect.getHeight();

    const float //Width and height of the character and username section
        charaWidth = m_characterChooser.getWidth(),
        charaHeight = m_characterChooser.getHeight() + gap +
                      m_usernameInput.getHeight();
    const float
        totalWidth = charaWidth + gap * 2.f + addrWidth,
        totalHeight = charaHeight;

    m_characterChooser.setPosition((viewSize.x - totalWidth) / 2.f,
                                   m_header.getHeight() + (viewSize.y -
                                   m_header.getHeight() - totalHeight) / 2.f);

    m_usernameInput.setPosition(m_characterChooser.getXPosition() +
        (m_characterChooser.getWidth() - m_usernameInput.getWidth()) / 2.f,
        m_characterChooser.getYPosition() + m_characterChooser.getHeight() +
        gap);

    //Server address, port text, and connect button
    m_addressInput.setPosition(
        m_characterChooser.getXPosition() + charaWidth + gap * 2.f,
        m_characterChooser.getYPosition() + (charaHeight - addrHeight) / 2.f);

    m_portInput.setPosition(
        m_addressInput.getXPosition() + m_addressInput.getWidth() + gap,
        m_addressInput.getYPosition());

    //...connect button
    const Sprite ui_default(textureManager.getTexture("gfx/ui/ui.png"),
        Box(266.f / 512.f, 0.f, 246.f / 512.f, 71.f / 512.f));
    const Sprite ui_hover(textureManager.getTexture("gfx/ui/ui.png"),
        Box(266.f / 512.f, 75.f / 512.f, 246.f / 512.f, 71.f / 512.f));

    SkinBox skin(sp(225.f), sp(50.f), ui_default, ui_hover);
    SkinSound sounds(soundEngine, "audio/ui/menuFocus.ogg",
                     "audio/ui/menuClick.ogg");

    m_connect = TextButton(_(ClientLobbyConnect), skin, sounds);
    m_connect.setTextFont(fontManager.getDisplay(sp(22)));
    m_connect.setTextColor(Color(255, 255, 255));
    m_connect.setPosition(
        m_addressInput.getXPosition(),
        m_addressInput.getYPosition() + m_addressInput.getHeight() + gap);

    //Manually handle the connection dialog cancelling
    m_connectionDialog.setCallbackReceiver(this);

    //Playlist notifier
    m_notifier.setFonts(fontManager.getDefault(sp(20)),
                        fontManager.getDefault(sp(14)));
    m_notifier.setPosition(viewSize.x - m_notifier.getWidth() - sp(20.f),
                           viewSize.y - m_notifier.getHeight() - sp(20.f));

    PlaylistPlayer::setNotifier(&m_notifier);
}

ClientLobby::~ClientLobby()
{
    PlaylistPlayer::setNotifier(nullptr);
}

void ClientLobby::setLobbyCallbackReceiver(ClientLobbyCallbackReceiver *receiver)
{
    m_callbackReceiver = receiver;
}

void ClientLobby::mouseMove(const Vector &viewCoords)
{
    m_header.backMove(viewCoords);
    m_infoDialog.mouseMove(viewCoords);
    m_connectionDialog.mouseMove(viewCoords);

    if (!m_connectionDialog.active())
    {
        m_addressInput.mouseMove(viewCoords);
        m_portInput.mouseMove(viewCoords);
        m_usernameInput.mouseMove(viewCoords);
        m_characterChooser.mouseMove(viewCoords);
        m_connect.mouseMove(viewCoords);
    }
}

void ClientLobby::mouseRelease(const Vector &viewCoords)
{
    if (m_header.backRelease(viewCoords) && m_callbackReceiver != nullptr)
        m_callbackReceiver->lobbyExitRequestedCallback();

    //Update the dialog before the action buttons to avoid
    //immediately cancelling a dialog before it was shown
    m_infoDialog.mouseRelease(viewCoords);
    m_connectionDialog.mouseRelease(viewCoords);

    if (!m_connectionDialog.active())
    {
        m_inputEnabler.mouseRelease(viewCoords);

        if (m_characterChooser.mouseRelease(viewCoords))
            m_configuration->setNetworkCharacter(
                m_characterChooser.getCharacter());

        if (m_connect.mouseRelease(viewCoords) &&
            !requestConnection(m_addressInput.getInput().get(),
                               m_portInput.getInput().get(),
                               m_characterChooser.getCharacter()))
        {
            m_infoDialog.setMessage(_(ClientLobbyUnresolvable));
            m_infoDialog.show();
        }
    }
}

void ClientLobby::keyEvent(const SDL_Event &event)
{
    if (!m_connectionDialog.active())
    {
        if (m_addressInput.keyEvent(event))
            m_configuration->setNetworkAddress(m_addressInput.getInput().get());
        else if (m_portInput.keyEvent(event))
            m_configuration->setNetworkPort(
                Address::readPort(m_addressInput.getInput().get()));
        else if (m_usernameInput.keyEvent(event))
            m_configuration->setNetworkUsername(m_usernameInput.getInput());
    }

    if (KeyboardKey::layoutDependent(SDLK_ESCAPE).keyPressed(event))
    {
        if (!m_connectionDialog.cancel() && !m_infoDialog.cancel() &&
            m_callbackReceiver != nullptr)
            m_callbackReceiver->lobbyExitRequestedCallback();
    }
}

void ClientLobby::update(float frameTime)
{
    m_characterChooser.update(frameTime);
    m_notifier.update();
}

void ClientLobby::draw()
{
    m_background.draw();
    m_header.draw();

    m_connect.draw();
    m_characterChooser.draw();

    m_addressInput.draw();

    m_portInput.setXPosition(m_addressInput.getXPosition() +
                             m_addressInput.getWidth() + m_addressPortGap);
    m_portInput.draw();

    m_usernameInput.draw();

    m_connectionDialog.draw();
    m_infoDialog.draw();

    m_notifier.draw();
}

void ClientLobby::dialogCancelledCallback()
{
    if (m_callbackReceiver != nullptr)
        m_callbackReceiver->lobbyConnectionCancelledCallback();
}

void ClientLobby::switchToConnecting()
{
    m_infoDialog.hide();

    m_connectionDialog.setMessage(_(ClientLobbyConnecting));
    m_connectionDialog.show();
}

void ClientLobby::switchToDisconnected(KickReason reason)
{
    m_connect.setFocus(false);
    if (!m_connectionDialog.active())
        return;

    m_connectionDialog.hide();
    if (reason != KickReason::ClientDecision)
    {
        m_infoDialog.setMessage(KickInfo::describe(reason));
        m_infoDialog.show();
    }
}

const std::string &ClientLobby::getCharacter() const
{
    return m_character;
}

bool ClientLobby::requestConnection(const std::string &address,
                                    const std::string &port,
                                    const std::string &character)
{
    if (m_callbackReceiver == nullptr)
        return false;

    Address serverAddress;
    if (serverAddress.setHost(address) &&
        serverAddress.setPort(port))
    {
        Log::info(Format::format("Connecting to {}:{}...", address, port));
        m_character = character;
        m_callbackReceiver->lobbyConnectionRequestedCallback(serverAddress);
        return true;
    }

    Log::err(Format::format("Could not parse address {}:{}", address, port));
    return false;
}
