/*******************************************************************************
**
** SPDX-FileCopyrightText: Copyright 2023 Open Mobile Platform LLC <community@omp.ru>
** SPDX-License-Identifier: BSD-3-Clause
** This file is part of the OfficeViewer project.
**
** Redistribution and use in source and binary forms,
** with or without modification, are permitted provided
** that the following conditions are met:
**
** * Redistributions of source code must retain the above copyright notice,
**   this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright notice,
**   this list of conditions and the following disclaimer
**   in the documentation and/or other materials provided with the distribution.
** * Neither the name of the copyright holder nor the names of its contributors
**   may be used to endorse or promote products derived from this software
**   without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
** THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
** FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
** OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
** LOSS OF USE, DATA, OR PROFITS;
** OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE)
** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
** EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
*******************************************************************************/


#include <QTimer>
#include <QSGTexture>
#include <QSGSimpleTextureNode>
#include <QPropertyAnimation>
#include <QQuickWindow>
#include <QThreadPool>
#include <QtMath>
#include <QPainter>

#include "basepage.h"
#include "bitmaploadworker.h"

#include "backgroundpage.h"

BackgroundPage::BackgroundPage(QQuickItem *parent) : QQuickItem(parent),
    m_needUpdateImage(false),
    m_needClearImage(false),
    m_renderInProcess(false),
    m_renderable(false),
    m_forceRender(false),
    m_imageScale(1.0),
    m_pageScale(1.0),
    m_debugCancel(false),
    m_debugError(false)
{
    setFlag(QQuickItem::ItemHasContents, true);

    m_timer = new QTimer(this);
    connect(m_timer, &QTimer::timeout, this, &BackgroundPage::_loadBitmap);

    m_timer->setInterval(150);
    m_timer->setSingleShot(true);

    m_timerClear = new QTimer(this);
    connect(m_timerClear, &QTimer::timeout, this, &BackgroundPage::_clearImage);

    m_timerClear->setInterval(5000);
    m_timerClear->setSingleShot(true);

    connect(this, &BackgroundPage::renderableChanged, m_timer, static_cast<void(QTimer::*)(void)>(&QTimer::start));
    connect(this, &BackgroundPage::bitmapError, m_timer, static_cast<void(QTimer::*)(void)>(&QTimer::start));
    connect(this, &BackgroundPage::partReady, this, &BackgroundPage::update);

    m_animation = new QPropertyAnimation(this, "opacity");
    m_animation->setDuration(180);
    m_animation->setStartValue(0.42);
    m_animation->setEndValue(1.0);

    connect(m_animation, &QPropertyAnimation::finished, this, &BackgroundPage::animationEnded);
}

BackgroundPage::~BackgroundPage()
{
    m_timer->stop();
    m_timerClear->stop();
}

QSGNode *BackgroundPage::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
{
    if (height() <= 0 || width() <= 0)
        return nullptr;

    auto node = static_cast<QSGSimpleTextureNode *>(oldNode);

    if (node == nullptr) {
        node = new QSGSimpleTextureNode();
        node->setOwnsTexture(true);
    }

    if (node == nullptr)
        qFatal("Error: create node error.");

    if (m_needClearImage && node->texture()) {
        node->texture()->deleteLater();
        auto paper = QImage(1, 1, QImage::Format_RGBA8888);
        paper.fill(Qt::white);
        node->setTexture(window()->createTextureFromImage(paper, QQuickWindow::TextureHasAlphaChannel));
        m_needClearImage = false;
    }

    if (node->texture() == nullptr) {
        auto paper = QImage(1, 1, QImage::Format_RGBA8888);
        paper.fill(Qt::white);
        node->setTexture(window()->createTextureFromImage(paper, QQuickWindow::TextureHasAlphaChannel));

        if (!m_pagePart.isNull())
            m_needUpdateImage = true;
    }

    if (m_renderable && m_needUpdateImage) {
        if (m_pagePart.isNull()) {
            emit bitmapError();
        } else {
#ifdef DEBUG_MODE
            _debugDraw(&m_pagePart);
#endif
            node->texture()->deleteLater();
            node->setTexture(window()->createTextureFromImage(m_pagePart, QQuickWindow::TextureCanUseAtlas));
        }

        m_needUpdateImage = false;
    }

    if (m_renderable && !m_renderInProcess && m_pagePart.isNull())
        emit bitmapError();

    node->setRect(boundingRect());

    return node;
}

void BackgroundPage::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
{
    QQuickItem::geometryChanged(newGeometry, oldGeometry);
}

void BackgroundPage::setPageSource(QSharedPointer<BasePage> pageSource)
{
    if (!pageSource)
        return;

    m_pageSource = pageSource;
    m_timer->start();
}

bool BackgroundPage::renderable() const
{
    return m_renderable;
}

