#include "ArenaView.h"
#include "FactoryQueries.h"

#include <algorithm>
#include <cmath>
#include <functional>
#include <optional>

#include <QKeyEvent>
#include <QMouseEvent>
#include <QPainter>
#include <QPoint>

#include "ArenaSimulation.h"
#include "AttackBehavior.h"
#include "Building.h"
#include "BuildingSystem.h"
#include "EntityHitTest.h"
#include "EntitySelectionChangedEvent.h"
#include "EventManager.h"
#include "FacingComponent.h"
#include "FactionComponent.h"
#include "GameSpeedChangedEvent.h"
#include "HealthComponent.h"
#include "PositionComponent.h"
#include "RepairBehavior.h"
#include "SalvageScrapBehavior.h"
#include "DebrisSystem.h"
#include "SensorRangeComponent.h"
#include "ShipIdentityComponent.h"
#include "StationBodyComponent.h"
#include "DebrisComponent.h"
#include "WorldPrimitives.h"

namespace
{
}  // namespace


ArenaView::ArenaView(ArenaSimulation* sim, const VisualsConfig* visuals,
                     QWidget* parent)
    : QOpenGLWidget(parent)
    , m_sim(sim)
    , m_visuals(visuals)
    , m_gameSpeedMultiplier(1.0)
    , m_prevNonZeroSpeed(1.0)
    , m_rng(std::random_device{}())
    , m_finishedEmitted(false)
    , m_debugDraw(false)
{
    setFocusPolicy(Qt::StrongFocus);

    m_renderTimer = new QTimer(this);
    m_renderTimer->setInterval(16);
    connect(m_renderTimer, &QTimer::timeout, this, &ArenaView::onFrame);
    m_renderTimer->start();
    m_frameTimer.start();

    registerForEvent();
}

ArenaView::~ArenaView()
{
    unregisterForEvent();
}

void ArenaView::setGameSpeed(double multiplier)
{
    if (multiplier > 0.001)
    {
        m_prevNonZeroSpeed = multiplier;
    }
    m_gameSpeedMultiplier = multiplier;
    EventManager::getInstance()->sendEventImmediately(
        std::make_shared<GameSpeedChangedEvent>(multiplier));
}

double ArenaView::getGameSpeed() const
{
    return m_gameSpeedMultiplier;
}

void ArenaView::stopRendering()
{
    m_renderTimer->stop();
}

void ArenaView::togglePause()
{
    if (m_gameSpeedMultiplier < 0.001)
    {
        setGameSpeed(m_prevNonZeroSpeed);
    }
    else
    {
        setGameSpeed(0.0);
    }
}

void ArenaView::onFrame()
{
    const qint64 elapsed = m_frameTimer.restart();

    {
        const int ticks = m_tickDriver.advance(
            static_cast<double>(elapsed), m_gameSpeedMultiplier);
        for (int i = 0; i < ticks; ++i)
        {
            m_sim->tickOnce();
        }
    }

    // Emit fire events via EventManager
    {
        const std::vector<BeamFiredEvent> fires = m_sim->drainBeamFiredEvents();
        for (const BeamFiredEvent& fe : fires)
        {
            EventManager::getInstance()->sendEventImmediately(
                std::make_shared<BeamFiredEvent>(fe));
        }
    }

    // Expire old beams. Lifetime is measured in game ticks so beams stay
    // visible while the simulation is paused or slowed (REQ-SHP-FIRING-BEAM).
    {
        const Tick now = m_sim->getCurrentTick();
        std::vector<ActiveBeam> live;
        for (const ActiveBeam& b : m_activeBeams)
        {
            if (now - b.event.emittedAt < kBeamLifetimeTicks)
            {
                live.push_back(b);
            }
        }
        m_activeBeams = std::move(live);
    }

    if (m_sim->isFinished() && !m_finishedEmitted)
    {
        m_finishedEmitted = true;
    }

    update();
}

void ArenaView::handleEvent(std::shared_ptr<const BeamFiredEvent> event)
{
    float maxRadius = 0.125f;
    if (m_sim->getAdmin().isValid(event->target)
        && m_sim->getAdmin().hasAll<StationBodyComponent>(event->target))
    {
        const StationBodyComponent& sb = m_sim->getAdmin().get<StationBodyComponent>(event->target);
        const int shorter = std::min(sb.footprint.width(),
                                     sb.footprint.height());
        maxRadius = shorter / 2.0f;
    }
    else if (m_sim->getAdmin().isValid(event->target)
        && m_sim->getAdmin().hasAll<DebrisComponent>(event->target))
    {
        maxRadius = 0.1f;
    }

    std::uniform_real_distribution<float> angleDist(0.0f, 6.28318530f);
    std::uniform_real_distribution<float> radiusDist(0.0f, maxRadius);
    const float angle  = angleDist(m_rng);
    const float radius = radiusDist(m_rng);

    ActiveBeam beam;
    beam.event         = *event;
    beam.targetOffset  = QVector2D(radius * std::cos(angle),
                                   radius * std::sin(angle));
    m_activeBeams.push_back(beam);
}

