//    Copyright (C) 2023-2024 Jakub Melka
//
//    This file is part of PDF4QT.
//
//    PDF4QT is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Lesser General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    with the written consent of the copyright owner, any later version.
//
//    PDF4QT is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU Lesser General Public License for more details.
//
//    You should have received a copy of the GNU Lesser General Public License
//    along with PDF4QT. If not, see <https://www.gnu.org/licenses/>.

#include "pdfpagecontenteditorprocessor.h"

namespace pdf
{

PDFPageContentEditorProcessor::PDFPageContentEditorProcessor(const PDFPage* page,
                                                             const PDFDocument* document,
                                                             const PDFFontCache* fontCache,
                                                             const PDFCMS* CMS,
                                                             const PDFOptionalContentActivity* optionalContentActivity,
                                                             QTransform pagePointToDevicePointMatrix,
                                                             const PDFMeshQualitySettings& meshQualitySettings) :
    BaseClass(page, document, fontCache, CMS, optionalContentActivity, pagePointToDevicePointMatrix, meshQualitySettings)
{
    m_clippingPaths.push(QPainterPath());

    if (auto fontDictionary = getFontDictionary())
    {
        m_content.setFontDictionary(*fontDictionary);
    }

    if (auto xObjectDictionary = getXObjectDictionary())
    {
        m_content.setXObjectDictionary(*xObjectDictionary);
    }
}

const PDFEditedPageContent& PDFPageContentEditorProcessor::getEditedPageContent() const
{
    return m_content;
}

PDFEditedPageContent PDFPageContentEditorProcessor::takeEditedPageContent()
{
    return std::move(m_content);
}

void PDFPageContentEditorProcessor::performInterceptInstruction(Operator currentOperator,
                                                                ProcessOrder processOrder,
                                                                const QByteArray& operatorAsText)
{
    BaseClass::performInterceptInstruction(currentOperator, processOrder, operatorAsText);

    if (processOrder == ProcessOrder::BeforeOperation)
    {
        if (currentOperator == Operator::TextBegin && !isTextProcessing())
        {
            m_contentElementText.reset(new PDFEditedPageContentElementText(*getGraphicState(), getGraphicState()->getCurrentTransformationMatrix()));
        }
    }
    else
    {
        if (currentOperator == Operator::TextEnd && !isTextProcessing())
        {
            if (m_contentElementText)
            {
                m_contentElementText->optimize();

                if (!m_contentElementText->isEmpty())
                {
                    m_contentElementText->setTextPath(std::move(m_textPath));
                    m_contentElementText->setItemsAsText(PDFEditedPageContentElementText::createItemsAsText(m_contentElementText->getState(), m_contentElementText->getItems()));
                    m_content.addContentElement(std::move(m_contentElementText));
                }
            }
            m_contentElementText.reset();
            m_textPath = QPainterPath();
        }
    }
}

void PDFPageContentEditorProcessor::performPathPainting(const QPainterPath& path, bool stroke, bool fill, bool text, Qt::FillRule fillRule)
{
    BaseClass::performPathPainting(path, stroke, fill, text, fillRule);

    if (path.isEmpty())
    {
        return;
    }

    if (text)
    {
        m_textPath.addPath(path);
    }
    else
    {
        m_content.addContentPath(*getGraphicState(), path, stroke, fill);
    }
}

void PDFPageContentEditorProcessor::performUpdateGraphicsState(const PDFPageContentProcessorState& state)
{
    BaseClass::performUpdateGraphicsState(state);

    if (isTextProcessing() && m_contentElementText)
    {
        PDFEditedPageContentElementText::Item item;
        item.isUpdateGraphicState = true;
        item.state = state;

        m_contentElementText->addItem(item);
    }
}

void PDFPageContentEditorProcessor::performProcessTextSequence(const TextSequence& textSequence, ProcessOrder order)
{
    BaseClass::performProcessTextSequence(textSequence, order);

    if (order == ProcessOrder::BeforeOperation)
    {
        PDFEditedPageContentElementText::Item item;
        item.isText = true;
        item.textSequence = textSequence;

        m_contentElementText->addItem(item);
    }
}

bool PDFPageContentEditorProcessor::performOriginalImagePainting(const PDFImage& image, const PDFStream* stream)
{
    BaseClass::performOriginalImagePainting(image, stream);

    PDFObject imageObject = PDFObject::createStream(std::make_shared<PDFStream>(*stream));
    m_content.addContentImage(*getGraphicState(), std::move(imageObject), QImage());

    return false;
}

void PDFPageContentEditorProcessor::performImagePainting(const QImage& image)
{
    BaseClass::performImagePainting(image);

    PDFEditedPageContentElement* backElement = m_content.getBackElement();
    Q_ASSERT(backElement);

    PDFEditedPageContentElementImage* imageElement = backElement->asImage();
    imageElement->setImage(image);
}

void PDFPageContentEditorProcessor::performSaveGraphicState(ProcessOrder order)
{
    BaseClass::performSaveGraphicState(order);

    if (order == ProcessOrder::BeforeOperation)
    {
        m_clippingPaths.push(m_clippingPaths.top());
    }
}

void PDFPageContentEditorProcessor::performRestoreGraphicState(ProcessOrder order)
{
    BaseClass::performRestoreGraphicState(order);

    if (order == ProcessOrder::AfterOperation)
    {
        m_clippingPaths.pop();
    }
}

void PDFPageContentEditorProcessor::performClipping(const QPainterPath& path, Qt::FillRule fillRule)
{
    BaseClass::performClipping(path, fillRule);

    if (m_clippingPaths.top().isEmpty())
    {
        m_clippingPaths.top() = path;
    }
    else
    {
        m_clippingPaths.top() = m_clippingPaths.top().intersected(path);
    }
}

bool PDFPageContentEditorProcessor::isContentKindSuppressed(ContentKind kind) const
{
    switch (kind)
    {
        case ContentKind::Shading:
        case ContentKind::Tiling:
            return true;

        default:
            break;
    }

    return false;
}

QString PDFEditedPageContent::getOperatorToString(PDFPageContentProcessor::Operator operatorValue)
{
    switch (operatorValue)
    {
        case pdf::PDFPageContentProcessor::Operator::SetLineWidth:
            return "set_line_width";
        case pdf::PDFPageContentProcessor::Operator::SetLineCap:
            return "set_line_cap";
        case pdf::PDFPageContentProcessor::Operator::SetLineJoin:
            return "set_line_join";
        case pdf::PDFPageContentProcessor::Operator::SetMitterLimit:
            return "set_mitter_limit";
        case pdf::PDFPageContentProcessor::Operator::SetLineDashPattern:
            return "set_line_dash_pattern";
        case pdf::PDFPageContentProcessor::Operator::SetRenderingIntent:
            return "set_rendering_intent";
        case pdf::PDFPageContentProcessor::Operator::SetFlatness:
            return "set_flatness";
        case pdf::PDFPageContentProcessor::Operator::SetGraphicState:
            return "set_graphic_state";
        case pdf::PDFPageContentProcessor::Operator::SaveGraphicState:
            return "save";
        case pdf::PDFPageContentProcessor::Operator::RestoreGraphicState:
            return "restore";
        case pdf::PDFPageContentProcessor::Operator::AdjustCurrentTransformationMatrix:
            return "set_cm";
        case pdf::PDFPageContentProcessor::Operator::MoveCurrentPoint:
            return "move_to";
        case pdf::PDFPageContentProcessor::Operator::LineTo:
            return "line_to";
        case pdf::PDFPageContentProcessor::Operator::Bezier123To:
            return "cubic123_to";
        case pdf::PDFPageContentProcessor::Operator::Bezier23To:
            return "cubic23_to";
        case pdf::PDFPageContentProcessor::Operator::Bezier13To:
            return "cubic13_to";
        case pdf::PDFPageContentProcessor::Operator::EndSubpath:
            return "close_path";
        case pdf::PDFPageContentProcessor::Operator::Rectangle:
            return "rect";
        case pdf::PDFPageContentProcessor::Operator::PathStroke:
            return "path_stroke";
        case pdf::PDFPageContentProcessor::Operator::PathCloseStroke:
            return "path_close_and_stroke";
        case pdf::PDFPageContentProcessor::Operator::PathFillWinding:
            return "path_fill_winding";
        case pdf::PDFPageContentProcessor::Operator::PathFillWinding2:
            return "path_fill_winding";
        case pdf::PDFPageContentProcessor::Operator::PathFillEvenOdd:
            return "path_fill_even_odd";
        case pdf::PDFPageContentProcessor::Operator::PathFillStrokeWinding:
            return "path_fill_stroke_winding";
        case pdf::PDFPageContentProcessor::Operator::PathFillStrokeEvenOdd:
            return "path_fill_stroke_even_odd";
        case pdf::PDFPageContentProcessor::Operator::PathCloseFillStrokeWinding:
            return "path_close_fill_stroke_winding";
        case pdf::PDFPageContentProcessor::Operator::PathCloseFillStrokeEvenOdd:
            return "path_close_fill_stroke_even_odd";
        case pdf::PDFPageContentProcessor::Operator::PathClear:
            return "path_clear";
        case pdf::PDFPageContentProcessor::Operator::ClipWinding:
            return "clip_winding";
        case pdf::PDFPageContentProcessor::Operator::ClipEvenOdd:
            return "clip_even_odd";
        case pdf::PDFPageContentProcessor::Operator::TextBegin:
            return "text_begin";
        case pdf::PDFPageContentProcessor::Operator::TextEnd:
            return "text_end";
        case pdf::PDFPageContentProcessor::Operator::TextSetCharacterSpacing:
            return "set_char_spacing";
        case pdf::PDFPageContentProcessor::Operator::TextSetWordSpacing:
            return "set_word_spacing";
        case pdf::PDFPageContentProcessor::Operator::TextSetHorizontalScale:
            return "set_hor_scale";
        case pdf::PDFPageContentProcessor::Operator::TextSetLeading:
            return "set_leading";
        case pdf::PDFPageContentProcessor::Operator::TextSetFontAndFontSize:
            return "set_font";
        case pdf::PDFPageContentProcessor::Operator::TextSetRenderMode:
            return "set_text_render_mode";
        case pdf::PDFPageContentProcessor::Operator::TextSetRise:
            return "set_text_rise";
        case pdf::PDFPageContentProcessor::Operator::TextMoveByOffset:
            return "text_move_by_offset";
        case pdf::PDFPageContentProcessor::Operator::TextSetLeadingAndMoveByOffset:
            return "text_set_leading_and_move_by_offset";
        case pdf::PDFPageContentProcessor::Operator::TextSetMatrix:
            return "text_set_matrix";
        case pdf::PDFPageContentProcessor::Operator::TextMoveByLeading:
            return "text_move_by_leading";
        case pdf::PDFPageContentProcessor::Operator::TextShowTextString:
            return "text_show_string";
        case pdf::PDFPageContentProcessor::Operator::TextShowTextIndividualSpacing:
            return "text_show_string_with_spacing";
        case pdf::PDFPageContentProcessor::Operator::TextNextLineShowText:
            return "text_next_line_and_show_text";
        case pdf::PDFPageContentProcessor::Operator::TextSetSpacingAndShowText:
            return "text_set_spacing_and_show_text";
        case pdf::PDFPageContentProcessor::Operator::Type3FontSetOffset:
            return "text_t3_set_offset";
        case pdf::PDFPageContentProcessor::Operator::Type3FontSetOffsetAndBB:
            return "text_t3_set_offset_and_bb";
        case pdf::PDFPageContentProcessor::Operator::ColorSetStrokingColorSpace:
            return "set_stroke_color_space";
        case pdf::PDFPageContentProcessor::Operator::ColorSetFillingColorSpace:
            return "set_filling_color_space";
        case pdf::PDFPageContentProcessor::Operator::ColorSetStrokingColor:
            return "set_stroke_color";
        case pdf::PDFPageContentProcessor::Operator::ColorSetStrokingColorN:
            return "set_stroke_color_n";
        case pdf::PDFPageContentProcessor::Operator::ColorSetFillingColor:
            return "set_filling_color";
        case pdf::PDFPageContentProcessor::Operator::ColorSetFillingColorN:
            return "set_filling_color_n";
        case pdf::PDFPageContentProcessor::Operator::ColorSetDeviceGrayStroking:
            return "set_stroke_gray_cs";
        case pdf::PDFPageContentProcessor::Operator::ColorSetDeviceGrayFilling:
            return "set_filling_gray_cs";
        case pdf::PDFPageContentProcessor::Operator::ColorSetDeviceRGBStroking:
            return "set_stroke_rgb_cs";
        case pdf::PDFPageContentProcessor::Operator::ColorSetDeviceRGBFilling:
            return "set_filling_rgb_cs";
        case pdf::PDFPageContentProcessor::Operator::ColorSetDeviceCMYKStroking:
            return "set_stroke_cmyk_cs";
        case pdf::PDFPageContentProcessor::Operator::ColorSetDeviceCMYKFilling:
            return "set_filling_cmyk_cs";
        case pdf::PDFPageContentProcessor::Operator::ShadingPaintShape:
            return "shading_paint";
        case pdf::PDFPageContentProcessor::Operator::InlineImageBegin:
            return "ib";
        case pdf::PDFPageContentProcessor::Operator::InlineImageData:
            return "id";
        case pdf::PDFPageContentProcessor::Operator::InlineImageEnd:
            return "ie";
        case pdf::PDFPageContentProcessor::Operator::PaintXObject:
            return "paint_object";
        case pdf::PDFPageContentProcessor::Operator::MarkedContentPoint:
            return "mc_point";
        case pdf::PDFPageContentProcessor::Operator::MarkedContentPointWithProperties:
            return "mc_point_prop";
        case pdf::PDFPageContentProcessor::Operator::MarkedContentBegin:
            return "mc_begin";
        case pdf::PDFPageContentProcessor::Operator::MarkedContentBeginWithProperties:
            return "mc_begin_prop";
        case pdf::PDFPageContentProcessor::Operator::MarkedContentEnd:
            return "mc_end";
        case pdf::PDFPageContentProcessor::Operator::CompatibilityBegin:
            return "compat_begin";
        case pdf::PDFPageContentProcessor::Operator::CompatibilityEnd:
            return "compat_end";

        default:
            break;
    }

    return QString();
}

QString PDFEditedPageContent::getOperandName(PDFPageContentProcessor::Operator operatorValue, int operandIndex)
{
    static const std::map<std::pair<PDFPageContentProcessor::Operator, int>, QString> operands =
    {
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::SetLineWidth, 0), "lineWidth" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::SetLineCap, 0), "lineCap" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::SetLineJoin, 0), "lineJoin" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::SetMitterLimit, 0), "mitterLimit" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::SetRenderingIntent, 0), "renderingIntent" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::SetFlatness, 0), "flatness" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::SetGraphicState, 0), "graphicState" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::AdjustCurrentTransformationMatrix, 0), "a" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::AdjustCurrentTransformationMatrix, 1), "b" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::AdjustCurrentTransformationMatrix, 2), "c" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::AdjustCurrentTransformationMatrix, 3), "d" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::AdjustCurrentTransformationMatrix, 4), "e" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::AdjustCurrentTransformationMatrix, 5), "f" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::MoveCurrentPoint, 0), "x" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::MoveCurrentPoint, 1), "y" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::LineTo, 0), "x" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::LineTo, 1), "y" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::Bezier123To, 0), "x1" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::Bezier123To, 1), "y1" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::Bezier123To, 2), "x2" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::Bezier123To, 3), "y2" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::Bezier123To, 4), "x3" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::Bezier123To, 5), "y3" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::Bezier23To, 0), "x2" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::Bezier23To, 1), "y2" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::Bezier23To, 2), "x3" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::Bezier23To, 3), "y3" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::Bezier13To, 0), "x1" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::Bezier13To, 1), "y1" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::Bezier13To, 2), "x3" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::Bezier13To, 3), "y3" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::Rectangle, 0), "x" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::Rectangle, 1), "y" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::Rectangle, 2), "width" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::Rectangle, 3), "height" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextSetCharacterSpacing, 0), "charSpacing" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextSetWordSpacing, 0), "wordSpacing" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextSetHorizontalScale, 0), "scale" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextSetLeading, 0), "leading" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextSetFontAndFontSize, 0), "font" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextSetFontAndFontSize, 1), "fontSize" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextSetRenderMode, 0), "renderMode" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextSetRise, 0), "rise" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextMoveByOffset, 0), "tx" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextMoveByOffset, 1), "ty" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextSetLeadingAndMoveByOffset, 0), "tx" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextSetLeadingAndMoveByOffset, 1), "ty" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextSetMatrix, 0), "a" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextSetMatrix, 1), "b" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextSetMatrix, 2), "c" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextSetMatrix, 3), "d" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextSetMatrix, 4), "e" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextSetMatrix, 5), "f" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextShowTextString, 0), "string" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextNextLineShowText, 0), "string" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextShowTextIndividualSpacing, 0), "wSpacing" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextShowTextIndividualSpacing, 1), "chSpacing" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextShowTextIndividualSpacing, 2), "string" },
        { std::make_pair(pdf::PDFPageContentProcessor::Operator::TextSetSpacingAndShowText, 0), "string" },
    };

    auto it = operands.find(std::make_pair(operatorValue, operandIndex));
    if (it != operands.cend())
    {
        return it->second;
    }

    return QString("op%1").arg(operandIndex);
}

