#include "WorldCamera.h"

#include <algorithm>

namespace
{
    // Linearly blend from valueAt0 (for x <= x0) to valueAt1 (for x >= x1), clamped
    // outside [x0, x1]. A zero- or negative-width band collapses to a hard step at x1.
    float lerpClamped(float valueAt0, float valueAt1, float x0, float x1, float x)
    {
        if (x1 <= x0) { return x < x1 ? valueAt0 : valueAt1; }
        const float t = std::max(0.0f, std::min(1.0f, (x - x0) / (x1 - x0)));
        return valueAt0 + (valueAt1 - valueAt0) * t;
    }
}

WorldCamera::WorldCamera(const WorldScroll& scroll, const WorldRegions& regions)
    : m_scroll(&scroll)
    , m_regions(&regions)
    , m_viewCenterXTiles(0.0f)
{
}

bool WorldCamera::advance(PanDirection direction, qint64 elapsedMs,
                          ScrollBounds bounds)
{
    const float before = m_viewCenterXTiles;

    if (direction != PanDirection::None)
    {
        const float distance =
            getPanSpeedTilesPerSecondAt(m_viewCenterXTiles, bounds.rightTiles)
            * static_cast<float>(elapsedMs) / 1000.0f;
        m_viewCenterXTiles += (direction == PanDirection::Left) ? -distance : distance;
    }

    m_viewCenterXTiles = std::max(bounds.leftTiles,
                                  std::min(m_viewCenterXTiles, bounds.rightTiles));
    return m_viewCenterXTiles != before;
}

float WorldCamera::getViewCenterXTiles() const
{
    return m_viewCenterXTiles;
}

void WorldCamera::reset()
{
    m_viewCenterXTiles = 0.0f;
}

float WorldCamera::getPanSpeedTilesPerSecondAt(float viewCenterXTiles,
                                               float contestZoneRightEdgeTiles) const
{
    // Slow near the asteroid/player buffer, fast across the contest zone, with a
    // linear ramp straddling each contest-zone boundary (REQ-UI-SCROLL-SPEED). The
    // contest zone spans from the player buffer's right edge to the enemy stations,
    // the latter tracked live so the ramp follows the front line as it is pushed.
    const float slow = static_cast<float>(m_scroll->panSpeedSlow_tps);
    const float fast = static_cast<float>(m_scroll->panSpeedFast_tps);
    const float half = static_cast<float>(m_scroll->panRampBandWidth_tiles) / 2.0f;
    const float leftEdge  = static_cast<float>(m_regions->playerBufferWidth_tiles);
    const float rightEdge = contestZoneRightEdgeTiles;

    // Rising ramp at the left boundary (slow -> fast) and falling ramp at the right
    // boundary (fast -> slow); their minimum yields flat-slow outside, flat-fast in
    // the middle, and — if the bands overlap in a narrow contest zone — a single peak
    // below the fast speed where the two ramps cross.
    const float leftRamp  = lerpClamped(slow, fast, leftEdge  - half, leftEdge  + half, viewCenterXTiles);
    const float rightRamp = lerpClamped(fast, slow, rightEdge - half, rightEdge + half, viewCenterXTiles);
    return std::min(leftRamp, rightRamp);
}