void ArenaView::paintGL()
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, false);

    // One transform snapshot for the whole frame; every draw below reads the
    // viewport through it.
    const WorldCoordinates coordinates = getCoordinates();

    drawTiles(painter, coordinates);
    drawBuildings(painter, coordinates);
    drawStations(painter, coordinates);
    drawDebris(painter, coordinates);
    if (m_debugDraw)
    {
        drawDebugSensorRanges(painter, coordinates);
        drawDebugTargetLines(painter, coordinates);
    }
    drawShips(painter, coordinates);
    drawBeams(painter, coordinates);
}

// ---------------------------------------------------------------------------
// Coordinate helpers
// ---------------------------------------------------------------------------

WorldCoordinates ArenaView::getCoordinates() const
{
    // The arena is a fixed, fully visible world — unlike the game view it has no
    // scrolling, so the tile size comes from fitting the whole arena in the widget.
    const ArenaConfig& ac = m_sim->getArenaConfig();
    const int totalWidth = ac.playerBufferWidth_tiles
                         + ac.contestZoneWidth_tiles
                         + ac.enemyBufferWidth_tiles;
    return WorldCoordinates::fitToWorld(size(), totalWidth, ac.heightTiles);
}

std::optional<QVector2D> ArenaView::entityPosition(entt::entity entity) const
{
    if (!m_sim->getAdmin().isValid(entity) || !m_sim->getAdmin().hasAll<PositionComponent>(entity))
    {
        return std::nullopt;
    }
    return m_sim->getAdmin().get<PositionComponent>(entity).value;
}

void ArenaView::mousePressEvent(QMouseEvent* event)
{
    if (event->button() == Qt::LeftButton)
    {
        const QVector2D worldPos = getCoordinates().widgetToWorld(event->pos());
        entt::entity hit = entityAtWorldPos(m_sim->getAdmin(), worldPos);

        if (hit != entt::null)
        {
            m_selectedEntity = hit;
        }
        else
        {
            m_selectedEntity = std::nullopt;
        }

        // The arena is strictly single-select; emit a vector of size 0 or 1.
        std::vector<entt::entity> selection;
        if (m_selectedEntity.has_value())
        {
            selection.push_back(*m_selectedEntity);
        }
        EventManager::getInstance()->sendEventImmediately(
            std::make_shared<EntitySelectionChangedEvent>(selection));
    }

    QOpenGLWidget::mousePressEvent(event);
}

void ArenaView::keyPressEvent(QKeyEvent* event)
{
    if (event->key() == Qt::Key_F3)
    {
        m_debugDraw = !m_debugDraw;
        return;
    }
    QOpenGLWidget::keyPressEvent(event);
}

// ---------------------------------------------------------------------------
// Rendering
// ---------------------------------------------------------------------------

void ArenaView::drawTiles(QPainter& painter, const WorldCoordinates& coordinates)
{
    const ArenaConfig& ac = m_sim->getArenaConfig();
    const int totalWidth = ac.playerBufferWidth_tiles
                         + ac.contestZoneWidth_tiles
                         + ac.enemyBufferWidth_tiles;
    const int totalHeight = ac.heightTiles;

    painter.setPen(Qt::NoPen);
    for (int x = 0; x < totalWidth; ++x)
    {
        for (int y = 0; y < totalHeight; ++y)
        {
            painter.fillRect(coordinates.tileRect(QPoint(x, y)), m_visuals->space.fill);
        }
    }
}