bool BackgroundPage::isBitmap() const
{
    return !m_pagePart.isNull();
}

void BackgroundPage::setImageScale(qreal imageScale)
{
    if (qFuzzyCompare(imageScale, m_imageScale))
        return;

    m_imageScale = imageScale;
}

void BackgroundPage::setPageScale(qreal pageScale)
{
    if (qFuzzyCompare(pageScale, m_pageScale))
        return;

    m_pageScale = pageScale;
}

void BackgroundPage::render(bool force)
{
    m_forceRender = force;
    m_timer->start();
}

void BackgroundPage::setRenderable(bool renderable)
{
    if (m_renderable == renderable)
        return;

    m_renderable = renderable;
    emit renderableChanged(m_renderable);

    if (!m_renderable)
        QMetaObject::invokeMethod(m_timerClear, "start");
    else
        QMetaObject::invokeMethod(m_timerClear, "stop");
}

void BackgroundPage::_loadBitmap()
{
    if (!m_pageSource)
        return;

    if (!m_renderable || m_renderInProcess) {
        if (m_loader) {
            m_loader->cancel();
            m_debugCancel = true;
            _updateDebugStatus();
        }
        return;
    }

    if (width() <= 0 || height() <= 0)
        return;

    auto pageSize = m_pageSource->originalSize();
    if (!pageSize.isFinished() || pageSize.isCanceled()) {
        m_timer->start();
        return;
    }

    auto pageScaleX = width() / pageSize.result().width();
    auto pageScaleY = height() / pageSize.result().height();

    if (!m_forceRender && qFloor(width()) == m_pagePart.width() && qFloor(height()) == m_pagePart.height())
        return;

    m_loader = new BitmapLoaderWorker(m_pageSource, pageScaleX, pageScaleY, 0, m_imageScale, QPointF(x(), y()), 1 / m_pageScale * 0.3);
    connect(this, &BackgroundPage::destroyed, m_loader.data(), &BitmapLoaderWorker::cancel);
    connect(this, &BackgroundPage::stopRender, m_loader.data(), &BitmapLoaderWorker::cancel, Qt::DirectConnection);
    connect(m_loader, &BitmapLoaderWorker::done, this, [this](QImage result) {
        if (m_loader != sender())
            return;

        m_renderInProcess = false;
        m_debugCancel = false;
        m_debugError = false;
        if (result.isNull()) {
            emit bitmapError();
            m_debugError = true;
            _updateDebugStatus();
        } else {
            auto needAnimation = m_pagePart.isNull();
            m_pagePart = result;
            m_needUpdateImage = true;
            emit partReady();

            if (needAnimation) {
                m_animation->setStartValue(m_pagePart.isNull() ? 0.42 : 0.95);
                m_animation->start();
            }
            update();
        }
    });

    m_pageSource->threadPool()->start(m_loader);
    m_renderInProcess = true;
    m_forceRender = false;

    _updateDebugStatus();
}

void BackgroundPage::_clearImage()
{
    if (!m_renderable) {
        m_pagePart = QImage();
        m_needClearImage = true;
        update();
    }
}

void BackgroundPage::_debugDraw(QImage *image)
{
    if (m_debugError)
        _debugDraw(image, Qt::red);
    else if (m_debugCancel)
        _debugDraw(image, Qt::black);
    else if (m_renderInProcess)
        _debugDraw(image, Qt::darkBlue);
    else
        _debugDraw(image, Qt::darkGreen);
}

void BackgroundPage::_debugDraw(QImage *image, QColor color)
{
    QPainter p(image);
    p.setPen(QPen(color));
    p.setFont(QFont(QStringLiteral("Times"), 12, QFont::Bold));
    p.drawText(image->rect(), Qt::AlignLeft | Qt::AlignTop, m_debugText);
    p.drawText(image->rect(), Qt::AlignHCenter | Qt::AlignTop, QString("page: %1").arg(m_pageSource->pageIndex()));
    p.drawRect(image->rect());
    QString stringRect("%1,%2 %3x%4");
    auto rectTile = QRect(qFloor(x()), qFloor(y()), qRound(width()), qRound(height()));
    stringRect = stringRect.arg(rectTile.x()).arg(rectTile.y()).arg(rectTile.width()).arg(rectTile.height());
    p.setFont(QFont(QStringLiteral("Times"), 12, QFont::Bold));
    p.drawText(image->rect(), Qt::AlignCenter, stringRect);
}

void BackgroundPage::_updateDebugStatus()
{
#ifdef DEBUG_MODE
    m_pagePart = QImage(qFloor(width()) + 5, qFloor(height()) + 5, QImage::Format_RGBA8888);
    m_pagePart.fill(Qt::white);
    m_needUpdateImage = true;
    update();
#endif
}

void BackgroundPage::setDebugText(const QString &newDebugText)
{
    m_debugText = newDebugText;
}
