#include "qquicksuruunits.h"
#include "qquicksurustyle_p.h"

#include <QtCore/QtMath>
#include <QtGui/qfontinfo.h>
#include <QtGui/QGuiApplication>
#include <QtGui/private/qhighdpiscaling_p.h>
#include <QtGui/qpa/qplatformscreen.h>
#include <QtQuick/qquickitem.h>

#define SURU_FONT_SIZE_MULTIPLIER 0.875
#define ENV_GRID_UNIT_PX "GRID_UNIT_PX"
#define DEFAULT_GRID_UNIT_PX 8

static QQuickItem* findParentItem(QObject* object)
{
    QQuickItem* item = nullptr;
    QObject* obj = object;

    do {
        QQuickItem* tmpItem = qobject_cast<QQuickItem*>(obj);
        if (tmpItem) {
            item = tmpItem;
            obj = nullptr;
            break;
        }
        if (obj)
            obj = obj->parent();
    } while (obj);

    return item;
}

QQuickSuruUnits::QQuickSuruUnits(QObject *parent) :
    QObject(parent),
    m_window(nullptr),
    m_gridUnit(0)
{
    /* Attempt to find an associated QScreen for its pixel density. */
    QQuickItem* parentItem = findParentItem(this);
    if (parentItem) {
        connect(parentItem, &QQuickItem::windowChanged,
                this, &QQuickSuruUnits::handleWindowChanged);

        handleWindowChanged(parentItem->window());
    }
    
    if (m_gridUnit == 0) {
        /*
         * It's possible that either our parent item has no window yet, or
         * we're made inside e.g. QQuickSuruTheme. Fallback to the application-
         * wide primary screen for its pixel density.
         */

        calculateGridUnit(qGuiApp->primaryScreen());
    }

    if (m_gridUnit == 0)
        m_gridUnit = DEFAULT_GRID_UNIT_PX;

    setupFonts();
}

void QQuickSuruUnits::handleWindowChanged(QQuickWindow * window)
{
    if (m_window)
        disconnect(m_window, &QWindow::screenChanged,
                   this, &QQuickSuruUnits::calculateGridUnit);

    m_window = window;

    if (window) {
        connect(window, &QWindow::screenChanged,
                this, &QQuickSuruUnits::calculateGridUnit);

        calculateGridUnit(window->screen());
    }
}

void QQuickSuruUnits::calculateGridUnit(QScreen* screen)
{
    if (!screen) {
        /* Retain previously set grid unit */
        return;
    }

    float oldGridUnit = m_gridUnit;

    QPlatformScreen * platformScreen = screen->handle();
    qreal factor;

    /*
     * If QHighDpiScaling is active, we won't scale anything ourselves to not
     * collide with Qt's auto scaling.
     * It's possible that the activeness of QHighDpiScaling changes with the
     * screen, so check that every time we're called.
     */
    if (QHighDpiScaling::isActive()) {
        factor = 1;
    } else {
    /* Note: mimicking QHighDpiScaling. */
    #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
        QDpi platformBaseDpi = platformScreen->logicalBaseDpi();
        const QDpi platformLogicalDpi = QPlatformScreen::overrideDpi(platformScreen->logicalDpi());
        factor = qreal(platformLogicalDpi.first) / qreal(platformBaseDpi.first);
    #else
        /* Note: on qtubuntu, is the same value returned by
        * nativeResourceForScreen("scale", screen()) */
        factor = platformScreen->pixelDensity();
    #endif
    }

    m_gridUnit = DEFAULT_GRID_UNIT_PX * factor;

    if (oldGridUnit != 0 && !qFuzzyCompare(oldGridUnit, m_gridUnit)) {
        setupFonts();
        emit unitsChanged();
    }
}

void QQuickSuruUnits::setupFonts()
{
    m_headingOne.setPixelSize(dp(32) * SURU_FONT_SIZE_MULTIPLIER);
    m_headingOne.setWeight(QFont::Light);

    m_headingTwo.setPixelSize(dp(24) * SURU_FONT_SIZE_MULTIPLIER);
    m_headingTwo.setWeight(QFont::Light);

    m_headingThree.setPixelSize(dp(22) * SURU_FONT_SIZE_MULTIPLIER);
    m_headingThree.setWeight(QFont::Light);

    m_paragraph.setPixelSize(dp(16) * SURU_FONT_SIZE_MULTIPLIER);
    m_paragraph.setWeight(QFont::Light);

    m_small.setPixelSize(dp(14) * SURU_FONT_SIZE_MULTIPLIER);
    m_small.setWeight(QFont::Light);

    m_caption.setPixelSize(dp(14) * SURU_FONT_SIZE_MULTIPLIER);
    m_caption.setWeight(QFont::Light);
    m_caption.setItalic(true);

    m_codeBlock.setPixelSize(dp(16) * SURU_FONT_SIZE_MULTIPLIER);
    m_codeBlock.setWeight(QFont::Light);

    const QFont font(QStringLiteral("Ubuntu"));
    if (QFontInfo(font).family() == QStringLiteral("Ubuntu")) {
        const QString family = font.family();

       m_headingOne.setFamily(family);
       m_headingTwo.setFamily(family);
       m_headingThree.setFamily(family);
       m_paragraph.setFamily(family);
       m_small.setFamily(family);
       m_caption.setFamily(family);
    }

    const QFont monoFont(QStringLiteral("Ubuntu Mono"));
    if (QFontInfo(monoFont).family() == QStringLiteral("Ubuntu Mono")) {
        const QString family = monoFont.family();
        m_codeBlock.setFamily(family);
    }

    emit fontsChanged();
}

int QQuickSuruUnits::gu(qreal value) const
{
    return qRound(value * m_gridUnit);
}

int QQuickSuruUnits::dp(qreal value) const
{
    if (QHighDpiScaling::isActive()) {
        return value;
    }

    const qreal ratio = m_gridUnit / (qreal)DEFAULT_GRID_UNIT_PX;
    if (value <= 2.0) {
        // for values under 2dp, return only multiples of the value
        return qRound(value * qFloor(ratio));
    } else {
        return qRound(value * ratio);
    }
}

int QQuickSuruUnits::rem(qreal value) const
{
    return value * qreal(m_paragraph.pixelSize());
}

int QQuickSuruUnits::applyLabelTopPadding(const int previousBlockLevel = QQuickSuruStyle::Paragraph) const
{
    int prevBlockLevel = previousBlockLevel;

    if (prevBlockLevel < QQuickSuruStyle::HeadingOne || prevBlockLevel > QQuickSuruStyle::CodeBlock) {
        prevBlockLevel = QQuickSuruStyle::Paragraph;
    }

    QQuickSuruStyle *style = qobject_cast<QQuickSuruStyle*>(this->parent());
    if (style) {
        switch (style->textLevel()) {
        case QQuickSuruStyle::HeadingOne:
            return rem(2);
        case QQuickSuruStyle::HeadingTwo:
        case QQuickSuruStyle::HeadingThree:
            return rem(1.5);
        case QQuickSuruStyle::Paragraph:
            return (prevBlockLevel == QQuickSuruStyle::HeadingOne
                    || prevBlockLevel == QQuickSuruStyle::Paragraph) ? rem(1) : rem(0.5);
        case QQuickSuruStyle::Small:
        case QQuickSuruStyle::Caption:
            return rem(1);
        case QQuickSuruStyle::CodeBlock:
            return rem(1.25);
        default:    // Likely, we'll never get here.
            return 0;
        }
    }

    return 0;
}