void ArenaView::drawBuildings(QPainter& painter, const WorldCoordinates& coordinates)
{
    for (const Building& b : getAllBuildings(m_sim->getFactoryState()))
    {
        const std::map<BuildingType, BuildingVisuals>::const_iterator it =
            m_visuals->buildings.find(b.type);
        if (it == m_visuals->buildings.end()) { continue; }
        const BuildingVisuals& bv = it->second;

        painter.setPen(Qt::NoPen);
        for (const QPoint& cell : b.bodyCells)
        {
            painter.fillRect(coordinates.tileRect(cell), bv.fill);
        }

        const QPointF tl = coordinates.tileToWidget(b.anchor);
        const QRectF bboxRect(tl.x(), tl.y(),
            b.footprint.width()  * static_cast<qreal>(coordinates.getTilePx()),
            b.footprint.height() * static_cast<qreal>(coordinates.getTilePx()));

        painter.setPen(QPen(bv.outline, 1));
        painter.setBrush(Qt::NoBrush);
        painter.drawRect(bboxRect);

        if (!bv.glyph.isEmpty())
        {
            painter.setPen(bv.outline);
            painter.drawText(bboxRect, Qt::AlignCenter, bv.glyph);
        }
    }
}

void ArenaView::drawDebris(QPainter& painter, const WorldCoordinates& coordinates)
{
    for (const DebrisInfo& debris : getAllDebrisInfo(m_sim->getAdmin()))
    {
        drawDebrisMarker(painter, coordinates,
                         coordinates.worldToWidget(debris.position));
    }
}

void ArenaView::drawStations(QPainter& painter, const WorldCoordinates& coordinates)
{
    m_sim->getAdmin().forEach<StationBodyComponent, FactionComponent, HealthComponent>(
        [&](entt::entity e, const StationBodyComponent& sb, const FactionComponent& f, const HealthComponent& h)
        {
            const BuildingType visType = f.isEnemy
                ? BuildingType::EnemyDefenceStation
                : BuildingType::PlayerDefenceStation;
            const std::map<BuildingType, BuildingVisuals>::const_iterator it =
                m_visuals->buildings.find(visType);
            if (it == m_visuals->buildings.end()) { return; }
            const BuildingVisuals& bv = it->second;

            painter.setPen(Qt::NoPen);
            for (const QPoint& cell : sb.bodyCells)
            {
                painter.fillRect(coordinates.tileRect(cell), bv.fill);
            }

            const QPointF tl = coordinates.tileToWidget(sb.anchor);
            const QRectF bboxRect(tl.x(), tl.y(),
                sb.footprint.width()  * static_cast<qreal>(coordinates.getTilePx()),
                sb.footprint.height() * static_cast<qreal>(coordinates.getTilePx()));

            painter.setPen(QPen(bv.outline, 1));
            painter.setBrush(Qt::NoBrush);
            painter.drawRect(bboxRect);

            if (h.maxHp > 0.0f)
            {
                drawHealthBar(painter, coordinates, bboxRect.left(),
                              bboxRect.bottom() + 1.0, bboxRect.width(),
                              h.hp / h.maxHp, f.isEnemy);
            }

            if (m_selectedEntity.has_value() && *m_selectedEntity == e)
            {
                painter.setPen(QPen(QColor(255, 255, 0), 2));
                painter.setBrush(Qt::NoBrush);
                painter.drawRect(bboxRect.adjusted(-2, -2, 2, 2));
            }
        });
}

void ArenaView::drawShips(QPainter& painter, const WorldCoordinates& coordinates)
{
    const float forward = getShipForwardExtentPx(coordinates);

    m_sim->getAdmin().forEach<ShipIdentityComponent, PositionComponent, FacingComponent,
                           FactionComponent, HealthComponent>(
        [&](entt::entity e, const ShipIdentityComponent& si,
            const PositionComponent& pos, const FacingComponent& facing,
            const FactionComponent& fac, const HealthComponent& h)
        {
            const std::map<std::string, ShipVisuals>::const_iterator it =
                m_visuals->ships.find(si.schematicId);
            if (it == m_visuals->ships.end()) { return; }

            const QPointF center = coordinates.worldToWidget(pos.value);
            drawShipBody(painter, coordinates, center, facing.radians,
                         it->second.fill, it->second.outline);

            if (h.maxHp > 0.0f)
            {
                const qreal barW = static_cast<qreal>(forward) * 2.0;
                const qreal barX = center.x() - static_cast<qreal>(forward);
                const qreal barY = center.y() + static_cast<qreal>(forward) + 1.0;
                drawHealthBar(painter, coordinates, barX, barY, barW,
                              h.hp / h.maxHp, fac.isEnemy);
            }

            if (m_selectedEntity.has_value() && *m_selectedEntity == e)
            {
                const qreal radius = static_cast<qreal>(coordinates.getTilePx()) * 0.55;
                painter.setPen(QPen(QColor(255, 255, 0), 2));
                painter.setBrush(Qt::NoBrush);
                painter.drawEllipse(center, radius, radius);
            }
        });
}