void PDFEditedPageContent::addContentPath(PDFPageContentProcessorState state, QPainterPath path, bool strokePath, bool fillPath)
{
    QTransform transform = state.getCurrentTransformationMatrix();
    m_contentElements.emplace_back(new PDFEditedPageContentElementPath(std::move(state), std::move(path), strokePath, fillPath, transform));
}

void PDFEditedPageContent::addContentImage(PDFPageContentProcessorState state, PDFObject imageObject, QImage image)
{
    QTransform transform = state.getCurrentTransformationMatrix();
    m_contentElements.emplace_back(new PDFEditedPageContentElementImage(std::move(state), std::move(imageObject), std::move(image), transform));
}

void PDFEditedPageContent::addContentElement(std::unique_ptr<PDFEditedPageContentElement> element)
{
    m_contentElements.emplace_back(std::move(element));
}

PDFEditedPageContentElement* PDFEditedPageContent::getBackElement() const
{
    if (m_contentElements.empty())
    {
        return nullptr;
    }

    return m_contentElements.back().get();
}

PDFDictionary PDFEditedPageContent::getFontDictionary() const
{
    return m_fontDictionary;
}

void PDFEditedPageContent::setFontDictionary(const PDFDictionary& newFontDictionary)
{
    m_fontDictionary = newFontDictionary;
}

