/*******************************************************************************
**
** 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 "lodocumentprovider.h"
#include <documentcache.h>
#include <debug.h>
#include "loevent.h"
#define LOK_USE_UNSTABLE_API
#include <LibreOfficeKit/LibreOfficeKitEnums.h>

#include <QUrl>
#include <QRect>
#include <QImage>
#include <QTimerEvent>
#include <QTime>
#include <QThread>
#include <QtConcurrent/QtConcurrent>

#define RECONNECTION_PERIOD 500 // 500ms
#define RECONNECTION_ATTEMPTS 20 // 10 sec (20 * 500ms)
#define SPREADSHEET_WIDTH_INCREASE 1.0 //1.3
#define SPREADSHEET_HEIGHT_INCREASE 1.0 //1.1

namespace {
    const int DEFAULT_TILE_SIZE = 256;
    const qreal VIRTUAL_DEVICE_DPI = 96.0;
    const qreal TWIPS_PER_INCH = 1440.0;
}  // namespace

class MakeImageWorker : public QRunnable
{
public:
    MakeImageWorker(LoDocumentProvider *provider,
                    const LoEvent::PayloadNewTile msg,
                    QSharedMemory *mem, bool tailed = false)
        : m_provider(provider), m_msg(msg), m_mem(mem), m_tiled(tailed) {}

    void run() override
    {
        if (!m_mem)
            return;

        QImage image(m_msg.canvasSize.width(), m_msg.canvasSize.height(), QImage::Format_RGB32);

        if (m_mem->size() >= m_msg.canvasSize.width() * m_msg.canvasSize.height() * 4)
            memcpy(image.bits(), m_mem->data(), m_msg.canvasSize.width() * m_msg.canvasSize.height() * 4);
        else
            qCWarning(lcDocviewer()) << "Incorrectly sized shared memory" << m_mem->key();

        delete m_mem;

        qCDebug(lcDocviewerRender) << "Load rendered tile" << m_msg.rect << "page index:" << m_msg.pageIndex << "zoom:" << m_msg.zoom;

        if (m_tiled) {
            emit m_provider->tileRendered(m_msg.pageIndex + 1, image, m_msg.rect, m_msg.zoom);
        } else {
            emit m_provider->pageRendered(m_msg.pageIndex + 1, image, m_msg.zoom);
        }

        if (!m_provider->checksumFile().isEmpty())
            DocumentCache::instance()->insertImage(m_provider->checksumFile(), m_msg.pageIndex + 1, image, m_msg.rect, m_msg.zoom);
    }

private:
    LoDocumentProvider *m_provider;
    LoEvent::PayloadNewTile m_msg;
    QSharedMemory *m_mem;
    bool m_tiled;
};

LoDocumentProvider::LoDocumentProvider(QObject *parent)
    : BaseDocumentProvider(parent)
    , m_socket(new QLocalSocket(this))
    , m_socketReconnectTimer(0)
    , m_reconnectionAttempts(0)
    , m_shiftY(0.0)
    , m_shiftX(0.0)
{
    m_process.setProgram(QStringLiteral(WORKER_PATH));
    m_process.setProcessEnvironment(QProcessEnvironment::systemEnvironment());

    connect(m_socket, &QLocalSocket::disconnected, [this] {setLoaded(false); setError(ProviderProcessError);});
    connect(m_socket, &QLocalSocket::connected, [this] { qCDebug(lcDocviewer()) << "Connected socket" << m_socketName;});
    connect(m_socket, &QLocalSocket::readyRead, this, &LoDocumentProvider::newMessage, Qt::QueuedConnection);
    connect(m_socket, static_cast<void(QLocalSocket::*)(QLocalSocket::LocalSocketError)>(&QLocalSocket::error), this, &LoDocumentProvider::socketError);

    connect(DocumentCache::instance(), &DocumentCache::imageLoaded, this, &LoDocumentProvider::imageCacheLoaded);
    connect(DocumentCache::instance(), &DocumentCache::documentExported, this, &LoDocumentProvider::saveAsStatusChanged);
    connect(DocumentCache::instance(), &DocumentCache::checksumCalculated, this, &LoDocumentProvider::checksumCalculated);
    qRegisterMetaType<LoEvent::Message>("LoEvent::Message");
    m_socketName = makeSocketName();
}

LoDocumentProvider::~LoDocumentProvider()
{
    close();
}

void LoDocumentProvider::load(const QUrl& filePath)
{
    m_process.setArguments({filePath.toString(), m_socketName});
    m_process.start(QProcess::ReadOnly);
    qCDebug(lcDocviewer()) << "Load document" << filePath.toString() << "socket:" << m_socketName;

    if (!m_process.waitForStarted())
        qCWarning(lcDocviewer()) << "Failed to start libreoffice worker" << filePath;

    m_socket->connectToServer(m_socketName);
    DocumentCache::instance()->calculateChecksum(filePath.toString(QUrl::RemoveScheme));
}

void LoDocumentProvider::clearDocument()
{
    m_pageRectangles.clear();
    setPageCount(0);
    setLoaded(false);
}

QSizeF LoDocumentProvider::originalSize(int pageNumber) const
{
    return (pageNumber >= 1 && pageNumber <= m_pageRectangles.size())
               ? QSizeF(m_pageRectangles[pageNumber - 1].width(), m_pageRectangles[pageNumber - 1].height())
               : QSizeF();
}

QRect LoDocumentProvider::originalRect(int pageNumber) const
{
    return (pageNumber >= 1 && pageNumber <= m_pageRectangles.size())
               ? m_pageRectangles[pageNumber - 1]
               : QRect();
}

QSize LoDocumentProvider::pageSize(int pageNumber) const {
    return (pageNumber >= 1 && pageNumber <= m_pageRectangles.size())
            ? QSize(twipToPixel(m_pageRectangles[pageNumber - 1].width() , 1.0),
                    twipToPixel(m_pageRectangles[pageNumber - 1].height(), 1.0))
            : QSize();
}

QRect LoDocumentProvider::pageRect(int pageNumber) const {
    return (pageNumber >= 1 && pageNumber <= m_pageRectangles.size())
            ? twipRectToPixel(m_pageRectangles[pageNumber - 1], 1.0, m_shiftX, m_shiftY)
            : QRect();
}

bool LoDocumentProvider::supportsTiledRendering() const { return true; }

void LoDocumentProvider::renderPage(int pageNumber, const QSize& canvasSize, qreal zoom) {
    const QRect &twipTileRect = m_pageRectangles.at(pageNumber - 1);
    if (twipTileRect.isEmpty())
        return;

    if (!m_checksumFile.isEmpty() && DocumentCache::instance()->loadImage(m_checksumFile, pageNumber, canvasSize, QRect(), zoom))
        return;

    int pixelPageWidth  = 0;
    int pixelPageHeight = 0;

    if (canvasSize.isEmpty()) {
        pixelPageWidth  = twipToPixel(twipTileRect.width(), zoom);
        pixelPageHeight = twipToPixel(twipTileRect.height(), zoom);
    } else {
        pixelPageWidth  = canvasSize.width();
        pixelPageHeight = canvasSize.height();
    }

    m_currentPageSize.insert(pageNumber - 1, QSize(pixelPageWidth, pixelPageHeight));

    LoEvent::PayloadMakePage msgMakePage;
    msgMakePage.pageIndex = pageNumber - 1;
    msgMakePage.rect = twipTileRect;
    msgMakePage.zoom = zoom;
    msgMakePage.canvasSize = QSize(pixelPageWidth, pixelPageHeight);

    sendToProcess(LoEvent::Message(LoEvent::MakePage, msgMakePage));
}

void LoDocumentProvider::renderToImage(int pageNumber, const QRect& documentTileRect, const QSize& canvasSize, const QSize &pageSize, qreal zoom, bool forceUpdate) {
    Q_UNUSED(pageSize)

    if (documentTileRect.isEmpty())
        return;

    QRect twipTileRect = pixelRectToTwip(documentTileRect, zoom, shiftX(), shiftY(pageNumber - 1));

    int pixelPageWidth  = 0;
    int pixelPageHeight = 0;
    LoEvent::PayloadMakeTile msgMakeTile;
    msgMakeTile.renderRequest = LoEvent::RenderRequestFlags::Visible | LoEvent::RenderRequestFlags::Cached;

    if (!m_checksumFile.isEmpty() && !forceUpdate)
        if (DocumentCache::instance()->loadImage(m_checksumFile, pageNumber, canvasSize, documentTileRect, zoom)) {
            if (documentTileRect.size() != canvasSize) { // If request background page
                msgMakeTile.renderRequest = LoEvent::RenderRequestFlags::NoFlags;
            } else {
                return;
            }
        }

    if (canvasSize.isEmpty()) {
        pixelPageWidth  = twipToPixel(twipTileRect.width(), zoom);
        pixelPageHeight = twipToPixel(twipTileRect.height(), zoom);
    } else {
        pixelPageWidth  = canvasSize.width();
        pixelPageHeight = canvasSize.height();
    }

    if (forceUpdate)
        msgMakeTile.renderRequest = LoEvent::RenderRequestFlags::Visible;

    msgMakeTile.pageIndex = pageNumber - 1;
    msgMakeTile.rect = twipTileRect;
    msgMakeTile.canvasSize = QSize(pixelPageWidth, pixelPageHeight);
    msgMakeTile.zoom = zoom;

    sendToProcess(LoEvent::Message(LoEvent::MakeTile, msgMakeTile));
    qCDebug(lcDocviewerRender) << "Render tile" << documentTileRect  << "page index:" << msgMakeTile.pageIndex << "zoom" << zoom << "twipRect" << twipTileRect;
}

void LoDocumentProvider::resetQueue()
{
    LoEvent::Message msgClearTasks;
    msgClearTasks.header.commandType = LoEvent::ClearTasks;

    sendToProcess(msgClearTasks);
}

int LoDocumentProvider::tileSize() const
{
    return DEFAULT_TILE_SIZE;
}

void LoDocumentProvider::stopRenderImage(int pageNumber, const QRect &renderRect, qreal zoom)
{
    qreal shiftY = m_pageRectangles.at(pageNumber - 1).y();
    QRect twipTileRect = pixelRectToTwip(renderRect, zoom, m_shiftX, shiftY);
    LoEvent::PayloadStopRender msgStopRender;
    msgStopRender.pageIndex = pageNumber - 1;
    msgStopRender.rect = twipTileRect;
    msgStopRender.zoom = zoom;

    sendToProcess(LoEvent::Message(LoEvent::StopRender, msgStopRender));
    qCDebug(lcDocviewerRender) << "Stop render tile:" << renderRect << "page index:" << msgStopRender.pageIndex << zoom;
}

void LoDocumentProvider::sendUnoCommand(const QByteArray &command, const QByteArray &arguments, bool notifyWhenFinished)
{
    LoEvent::PayloadUnoEvent payloadUno;
    payloadUno.command = command;
    payloadUno.arguments = arguments;
    payloadUno.notifyWhenFinished = notifyWhenFinished;
    sendToProcess(LoEvent::Message(LoEvent::PostUnoCommand, payloadUno));
    qCDebug(lcDocviewerUno) << "Send UNO: " << command << "arguments:" << arguments << "notify:" << notifyWhenFinished;
}

void LoDocumentProvider::sendKeyEvent(int type, int charCode, int keyCode)
{
    LoEvent::PayloadKeyEvent keyEvent;
    keyEvent.type = type;
    keyEvent.charCode = charCode;
    keyEvent.keyCode = keyCode;
    sendToProcess(LoEvent::Message(LoEvent::PostKeyEvent, keyEvent));
}

void LoDocumentProvider::sendMouseEvent(int type, qreal x, qreal y, int count, int buttons, int modifier, int pageIndex, qreal zoom)
{
    LoEvent::PayloadMouseEvent mouseEvent;
    mouseEvent.type = static_cast<LibreOfficeKitMouseEventType>(type);
    mouseEvent.pageIndex = pageIndex;
    mouseEvent.x = pixelToTwip(x, zoom) + m_shiftX;
    mouseEvent.y = pixelToTwip(y, zoom) + shiftY(pageIndex);
    mouseEvent.count = count;
    mouseEvent.buttons = buttons;
    mouseEvent.modifier = modifier;
    sendToProcess(LoEvent::Message(LoEvent::PostMouseEvent, mouseEvent));
}

void LoDocumentProvider::sendSelectEvent(int type, qreal x, qreal y, int pageIndex, qreal zoom)
{
    LoEvent::PayloadSelectEvent selectEvent;
    selectEvent.type = static_cast<LibreOfficeKitSetTextSelectionType>(type);
    selectEvent.x = pixelToTwip(x, zoom) + m_shiftX;
    selectEvent.y = pixelToTwip(y, zoom) + shiftY(pageIndex);
    sendToProcess(LoEvent::Message(LoEvent::PostSelectEvent, selectEvent));
}

void LoDocumentProvider::setPartIndex(int partIndex)
{
    sendToProcess(LoEvent::Message(LoEvent::SetPart, partIndex));
}

void LoDocumentProvider::saveDocumentAs(const QString &path, const QString &format)
{
    if (DocumentCache::instance()->loadExportedDoc(checksumFile(), path, format))
        return;

    LoEvent::PayloadSaveAs payoadSaveAs;
    payoadSaveAs.path = path;
    payoadSaveAs.format = format;
    sendToProcess(LoEvent::Message(LoEvent::SaveAs, payoadSaveAs));
}

void LoDocumentProvider::getUnoCommandValues(const QByteArray &command)
{
    LoEvent::PayloadUnoEvent unoEvent;
    unoEvent.command = command;
    sendToProcess(LoEvent::Message(LoEvent::GetUnoCommandValues, unoEvent));
}

void LoDocumentProvider::getTextSelection(const QString &mimeType)
{
    sendToProcess(LoEvent::Message(LoEvent::GetTextSelection, mimeType));
}

void LoDocumentProvider::close()
{
    QThreadPool::globalInstance()->clear();

    if (!QThreadPool::globalInstance()->waitForDone(10))
        QCoreApplication::processEvents();

    m_socket->close();
    m_process.terminate();
    m_process.waitForFinished();
    emit closed();
}

void LoDocumentProvider::sendToProcess(const LoEvent::Message &command)
{
    if (thread() != QThread::currentThread()) {
        QMetaObject::invokeMethod(this, "sendToProcess", Q_ARG(const LoEvent::Message &, command));
        return;
    }

    m_processQueueLok.enqueue(command);

    if (m_socket && m_socket->state() == QLocalSocket::ConnectedState) {
        while (!m_processQueueLok.isEmpty()) {
            QDataStream out(m_socket);
            out << m_processQueueLok.takeFirst();
        }
    }
}

void LoDocumentProvider::newMessage()
{
    bool readedPacket = false;

    do {
        LoEvent::Message msg;
        readedPacket = false;

        static auto sizeHeader = sizeof (LoEvent::Header);
        if (m_socket->bytesAvailable() >= sizeHeader) {
            QByteArray headerBuf = m_socket->peek(sizeHeader);
            QDataStream inHeader(headerBuf);
            inHeader >> msg.header;

            if (m_socket->bytesAvailable() >= (msg.header.sizePayload + sizeHeader)) {
                readedPacket = true;
                QDataStream in(m_socket);
                in >> msg;
                QMetaObject::invokeMethod(this, "handleMessage", Q_ARG(LoEvent::Message, msg));
            }
        }
    } while (readedPacket && m_socket->bytesAvailable() >= sizeof (LoEvent::Header));
}

void LoDocumentProvider::handleMessage(const LoEvent::Message &msg)
{
    switch (msg.header.commandType) {
    case LoEvent::OfficeReady: {
        bool ready = false;
        QDataStream stream(msg.payload);
        stream >> ready;
        setInitialized(ready);
    } break;
    case LoEvent::LoadStatus: {
        bool loaded = false;
        QDataStream stream(msg.payload);
        stream >> loaded;
        setPageCount(m_pageRectangles.count());
        setLoaded(loaded);
    } break;
    case LoEvent::LoadProgress: {
        int progress = 0;
        QDataStream stream(msg.payload);
        stream >> progress;
        setLoadProgress(progress);
    } break;
    case LoEvent::DocumentType: {
        int type = 0;
        QDataStream stream(msg.payload);
        stream >> type;

        switch (type) {
        case LOK_DOCTYPE_TEXT:
            setDocumentType(Text);
            break;
        case LOK_DOCTYPE_OTHER:
            setDocumentType(Other);
            break;
        case LOK_DOCTYPE_DRAWING:
            setDocumentType(Drawing);
            break;
        case LOK_DOCTYPE_SPREADSHEET:
            setDocumentType(Spreadsheet);
            break;
        case LOK_DOCTYPE_PRESENTATION:
            setDocumentType(Presentation);
            break;
        }
    } break;
    case LoEvent::PageRectangles: {
        QList<QRect> rectangles;
        QDataStream stream(msg.payload);
        stream >> rectangles;
        m_pageRectangles = rectangles.toVector();

        if (m_pageRectangles.count()) {
            m_shiftX = m_pageRectangles[0].x();
            m_shiftY = m_pageRectangles[0].y();
        }
    } break;
    case LoEvent::PageCount: {
        int count = 0;
        QDataStream stream(msg.payload);
        stream >> count;
        m_pageRectangles.resize(count);
    } break;
    case LoEvent::PageSize: {
        LoEvent::PayloadPageSize msgPageSize;
        QDataStream stream(msg.payload);
        stream >> msgPageSize;

        qreal spreadsheetWidthFixer = documentType() == Spreadsheet ? SPREADSHEET_WIDTH_INCREASE : 1;
        qreal spreadsheetHeightFixer = documentType() == Spreadsheet ? SPREADSHEET_HEIGHT_INCREASE : 1;
        int y = msgPageSize.pageIndex <= 0 ? 0 : m_pageRectangles[msgPageSize.pageIndex - 1].y() + m_pageRectangles[msgPageSize.pageIndex - 1].height();
        m_pageRectangles[msgPageSize.pageIndex] = QRect(0, y, msgPageSize.size.width() * spreadsheetWidthFixer, msgPageSize.size.height() * spreadsheetHeightFixer);
    } break;
    case LoEvent::PageName: {
        LoEvent::PayloadPageName msgPageName;
        QDataStream stream(msg.payload);
        stream >> msgPageName;

        if (msgPageName.pageIndex == 0)
            m_pageNames.clear();

        m_pageNames.append(msgPageName.name);

        if (msgPageName.pageIndex == m_pageRectangles.count() - 1)
            setPageNames(m_pageNames);
    } break;
    case LoEvent::Error: {
        int errorType = NoError;
        QDataStream stream(msg.payload);
        stream >> errorType;
        setError(errorType);
    } break;
    case LoEvent::NewPage: {
        LoEvent::PayloadNewPage msgNewPage;
        QDataStream stream(msg.payload);
        stream >> msgNewPage;
        int pageIndex = msgNewPage.pageIndex;

        if (m_currentPageSize[pageIndex].width() == msgNewPage.size.width() && m_currentPageSize[pageIndex].height() == msgNewPage.size.height()) {
            QSharedMemory *mem = new QSharedMemory(msgNewPage.sharedMemKey);
            if (mem->attach()) {
                LoEvent::PayloadNewTile msgNewTailLocal;
                msgNewTailLocal.pageIndex = msgNewPage.pageIndex;
                msgNewTailLocal.canvasSize = msgNewPage.size;
                msgNewTailLocal.sharedMemKey = msgNewPage.sharedMemKey;

                QThreadPool::globalInstance()->start(new MakeImageWorker(this, msgNewTailLocal, mem));
            } else {
                LoEvent::PayloadMakePage msgMakePage;
                msgMakePage.pageIndex = msgNewPage.pageIndex;
                msgMakePage.canvasSize = msgNewPage.size;
                msgMakePage.rect = QRect(0, 0, msgNewPage.size.width(), msgNewPage.size.height());
                sendToProcess(LoEvent::Message(LoEvent::MakePage, msgMakePage));
                delete mem;
            }
        }
        sendToProcess(LoEvent::Message(LoEvent::RemoveSharedMem, msgNewPage.sharedMemKey));
    } break;
    case LoEvent::NewTile: {
        LoEvent::PayloadNewTile msgNewTile;
        QDataStream stream(msg.payload);
        stream >> msgNewTile;

        QSharedMemory *mem = new QSharedMemory(msgNewTile.sharedMemKey);
        if (mem->attach()) {
            QRect pixelTileRect = twipRectToPixel(msgNewTile.rect, msgNewTile.zoom, shiftX(), shiftY(msgNewTile.pageIndex));
            qCDebug(lcDocviewer()) << "Tile rendered:" << pixelTileRect << "page index:" << msgNewTile.pageIndex << msgNewTile.zoom;
            LoEvent::PayloadNewTile msgNewTileLocal = msgNewTile;
            msgNewTileLocal.rect = pixelTileRect;
            QThreadPool::globalInstance()->start(new MakeImageWorker(this, msgNewTileLocal, mem, true));
        } else {
            LoEvent::PayloadMakeTile msgMakeTail;
            msgMakeTail.pageIndex = msgNewTile.pageIndex;
            msgMakeTail.canvasSize = msgNewTile.canvasSize;
            msgMakeTail.rect = msgNewTile.rect;
            sendToProcess(LoEvent::Message(LoEvent::MakeTile, msgMakeTail));
            delete mem;
            qreal shiftY = m_pageRectangles.at(msgNewTile.pageIndex).y();
            QRect pixelTileRect = twipRectToPixel(msgNewTile.rect, msgNewTile.zoom, m_shiftX, shiftY);
            qCDebug(lcDocviewer()) << "Not make shared memory" << pixelTileRect << "page index:" << msgNewTile.pageIndex << msgNewTile.zoom;
        }
        sendToProcess(LoEvent::Message(LoEvent::RemoveSharedMem, msgNewTile.sharedMemKey));
    } break;
    case LoEvent::ProcessingStatus: {
        bool processing;
        QDataStream stream(msg.payload);
        stream >> processing;
        setProcessing(processing);
    } break;
    case LoEvent::Debug: {
        QString errorString;
        QDataStream stream(msg.payload);
        stream >> errorString;
        qCDebug(lcDocviewerBackend()) << errorString;
    } break;
    case LoEvent::CallbakEvent: {
        LoEvent::PayloadCallbackEvent payloadCallback;
        QDataStream stream(msg.payload);
        stream >> payloadCallback;
        libreofficeCallback(payloadCallback);
    } break;
    case LoEvent::CallbakUnoCommand: {
        LoEvent::PayloadUnoEvent unoEvent;
        QDataStream stream(msg.payload);
        stream >> unoEvent;
        unoCallback(unoEvent);
    } break;
    case LoEvent::CallbackTextSelection: {
        QString text;
        QDataStream stream(msg.payload);
        stream >> text;
        emit textSelectionAnswer(text);
    } break;
    case LoEvent::SaveAsStatus: {
        LoEvent::PayloadSaveAsStatus payloadSaveAsStatus;
        QDataStream stream(msg.payload);
        stream >> payloadSaveAsStatus;
        emit saveAsStatusChanged(payloadSaveAsStatus.path, payloadSaveAsStatus.format, payloadSaveAsStatus.status);

        if (payloadSaveAsStatus.status)
            DocumentCache::instance()->insertEportedDoc(checksumFile(), payloadSaveAsStatus.path, payloadSaveAsStatus.format);
    } break;
    default:
        break;
    }
}

void LoDocumentProvider::socketError(QLocalSocket::LocalSocketError error)
{
    if (error == QLocalSocket::ConnectionRefusedError || error == QLocalSocket::ServerNotFoundError) {
        m_reconnectionAttempts = RECONNECTION_ATTEMPTS;
        m_socketReconnectTimer = startTimer(RECONNECTION_PERIOD);
    } else {
        qWarning("Socket error: %d (%s)", error, m_socket->errorString().toUtf8().data());
        setError(ProviderProcessError);
    }
}

void LoDocumentProvider::imageCacheLoaded(const QString &filePath, int pageNumber, const QImage &image, const QRect &rectTile, qreal zoom)
{
    // TODO:
    Q_UNUSED(filePath)
    if (rectTile.isNull())
        emit pageRendered(pageNumber, image, zoom);
    else
        emit tileRendered(pageNumber, image, rectTile, zoom);

    qCDebug(lcDocviewerRender) << "Loaded tile cache" << rectTile << "page number:" << pageNumber << "zoom:" << zoom;
}

void LoDocumentProvider::checksumCalculated(const QString &filePath, const QByteArray &checksum)
{
    if (QUrl(path()).toString(QUrl::RemoveScheme) != filePath)
        return;

    m_checksumFile = checksum;
}

void LoDocumentProvider::libreofficeCallback(const LoEvent::PayloadCallbackEvent &event)
{
    if (event.type <= LOK_CALLBACK_FORM_FIELD_BUTTON)
        qCDebug(lcDocviewerUno) << "Call back type:" << lokCallbackTypeToString(event.type) << "payload:" << QString::fromLocal8Bit(event.payload);
    else
        qCWarning(lcDocviewer) << "Invalid type LOK callback" << int(event.type);
    emit libreofficeAnswer(event.type, event.payload);
}

void LoDocumentProvider::unoCallback(const LoEvent::PayloadUnoEvent &event)
{
    qCDebug(lcDocviewerUno) << "UNO Call back:" << event.command <<  QString::fromLocal8Bit(event.arguments);
    emit unoAnswer(event.command, event.arguments);
}

qreal LoDocumentProvider::shiftX() const
{
    return m_shiftX;
}

qreal LoDocumentProvider::shiftY(int pageIndex) const
{
    if (m_documentType == Spreadsheet || m_documentType == Presentation || m_documentType == Drawing)
        return 0;

    if (0 <= pageIndex && pageIndex < m_pageRectangles.count())
        return m_pageRectangles[pageIndex].y();
    return m_shiftY;
}

QByteArray LoDocumentProvider::checksumFile() const
{
    return m_checksumFile;
}

qreal LoDocumentProvider::pixelToTwip(qreal pixels, qreal zoom)
{
    return qRound64(pixels / VIRTUAL_DEVICE_DPI / zoom * TWIPS_PER_INCH);
}

quint64 LoDocumentProvider::twipToPixel(qreal twips, qreal zoom)
{
    return qRound64(twips  * VIRTUAL_DEVICE_DPI * zoom / TWIPS_PER_INCH);
}

qreal LoDocumentProvider::twipToPixelReal(qreal twips, qreal zoom)
{
    return twips  * VIRTUAL_DEVICE_DPI * zoom / TWIPS_PER_INCH;
}

QRect LoDocumentProvider::twipRectToPixel(const QRect &rect, qreal zoom, qreal shiftX, qreal shiftY)
{
    return QRect(twipToPixel(rect.x() - shiftX, zoom),
                 twipToPixel(rect.y() - shiftY, zoom),
                 twipToPixel(rect.width(), zoom),
                 twipToPixel(rect.height(), zoom));
}

QRect LoDocumentProvider::pixelRectToTwip(const QRect &rect, qreal zoom, qreal shiftX, qreal shiftY)
{
    return QRect(pixelToTwip(rect.x(), zoom) + shiftX,
                 pixelToTwip(rect.y(), zoom) + shiftY,
                 pixelToTwip(rect.width(), zoom),
                 pixelToTwip(rect.height(), zoom));
}

QString LoDocumentProvider::makeSocketName()
{
    return QUuid::createUuid().toString();
}

void LoDocumentProvider::timerEvent(QTimerEvent *event)
{
    if (event->timerId() == m_socketReconnectTimer) {
        killTimer(m_socketReconnectTimer);
        m_socketReconnectTimer = 0;
        if (m_reconnectionAttempts) {
            m_socket->connectToServer(m_socketName);
            m_reconnectionAttempts--;
        } else {
            qWarning("Socket error: %d (%s)", m_socket->error(), m_socket->errorString().toUtf8().data());
            setError(ProviderProcessError);
        }
    }
}