void ArenaView::drawDebugSensorRanges(QPainter& painter,
                                      const WorldCoordinates& coordinates)
{
    m_sim->getAdmin().forEach<ShipIdentityComponent, PositionComponent, SensorRangeComponent>(
        [&](entt::entity /*e*/, const ShipIdentityComponent& si,
            const PositionComponent& pos, const SensorRangeComponent& sensor)
        {
            const std::map<std::string, ShipVisuals>::const_iterator it =
                m_visuals->ships.find(si.schematicId);
            if (it == m_visuals->ships.end()) { return; }

            drawSensorRange(painter, coordinates,
                            coordinates.worldToWidget(pos.value),
                            sensor.value_tiles, it->second.outline);
        });
}

void ArenaView::drawDebugTargetLines(QPainter& painter,
                                     const WorldCoordinates& coordinates)
{
    // Draw a thin translucent line from a ship to a target, colored by the ship's
    // team to match the per-side HQ/station colors used elsewhere in the arena
    // (team 1 player, team 2 enemy). Shared by the attack, repair and salvage lines.
    const std::function<void(bool, const QVector2D&, const QVector2D&)> drawTargetLine =
        [&](bool isEnemy, const QVector2D& from, const QVector2D& to)
    {
        const BuildingType visType = isEnemy
            ? BuildingType::EnemyDefenceStation
            : BuildingType::PlayerDefenceStation;
        const std::map<BuildingType, BuildingVisuals>::const_iterator it =
            m_visuals->buildings.find(visType);
        if (it == m_visuals->buildings.end()) { return; }

        QColor lineColor = it->second.fill;
        lineColor.setAlpha(128);
        painter.setPen(QPen(lineColor, 1));
        painter.drawLine(coordinates.worldToWidget(from),
                         coordinates.worldToWidget(to));
    };

    m_sim->getAdmin().forEach<ShipIdentityComponent, PositionComponent,
                           FactionComponent, AttackBehavior>(
        [&](entt::entity /*e*/, const ShipIdentityComponent& /*si*/,
            const PositionComponent& pos, const FactionComponent& fac,
            const AttackBehavior& attack)
        {
            if (!attack.currentTarget.has_value()) { return; }

            const std::optional<QVector2D> targetPos =
                entityPosition(*attack.currentTarget);
            if (!targetPos.has_value()) { return; }

            drawTargetLine(fac.isEnemy, pos.value, *targetPos);
        });

    m_sim->getAdmin().forEach<ShipIdentityComponent, PositionComponent,
                           FactionComponent, RepairBehavior>(
        [&](entt::entity /*e*/, const ShipIdentityComponent& /*si*/,
            const PositionComponent& pos, const FactionComponent& fac,
            const RepairBehavior& repair)
        {
            if (!repair.currentTarget.has_value()) { return; }

            const std::optional<QVector2D> targetPos =
                entityPosition(*repair.currentTarget);
            if (!targetPos.has_value()) { return; }

            drawTargetLine(fac.isEnemy, pos.value, *targetPos);
        });

    m_sim->getAdmin().forEach<ShipIdentityComponent, PositionComponent,
                           FactionComponent, SalvageScrapBehavior>(
        [&](entt::entity /*e*/, const ShipIdentityComponent& /*si*/,
            const PositionComponent& pos, const FactionComponent& fac,
            const SalvageScrapBehavior& salvage)
        {
            if (!salvage.debrisTarget.has_value()) { return; }

            drawTargetLine(fac.isEnemy, pos.value, *salvage.debrisTarget);
        });
}

void ArenaView::drawBeams(QPainter& painter, const WorldCoordinates& coordinates)
{
    for (const ActiveBeam& beam : m_activeBeams)
    {
        const std::optional<QVector2D> shooterPos = entityPosition(beam.event.shooter);
        const std::optional<QVector2D> targetPos  = entityPosition(beam.event.target);
        if (!shooterPos.has_value() || !targetPos.has_value()) { continue; }

        QColor color = m_visuals->beams.weaponColor;
        switch (beam.event.kind)
        {
            case BeamKind::Weapon:  color = m_visuals->beams.weaponColor;  break;
            case BeamKind::Repair:  color = m_visuals->beams.repairColor;  break;
            case BeamKind::Salvage: color = m_visuals->beams.salvageColor; break;
        }
        painter.setPen(QPen(color, m_visuals->beams.widthPx));
        painter.drawLine(coordinates.worldToWidget(*shooterPos),
                         coordinates.worldToWidget(*targetPos + beam.targetOffset));
    }
}
