#include "BuildModeController.h"

#include <memory>
#include <utility>

#include "BlueprintModeExitedEvent.h"
#include "BuilderModeExitedEvent.h"
#include "DeconstructModeChangedEvent.h"
#include "EventManager.h"

namespace
{

Rotation rotateClockwise(Rotation rotation)
{
    switch (rotation)
    {
    case Rotation::North: return Rotation::East;
    case Rotation::East:  return Rotation::South;
    case Rotation::South: return Rotation::West;
    case Rotation::West:  return Rotation::North;
    }
    return Rotation::East;
}

Rotation rotateCounterClockwise(Rotation rotation)
{
    switch (rotation)
    {
    case Rotation::North: return Rotation::West;
    case Rotation::East:  return Rotation::North;
    case Rotation::South: return Rotation::East;
    case Rotation::West:  return Rotation::South;
    }
    return Rotation::East;
}

}  // namespace

BuildMode BuildModeController::getMode() const
{
    return m_mode;
}

bool BuildModeController::isBuilderMode() const
{
    return m_mode == BuildMode::Builder;
}

bool BuildModeController::isBlueprintMode() const
{
    return m_mode == BuildMode::Blueprint;
}

bool BuildModeController::isDeconstructMode() const
{
    return m_mode == BuildMode::Deconstruct;
}

void BuildModeController::enterMode(BuildMode mode)
{
    if (m_mode == mode) { return; }

    // Leave the current mode properly, so its widget hears about it however the
    // player left. Each exit clears only its own state.
    switch (m_mode)
    {
    case BuildMode::Builder:
        m_draggingBelt = false;
        m_beltDragPath.clear();
        EventManager::getInstance()->sendEventImmediately(
            std::make_shared<BuilderModeExitedEvent>());
        break;
    case BuildMode::Blueprint:
        m_hoveredGhostIsTransfer = false;
        EventManager::getInstance()->sendEventImmediately(
            std::make_shared<BlueprintModeExitedEvent>());
        break;
    case BuildMode::Deconstruct:
        m_deconstructHoverBuildingId.reset();
        EventManager::getInstance()->sendEventImmediately(
            std::make_shared<DeconstructModeChangedEvent>(false));
        break;
    case BuildMode::None:
        break;
    }

    m_mode = mode;

    if (mode == BuildMode::Deconstruct)
    {
        EventManager::getInstance()->sendEventImmediately(
            std::make_shared<DeconstructModeChangedEvent>(true));
    }
}

void BuildModeController::enterBuilderMode(BuildingType type)
{
    enterMode(BuildMode::Builder);
    m_builderType     = type;
    m_ghostRotation   = Rotation::East;
    m_ghostValid      = false;
    m_tunnelGhostType = BuildingType::TunnelEntry;
    m_tunnelPartnerTile.reset();
}

void BuildModeController::enterBlueprintMode(Blueprint blueprint)
{
    enterMode(BuildMode::Blueprint);
    // The layout starts where the builder ghost last was, so switching from a
    // building to a blueprint does not jump the preview across the world.
    m_blueprintGhostTile = m_ghostTile;
    m_blueprint          = std::move(blueprint);
}

void BuildModeController::toggleDeconstructMode()
{
    enterMode(isDeconstructMode() ? BuildMode::None : BuildMode::Deconstruct);
}

void BuildModeController::exitBuilderMode()
{
    if (!isBuilderMode()) { return; }
    enterMode(BuildMode::None);
}

void BuildModeController::exitBlueprintMode()
{
    if (!isBlueprintMode()) { return; }
    enterMode(BuildMode::None);
}

void BuildModeController::exitCurrentMode()
{
    enterMode(BuildMode::None);
}

void BuildModeController::clearHover()
{
    m_ghostTile.reset();
    m_ghostValid      = false;
    m_tunnelGhostType = BuildingType::TunnelEntry;
    m_tunnelPartnerTile.reset();
    m_blueprintGhostTile.reset();
    m_hoveredGhostIsTransfer = false;
    m_deconstructHoverBuildingId.reset();
}

BuildingType BuildModeController::getBuilderType() const
{
    return m_builderType;
}

bool BuildModeController::isTunnelMode() const
{
    return isBuilderMode() && m_builderType == BuildingType::TunnelEntry;
}

BuildingType BuildModeController::getEffectiveBuilderType() const
{
    return isTunnelMode() ? m_tunnelGhostType : m_builderType;
}

const std::optional<QPoint>& BuildModeController::getGhostTile() const
{
    return m_ghostTile;
}

Rotation BuildModeController::getGhostRotation() const
{
    return m_ghostRotation;
}

bool BuildModeController::isGhostValid() const
{
    return m_ghostValid;
}

void BuildModeController::setGhostTile(QPoint tile)
{
    m_ghostTile = tile;
}

void BuildModeController::setGhostValidity(bool valid)
{
    m_ghostValid = valid;
}

void BuildModeController::rotateGhost(bool clockwise)
{
    m_ghostRotation = clockwise ? rotateClockwise(m_ghostRotation)
                                : rotateCounterClockwise(m_ghostRotation);
}

BuildingType BuildModeController::getTunnelGhostType() const
{
    return m_tunnelGhostType;
}

const std::optional<QPoint>& BuildModeController::getTunnelPartnerTile() const
{
    return m_tunnelPartnerTile;
}

void BuildModeController::setTunnelGhost(BuildingType resolvedType,
                                         std::optional<QPoint> partnerTile)
{
    m_tunnelGhostType   = resolvedType;
    m_tunnelPartnerTile = std::move(partnerTile);
}

bool BuildModeController::isDraggingBelt() const
{
    return m_draggingBelt;
}

QPoint BuildModeController::getBeltDragAnchor() const
{
    return m_beltDragAnchor;
}

const std::vector<BeltPathTile>& BuildModeController::getBeltDragPath() const
{
    return m_beltDragPath;
}

void BuildModeController::beginBeltDrag(QPoint anchorTile)
{
    m_draggingBelt   = true;
    m_beltDragAnchor = anchorTile;
}

void BuildModeController::setBeltDragPath(std::vector<BeltPathTile> path)
{
    m_beltDragPath = std::move(path);
}

void BuildModeController::cancelBeltDrag()
{
    m_draggingBelt = false;
    m_beltDragPath.clear();
}

const Blueprint& BuildModeController::getBlueprint() const
{
    return m_blueprint;
}

Blueprint& BuildModeController::getMutableBlueprint()
{
    return m_blueprint;
}

const std::optional<QPoint>& BuildModeController::getBlueprintGhostTile() const
{
    return m_blueprintGhostTile;
}

void BuildModeController::setBlueprintGhostTile(QPoint tile)
{
    m_blueprintGhostTile = tile;
}

bool BuildModeController::isHoveredGhostTransfer() const
{
    return m_hoveredGhostIsTransfer;
}

void BuildModeController::setHoveredGhostTransfer(bool transfer)
{
    m_hoveredGhostIsTransfer = transfer;
}

const std::optional<BuildingId>&
BuildModeController::getDeconstructHoverBuildingId() const
{
    return m_deconstructHoverBuildingId;
}

void BuildModeController::setDeconstructHoverBuildingId(std::optional<BuildingId> id)
{
    m_deconstructHoverBuildingId = std::move(id);
}