PDFDictionary PDFEditedPageContent::getXObjectDictionary() const
{
    return m_xobjectDictionary;
}

void PDFEditedPageContent::setXObjectDictionary(const PDFDictionary& newXobjectDictionary)
{
    m_xobjectDictionary = newXobjectDictionary;
}

PDFEditedPageContentElement::PDFEditedPageContentElement(PDFPageContentProcessorState state, QTransform transform) :
    m_state(std::move(state)),
    m_transform(transform)
{

}

const PDFPageContentProcessorState& PDFEditedPageContentElement::getState() const
{
    return m_state;
}

void PDFEditedPageContentElement::setState(const PDFPageContentProcessorState& newState)
{
    m_state = newState;
}

QTransform PDFEditedPageContentElement::getTransform() const
{
    return m_transform;
}

void PDFEditedPageContentElement::setTransform(const QTransform& newTransform)
{
    m_transform = newTransform;
}

PDFEditedPageContentElementPath::PDFEditedPageContentElementPath(PDFPageContentProcessorState state, QPainterPath path, bool strokePath, bool fillPath, QTransform transform) :
    PDFEditedPageContentElement(std::move(state), transform),
    m_path(std::move(path)),
    m_strokePath(strokePath),
    m_fillPath(fillPath)
{

}

PDFEditedPageContentElement::Type PDFEditedPageContentElementPath::getType() const
{
    return Type::Path;
}

