#include "WorldCoordinates.h"

#include <algorithm>
#include <cmath>

namespace
{
    // A zero-height widget, a not-yet-shown widget, or a degenerate world size
    // would otherwise make every conversion divide by zero.
    float sanitizeTilePx(float tilePx)
    {
        return tilePx > 0.0f ? tilePx : 1.0f;
    }
}

WorldCoordinates WorldCoordinates::scrolling(QSize widgetSize_px,
                                             int worldHeight_tiles,
                                             float viewCenterX_tiles)
{
    float tilePx = 1.0f;
    if (worldHeight_tiles > 0)
    {
        tilePx = static_cast<float>(widgetSize_px.height())
               / static_cast<float>(worldHeight_tiles);
    }
    tilePx = sanitizeTilePx(tilePx);

    const float viewportWidthTiles = static_cast<float>(widgetSize_px.width()) / tilePx;
    return WorldCoordinates(tilePx, static_cast<float>(widgetSize_px.width()),
                            viewCenterX_tiles - viewportWidthTiles / 2.0f,
                            worldHeight_tiles);
}

WorldCoordinates WorldCoordinates::fitToWorld(QSize widgetSize_px,
                                              int worldWidth_tiles,
                                              int worldHeight_tiles)
{
    float tilePx = 1.0f;
    if (worldWidth_tiles > 0 && worldHeight_tiles > 0)
    {
        // The tighter of the two fits, so the whole world stays on screen.
        tilePx = std::min(
            static_cast<float>(widgetSize_px.height()) / static_cast<float>(worldHeight_tiles),
            static_cast<float>(widgetSize_px.width())  / static_cast<float>(worldWidth_tiles));
    }
    tilePx = sanitizeTilePx(tilePx);

    return WorldCoordinates(tilePx, static_cast<float>(widgetSize_px.width()),
                            0.0f, worldHeight_tiles);
}

WorldCoordinates::WorldCoordinates(float tilePx, float viewportWidth_px,
                                   float viewLeft_tiles, int worldHeight_tiles)
    : m_tilePx(tilePx)
    , m_viewportWidthTiles(viewportWidth_px / tilePx)
    , m_viewLeftTiles(viewLeft_tiles)
    , m_worldHeightTiles(worldHeight_tiles)
{
}

float WorldCoordinates::getTilePx() const
{
    return m_tilePx;
}

float WorldCoordinates::getViewportWidthTiles() const
{
    return m_viewportWidthTiles;
}

float WorldCoordinates::getViewLeftTiles() const
{
    return m_viewLeftTiles;
}

QPointF WorldCoordinates::worldToWidget(QVector2D worldPos) const
{
    return QPointF(
        static_cast<qreal>((worldPos.x() - m_viewLeftTiles) * m_tilePx),
        static_cast<qreal>(worldPos.y() * m_tilePx));
}

QPointF WorldCoordinates::tileToWidget(QPoint tile) const
{
    return worldToWidget(QVector2D(static_cast<float>(tile.x()),
                                   static_cast<float>(tile.y())));
}

QPoint WorldCoordinates::widgetToTile(QPoint widgetPoint) const
{
    const QVector2D world = widgetToWorld(widgetPoint);
    return QPoint(static_cast<int>(std::floor(world.x())),
                  static_cast<int>(std::floor(world.y())));
}

QVector2D WorldCoordinates::widgetToWorld(QPoint widgetPoint) const
{
    return QVector2D(
        static_cast<float>(widgetPoint.x()) / m_tilePx + m_viewLeftTiles,
        static_cast<float>(widgetPoint.y()) / m_tilePx);
}

QRectF WorldCoordinates::tileRect(QPoint tile) const
{
    const QPointF topLeft = tileToWidget(tile);
    return QRectF(topLeft.x(), topLeft.y(),
                  static_cast<qreal>(m_tilePx), static_cast<qreal>(m_tilePx));
}

QRect WorldCoordinates::getViewportRect() const
{
    const int left   = static_cast<int>(std::floor(m_viewLeftTiles)) - 1;
    const int top    = 0;
    const int right  = static_cast<int>(
        std::ceil(m_viewLeftTiles + m_viewportWidthTiles)) + 1;
    const int bottom = m_worldHeightTiles;
    return QRect(left, top, right - left, bottom - top);
}
