/*******************************************************************************
**
** 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 "basedocument.h"

#include "documentmapper.h"

#define SPACING_DEFAULT_VALUE 20

DocumentMapper::DocumentMapper(QQuickItem *parent) : QQuickItem(parent),
    m_orientation(Qt::Vertical),
    m_spacing(SPACING_DEFAULT_VALUE),
    m_contentHeight(0.0),
    m_contentWidth(0.0),
    m_lastPageActualSize(0.0)
{
    connect(this, &DocumentMapper::widthChanged, this, &DocumentMapper::_mapPages);
    connect(this, &DocumentMapper::heightChanged, this, &DocumentMapper::_mapPages);
    connect(parentItem(), &QQuickItem::widthChanged, this, &DocumentMapper::_updateSize);
    connect(parentItem(), &QQuickItem::heightChanged, this, &DocumentMapper::_updateSize);
}

QSGNode *DocumentMapper::updatePaintNode(QSGNode *, QQuickItem::UpdatePaintNodeData *)
{
    return nullptr;
}

QHash<int, PagePosition> DocumentMapper::actualMap() const
{
    return m_actualPagesCoordinates;
}

QPointF DocumentMapper::pagePosition(int pageIndex) const
{
    if (pageIndex < 0 || pageIndex > m_originalPagesMap.pages.size())
        return {  };

    auto pagePosition = m_originalPagesMap.pages.at(pageIndex);
    return { static_cast<qreal>(pagePosition.startX), static_cast<qreal>(pagePosition.startY)};
}

QPointF DocumentMapper::mapToPage(const QPointF &pos, qreal scale) const
{
    int pageIndex = pageIndexAtPosition(pos, scale);
    auto actualPos = actualPagePosition(pageIndex);
    QPointF posOnPage = QPointF(pos.x(), pos.y() - actualPos.start * scale);

    return posOnPage;
}

qreal DocumentMapper::contentHeight() const
{
    return m_contentHeight;
}

qreal DocumentMapper::contentWidth() const
{
    return m_contentWidth;
}

qreal DocumentMapper::spacing() const
{
    return m_spacing;
}

qreal DocumentMapper::lastPageActualSize() const
{
    return m_lastPageActualSize;
}

int DocumentMapper::pageIndexAtPosition(const QPointF &actualPosition, qreal scale) const
{
    for (auto it = m_actualPagesCoordinates.begin(); it != m_actualPagesCoordinates.end(); ++it) {
        if (m_orientation == Qt::Vertical && it->belong(actualPosition.y(), scale))
            return it.key();
        if (m_orientation == Qt::Horizontal && it->belong(actualPosition.x(), scale))
            return it.key();
    }
    return -1;
}

int DocumentMapper::pageIndexAtPosition(const QPointF &originalPosition) const
{
    if (m_orientation == Qt::Vertical)
        return m_originalPagesMap.pageOnY(originalPosition.y());
    if (m_orientation == Qt::Horizontal)
        return m_originalPagesMap.pageOnX(originalPosition.x());
    return -1;
}

void DocumentMapper::forceUpdate()
{
    _updateSize();
    _mapPages();
}

void DocumentMapper::setDocumentProvider(BaseDocument *documentProvider)
{
    if (documentProvider == nullptr)
        return;

    if (m_documentProvider == documentProvider)
        return;

    m_documentProvider = documentProvider;

    auto pageCount = m_documentProvider->count();
    m_originalPagesMap.pages.clear();
    m_originalPagesMap.pages.reserve(pageCount);

    if (pageCount <= 1)
        m_spacing = 0;
    else
        m_spacing = SPACING_DEFAULT_VALUE;

    qreal originalPageStartX = 0.0;
    qreal originalPageStartY = 0.0;
    for (int i = 0; i < pageCount; ++i) {
        auto pageSize = m_documentProvider->pageSize(i);

        m_originalPagesMap.pages.append({originalPageStartX,
                                 originalPageStartY,
                                 originalPageStartX + pageSize.width(),
                                 originalPageStartY + pageSize.height()});
        originalPageStartX += pageSize.width();
        originalPageStartY += pageSize.height();
    }

    _mapPages();
}

void DocumentMapper::setOrientation(Qt::Orientation orientation)
{
    if (m_orientation == orientation)
        return;

    m_orientation = orientation;
    _mapPages();
}

void DocumentMapper::_mapPages()
{
    if (width() <= 0 || height() <= 0)
        return;

    if (m_documentProvider == nullptr)
        return;

    int pageIndex = 0;
    auto contentHeight = -1.0f;
    auto contentWidth = -1.0f;
    PagePosition position;

    switch (m_orientation) {
    case Qt::Vertical : {
        for (auto &page : m_originalPagesMap.pages) {
            position.end += width() * page.heightToWidthRatio() + m_spacing;
            m_actualPagesCoordinates.insert(pageIndex, position);
            m_lastPageActualSize = position.end - position.start - m_spacing;
            position.start = position.end;
            ++pageIndex;
        }

        contentHeight = position.end;
        contentWidth = -1;
        break;
    }
    case Qt::Horizontal : {
        for (auto &page : m_originalPagesMap.pages) {
            auto fitHeight = qMin(width() * page.heightToWidthRatio(), height());
            position.end += fitHeight / page.heightToWidthRatio() + m_spacing;
            m_actualPagesCoordinates.insert(pageIndex, position);
            m_lastPageActualSize = position.end - position.start - m_spacing;
            position.start = position.end;
            ++pageIndex;
        }

        contentHeight = - 1;
        contentWidth = position.end;
        break;
    }
    }

    if (!qFuzzyCompare(double(contentHeight), double(m_contentHeight))) {
        m_contentHeight = contentHeight;
        emit contentHeightChanged(m_contentHeight);
    }

    if (!qFuzzyCompare(double(contentWidth), double(m_contentWidth))) {
        m_contentWidth = contentWidth;
        emit contentWidthChanged(m_contentWidth);
    }

    emit mapEnd();
}

void DocumentMapper::_updateSize()
{
    if (parentItem() == nullptr)
        return;

    setWidth(parentItem()->width());
    setHeight(parentItem()->height());
}

PagePosition DocumentMapper::actualPagePosition(int pageIndex) const
{
    if (!m_actualPagesCoordinates.contains(pageIndex))
        return {  };

    return m_actualPagesCoordinates.value(pageIndex);
}

PageGeometry DocumentMapper::originalPageGeometry(int pageIndex) const
{
    if (pageIndex < 0 || pageIndex > m_originalPagesMap.pages.size())
        return {  };

    return m_originalPagesMap.pages.at(pageIndex);
}