PDFEditedPageContentElementPath* PDFEditedPageContentElementPath::clone() const
{
    return new PDFEditedPageContentElementPath(getState(), getPath(), getStrokePath(), getFillPath(), getTransform());
}

QRectF PDFEditedPageContentElementPath::getBoundingBox() const
{
    QPainterPath mappedPath = getState().getCurrentTransformationMatrix().map(m_path);
    return mappedPath.boundingRect();
}

QPainterPath PDFEditedPageContentElementPath::getPath() const
{
    return m_path;
}

void PDFEditedPageContentElementPath::setPath(QPainterPath newPath)
{
    m_path = newPath;
}

bool PDFEditedPageContentElementPath::getStrokePath() const
{
    return m_strokePath;
}

void PDFEditedPageContentElementPath::setStrokePath(bool newStrokePath)
{
    m_strokePath = newStrokePath;
}

bool PDFEditedPageContentElementPath::getFillPath() const
{
    return m_fillPath;
}

void PDFEditedPageContentElementPath::setFillPath(bool newFillPath)
{
    m_fillPath = newFillPath;
}

PDFEditedPageContentElementImage::PDFEditedPageContentElementImage(PDFPageContentProcessorState state, PDFObject imageObject, QImage image, QTransform transform) :
    PDFEditedPageContentElement(std::move(state), transform),
    m_imageObject(std::move(imageObject)),
    m_image(std::move(image))
{

}

PDFEditedPageContentElement::Type PDFEditedPageContentElementImage::getType() const
{
    return PDFEditedPageContentElement::Type::Image;
}

PDFEditedPageContentElementImage* PDFEditedPageContentElementImage::clone() const
{
    return new PDFEditedPageContentElementImage(getState(), getImageObject(), getImage(), getTransform());
}

QRectF PDFEditedPageContentElementImage::getBoundingBox() const
{
    return getTransform().mapRect(QRectF(0, 0, 1, 1));
}

PDFObject PDFEditedPageContentElementImage::getImageObject() const
{
    return m_imageObject;
}

void PDFEditedPageContentElementImage::setImageObject(const PDFObject& newImageObject)
{
    m_imageObject = newImageObject;
}

QImage PDFEditedPageContentElementImage::getImage() const
{
    return m_image;
}

void PDFEditedPageContentElementImage::setImage(const QImage& newImage)
{
    m_image = newImage;
}

PDFEditedPageContentElementText::PDFEditedPageContentElementText(PDFPageContentProcessorState state, QTransform transform) :
    PDFEditedPageContentElement(state, transform)
{

}

PDFEditedPageContentElementText::PDFEditedPageContentElementText(PDFPageContentProcessorState state,
                                                                 std::vector<Item> items,
                                                                 QPainterPath textPath,
                                                                 QTransform transform,
                                                                 QString itemsAsText) :
    PDFEditedPageContentElement(state, transform),
    m_items(std::move(items)),
    m_textPath(std::move(textPath)),
    m_itemsAsText(itemsAsText)
{

}

PDFEditedPageContentElement::Type PDFEditedPageContentElementText::getType() const
{
    return Type::Text;
}

PDFEditedPageContentElementText* PDFEditedPageContentElementText::clone() const
{
    return new PDFEditedPageContentElementText(getState(), getItems(), getTextPath(), getTransform(), getItemsAsText());
}

void PDFEditedPageContentElementText::addItem(Item item)
{
    m_items.emplace_back(std::move(item));
}

const std::vector<PDFEditedPageContentElementText::Item>& PDFEditedPageContentElementText::getItems() const
{
    return m_items;
}

void PDFEditedPageContentElementText::setItems(const std::vector<Item>& newItems)
{
    m_items = newItems;
}

QRectF PDFEditedPageContentElementText::getBoundingBox() const
{
    return getTransform().mapRect(m_textPath.boundingRect());
}

QPainterPath PDFEditedPageContentElementText::getTextPath() const
{
    return m_textPath;
}

void PDFEditedPageContentElementText::setTextPath(QPainterPath newTextPath)
{
    m_textPath = newTextPath;
}

QString PDFEditedPageContentElementText::createItemsAsText(const PDFPageContentProcessorState& initialState,
                                                           const std::vector<Item>& items)
{
    QString text;

    PDFPageContentProcessorState state = initialState;
    state.setStateFlags(PDFPageContentProcessorState::StateFlags());

    for (const Item& item : items)
    {
        if (item.isText)
        {
            for (const TextSequenceItem& textItem : item.textSequence.items)
            {
                if (textItem.isCharacter())
                {
                    if (!textItem.character.isNull())
                    {
                        text += QString(textItem.character).toHtmlEscaped();
                    }
                    else if (textItem.isAdvance())
                    {
                        text += QString("<space advance=\"%1\"/>").arg(textItem.advance);
                    }
                    else if (textItem.cid != 0)
                    {
                        text += QString("<character cid=\"%1\"/>").arg(textItem.cid);
                    }
                }
            }
        }
        else if (item.isUpdateGraphicState)
        {
            PDFPageContentProcessorState newState = state;
            newState.setStateFlags(PDFPageContentProcessorState::StateFlags());

            newState.setState(item.state);
            PDFPageContentProcessorState::StateFlags flags = newState.getStateFlags();

            if (flags.testFlag(PDFPageContentProcessorState::StateTextRenderingMode))
            {
                text += QString("<tr v=\"%1\"/>").arg(int(newState.getTextRenderingMode()));
            }

            if (flags.testFlag(PDFPageContentProcessorState::StateTextRise))
            {
                text += QString("<ts v=\"%1\"/>").arg(newState.getTextRise());
            }

            if (flags.testFlag(PDFPageContentProcessorState::StateTextCharacterSpacing))
            {
                text += QString("<tc v=\"%1\"/>").arg(newState.getTextCharacterSpacing());
            }

            if (flags.testFlag(PDFPageContentProcessorState::StateTextWordSpacing))
            {
                text += QString("<tw v=\"%1\"/>").arg(newState.getTextWordSpacing());
            }

            if (flags.testFlag(PDFPageContentProcessorState::StateTextLeading))
            {
                text += QString("<tl v=\"%1\"/>").arg(newState.getTextLeading());
            }

            if (flags.testFlag(PDFPageContentProcessorState::StateTextHorizontalScaling))
            {
                text += QString("<tz v=\"%1\"/>").arg(newState.getTextHorizontalScaling());
            }

            if (flags.testFlag(PDFPageContentProcessorState::StateTextKnockout))
            {
                text += QString("<tk v=\"%1\"/>").arg(newState.getTextKnockout());
            }

            if (flags.testFlag(PDFPageContentProcessorState::StateTextFont) ||
                flags.testFlag(PDFPageContentProcessorState::StateTextFontSize))
            {
                text += QString("<tf font=\"%1\" size=\"%2\"/>").arg(newState.getTextFont()->getFontId()).arg(newState.getTextFontSize());
            }

            if (flags.testFlag(PDFPageContentProcessorState::StateTextMatrix))
            {
                QTransform transform = newState.getTextMatrix();

                qreal x = transform.dx();
                qreal y = transform.dy();

                if (transform.isTranslating())
                {
                    text += QString("<tpos x=\"%1\" y=\"%2\"/>").arg(x).arg(y);
                }
                else
                {
                    text += QString("<tmatrix m11=\"%1\" m12=\"%2\" m21=\"%3\" m22=\"%4\" x=\"%5\" y=\"%6\"/>").arg(transform.m11()).arg(transform.m12()).arg(transform.m21()).arg(transform.m22()).arg(x).arg(y);
                }
            }

            state = newState;
            state.setStateFlags(PDFPageContentProcessorState::StateFlags());
        }
    }

    return text;
}

QString PDFEditedPageContentElementText::getItemsAsText() const
{
    return m_itemsAsText;
}

void PDFEditedPageContentElementText::setItemsAsText(const QString& newItemsAsText)
{
    m_itemsAsText = newItemsAsText;
}

void PDFEditedPageContentElementText::optimize()
{
    while (!m_items.empty() && !m_items.back().isText)
    {
        m_items.pop_back();
    }
}


}   // namespace pdf