/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 */

#include <CommonStylePreviewRenderer.hxx>

#include <sfx2/objsh.hxx>
#include <svl/style.hxx>
#include <svl/itemset.hxx>
#include <svl/itempool.hxx>
#include <vcl/metric.hxx>
#include <vcl/outdev.hxx>

#include <com/sun/star/drawing/FillStyle.hpp>
#include <svx/xdef.hxx>
#include <svx/xfillit0.hxx>
#include <svx/xflclit.hxx>
#include <editeng/brushitem.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/charreliefitem.hxx>
#include <editeng/contouritem.hxx>
#include <editeng/colritem.hxx>
#include <editeng/crossedoutitem.hxx>
#include <editeng/editeng.hxx>
#include <editeng/emphasismarkitem.hxx>
#include <editeng/postitem.hxx>
#include <editeng/shdditem.hxx>
#include <editeng/udlnitem.hxx>
#include <editeng/wghtitem.hxx>
#include <editeng/svxfont.hxx>
#include <editeng/cmapitem.hxx>

#include <editeng/editids.hrc>

using namespace css;

namespace svx
{

CommonStylePreviewRenderer::CommonStylePreviewRenderer(
                                const SfxObjectShell& rShell, OutputDevice& rOutputDev,
                                SfxStyleSheetBase* pStyle, tools::Long nMaxHeight)
    : StylePreviewRenderer(rShell, rOutputDev, pStyle, nMaxHeight)
    , maFontColor(COL_AUTO)
    , maHighlightColor(COL_AUTO)
    , maBackgroundColor(COL_AUTO)
    , mnHeight(0)
    , mnBaseLine(0)
    , maStyleName(mpStyle->GetName())
{
}

CommonStylePreviewRenderer::~CommonStylePreviewRenderer()
{}

static bool GetWhich(const SfxItemSet& rSet, sal_uInt16 nSlot, sal_uInt16& rWhich)
{
    rWhich = rSet.GetPool()->GetWhichIDFromSlotID(nSlot);
    return rSet.GetItemState(rWhich) >= SfxItemState::DEFAULT;
}

static bool SetFont(const SfxItemSet& rSet, sal_uInt16 nSlot, SvxFont& rFont)
{
    sal_uInt16 nWhich;
    if (GetWhich(rSet, nSlot, nWhich))
    {
        const auto& rFontItem = static_cast<const SvxFontItem&>(rSet.Get(nWhich));
        rFont.SetFamily(rFontItem.GetFamily());
        rFont.SetFamilyName(rFontItem.GetFamilyName());
        rFont.SetPitch(rFontItem.GetPitch());
        rFont.SetCharSet(rFontItem.GetCharSet());
        rFont.SetStyleName(rFontItem.GetStyleName());
        rFont.SetAlignment(ALIGN_BASELINE);
        return true;
    }
    return false;
}

bool CommonStylePreviewRenderer::SetFontSize(const SfxItemSet& rSet, sal_uInt16 nSlot, SvxFont& rFont)
{
    sal_uInt16 nWhich;
    if (GetWhich(rSet, nSlot, nWhich))
    {
        const auto& rFontHeightItem = static_cast<const SvxFontHeightItem&>(rSet.Get(nWhich));
        Size aFontSize(0, rFontHeightItem.GetHeight());
        aFontSize = mrOutputDev.LogicToPixel(aFontSize, MapMode(mrShell.GetMapUnit()));
        rFont.SetFontSize(aFontSize);
        mrOutputDev.SetFont(rFont);
        FontMetric aMetric(mrOutputDev.GetFontMetric());
        return true;
    }
    return false;
}

bool CommonStylePreviewRenderer::recalculate()
{
    m_oFont.reset();
    m_oCJKFont.reset();
    m_oCTLFont.reset();

    std::optional<SfxItemSet> pItemSet(mpStyle->GetItemSetForPreview());

    if (!pItemSet) return false;

    SvxFont aFont;
    SvxFont aCJKFont;
    SvxFont aCTLFont;

    const SfxPoolItem* pItem;

    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_WEIGHT)) != nullptr)
        aFont.SetWeight(static_cast<const SvxWeightItem*>(pItem)->GetWeight());
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_CJK_WEIGHT)) != nullptr)
        aCJKFont.SetWeight(static_cast<const SvxWeightItem*>(pItem)->GetWeight());
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_CTL_WEIGHT)) != nullptr)
        aCTLFont.SetWeight(static_cast<const SvxWeightItem*>(pItem)->GetWeight());

    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_POSTURE)) != nullptr)
        aFont.SetItalic(static_cast<const SvxPostureItem*>(pItem)->GetPosture());
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_CJK_POSTURE)) != nullptr)
        aCJKFont.SetItalic(static_cast<const SvxPostureItem*>(pItem)->GetPosture());
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_CTL_POSTURE)) != nullptr)
        aCTLFont.SetItalic(static_cast<const SvxPostureItem*>(pItem)->GetPosture());

    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_CONTOUR)) != nullptr)
    {
        auto aVal = static_cast<const SvxContourItem*>(pItem)->GetValue();
        aFont.SetOutline(aVal);
        aCJKFont.SetOutline(aVal);
        aCTLFont.SetOutline(aVal);
    }
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_SHADOWED)) != nullptr)
    {
        auto aVal = static_cast<const SvxShadowedItem*>(pItem)->GetValue();
        aFont.SetShadow(aVal);
        aCJKFont.SetShadow(aVal);
        aCTLFont.SetShadow(aVal);
    }
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_RELIEF)) != nullptr)
    {
        auto aVal = static_cast<const SvxCharReliefItem*>(pItem)->GetValue();
        aFont.SetRelief(aVal);
        aCJKFont.SetRelief(aVal);
        aCTLFont.SetRelief(aVal);
    }
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_UNDERLINE)) != nullptr)
    {
        auto aVal = static_cast<const SvxUnderlineItem*>(pItem)->GetLineStyle();
        aFont.SetUnderline(aVal);
        aCJKFont.SetUnderline(aVal);
        aCTLFont.SetUnderline(aVal);
    }
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_OVERLINE)) != nullptr)
    {
        auto aVal = static_cast<const SvxOverlineItem*>(pItem)->GetValue();
        aFont.SetOverline(aVal);
        aCJKFont.SetOverline(aVal);
        aCTLFont.SetOverline(aVal);
    }
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_STRIKEOUT)) != nullptr)
    {
        auto aVal = static_cast<const SvxCrossedOutItem*>(pItem)->GetStrikeout();
        aFont.SetStrikeout(aVal);
        aCJKFont.SetStrikeout(aVal);
        aCTLFont.SetStrikeout(aVal);
    }
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_CASEMAP)) != nullptr)
    {
        auto aVal = static_cast<const SvxCaseMapItem*>(pItem)->GetCaseMap();
        aFont.SetCaseMap(aVal);
        aCJKFont.SetCaseMap(aVal);
        aCTLFont.SetCaseMap(aVal);
    }
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_EMPHASISMARK)) != nullptr)
    {
        auto aVal = static_cast<const SvxEmphasisMarkItem*>(pItem)->GetEmphasisMark();
        aFont.SetEmphasisMark(aVal);
        aCJKFont.SetEmphasisMark(aVal);
        aCTLFont.SetEmphasisMark(aVal);
    }
    if ((pItem = pItemSet->GetItem(SID_ATTR_CHAR_COLOR)) != nullptr)
    {
        maFontColor = static_cast<const SvxColorItem*>(pItem)->GetValue();
    }
    if ((pItem = pItemSet->GetItem(SID_ATTR_BRUSH_CHAR)) != nullptr)
    {
        maHighlightColor = static_cast<const SvxBrushItem*>(pItem)->GetColor();
    }

    if (mpStyle->GetFamily() == SfxStyleFamily::Para)
    {
        if ((pItem = pItemSet->GetItem(XATTR_FILLSTYLE)) != nullptr)
        {
            css::drawing::FillStyle aFillStyle = static_cast<const XFillStyleItem*>(pItem)->GetValue();
            if (aFillStyle == drawing::FillStyle_SOLID)
            {
                if ((pItem = pItemSet->GetItem(XATTR_FILLCOLOR)) != nullptr)
                {
                    maBackgroundColor = static_cast<const XFillColorItem*>(pItem)->GetColorValue();
                }
            }
        }
    }

    if (SetFont(*pItemSet, SID_ATTR_CHAR_FONT, aFont) &&
        SetFontSize(*pItemSet, SID_ATTR_CHAR_FONTHEIGHT, aFont))
        m_oFont = aFont;

    if (SetFont(*pItemSet, SID_ATTR_CHAR_CJK_FONT, aCJKFont) &&
        SetFontSize(*pItemSet, SID_ATTR_CHAR_CJK_FONTHEIGHT, aCJKFont))
        m_oCJKFont = aCJKFont;

    if (SetFont(*pItemSet, SID_ATTR_CHAR_CTL_FONT, aCTLFont) &&
        SetFontSize(*pItemSet, SID_ATTR_CHAR_CTL_FONTHEIGHT, aCTLFont))
        m_oCTLFont = aCTLFont;

    CheckScript();
    CalcRenderSize();
    return true;
}

void CommonStylePreviewRenderer::CalcRenderSize()
{
    const OUString& rText = maStyleName;

    mnBaseLine = 0;
    mnHeight = 0;
    SvtScriptType aScript;
    sal_uInt16 nIdx = 0;
    sal_Int32 nStart = 0;
    sal_Int32 nEnd;
    size_t nCnt = maScriptChanges.size();

    if (nCnt)
    {
        nEnd = maScriptChanges[nIdx].changePos;
        aScript = maScriptChanges[nIdx].scriptType;
    }
    else
    {
        nEnd = rText.getLength();
        aScript = SvtScriptType::LATIN;
    }

    do
    {
        auto oFont = (aScript == SvtScriptType::ASIAN) ?
                         m_oCJKFont :
                         ((aScript == SvtScriptType::COMPLEX) ?
                             m_oCTLFont :
                             m_oFont);

        mrOutputDev.Push(vcl::PushFlags::FONT);

        tools::Long nWidth;
        if (oFont)
        {
            mrOutputDev.SetFont(*oFont);
            nWidth = oFont->GetTextSize(mrOutputDev, rText, nStart, nEnd - nStart).Width();
        }
        else
            nWidth = mrOutputDev.GetTextWidth(rText, nStart, nEnd - nStart);

        tools::Rectangle aRect;
        mrOutputDev.GetTextBoundRect(aRect, rText, nStart, nStart, nEnd - nStart);

        mrOutputDev.Pop();

        mnBaseLine = std::max(mnBaseLine, -aRect.Top());
        mnHeight = std::max(mnHeight, aRect.GetHeight());
        if (nIdx >= maScriptChanges.size())
            break;

        maScriptChanges[nIdx++].textWidth = nWidth;

        if (nEnd < rText.getLength() && nIdx < nCnt)
        {
            nStart = nEnd;
            nEnd = maScriptChanges[nIdx].changePos;
            aScript = maScriptChanges[nIdx].scriptType;
        }
        else
            break;
    }
    while(true);

    double fRatio = 1;
    if (mnHeight > mnMaxHeight && mnHeight != 0)
        fRatio = double(mnMaxHeight) / mnHeight;

    mnHeight *= fRatio;
    mnBaseLine *= fRatio;
    if (fRatio != 1)
    {
        Size aFontSize;
        if (m_oFont)
        {
            aFontSize = m_oFont->GetFontSize();
            m_oFont->SetFontSize(Size(aFontSize.Width() * fRatio, aFontSize.Height() * fRatio));
        }
        if (m_oCJKFont)
        {
            aFontSize = m_oCJKFont->GetFontSize();
            m_oCJKFont->SetFontSize(Size(aFontSize.Width() * fRatio, aFontSize.Height() * fRatio));
        }
        if (m_oCTLFont)
        {
            aFontSize = m_oCTLFont->GetFontSize();
            m_oCTLFont->SetFontSize(Size(aFontSize.Width() * fRatio, aFontSize.Height() * fRatio));
        }

        for (auto& aChange : maScriptChanges)
            aChange.textWidth *= fRatio;
    }
}

bool CommonStylePreviewRenderer::render(const tools::Rectangle& aRectangle, RenderAlign eRenderAlign)
{
    const OUString& rText = maStyleName;

    // setup the device & draw
    auto popIt = mrOutputDev.ScopedPush(vcl::PushFlags::FONT | vcl::PushFlags::TEXTCOLOR | vcl::PushFlags::FILLCOLOR | vcl::PushFlags::TEXTFILLCOLOR);

    if (maBackgroundColor != COL_AUTO)
    {
        mrOutputDev.SetFillColor(maBackgroundColor);
        mrOutputDev.DrawRect(aRectangle);
    }

    Point aFontDrawPosition = aRectangle.TopLeft();
    aFontDrawPosition.AdjustY(mnBaseLine);
    if (eRenderAlign == RenderAlign::CENTER)
    {
        if (aRectangle.GetHeight() > mnHeight)
            aFontDrawPosition.AdjustY((aRectangle.GetHeight() - mnHeight) / 2 );
    }

    SvtScriptType aScript;
    sal_uInt16 nIdx = 0;
    sal_Int32 nStart = 0;
    sal_Int32 nEnd;
    size_t nCnt = maScriptChanges.size();
    if (nCnt)
    {
        nEnd = maScriptChanges[nIdx].changePos;
        aScript = maScriptChanges[nIdx].scriptType;
    }
    else
    {
        nEnd = rText.getLength();
        aScript = SvtScriptType::LATIN;
    }

    do
    {
        auto oFont = (aScript == SvtScriptType::ASIAN)
                         ? m_oCJKFont
                         : ((aScript == SvtScriptType::COMPLEX)
                             ? m_oCTLFont
                             : m_oFont);

        mrOutputDev.Push(vcl::PushFlags::FONT);

        if (oFont)
            mrOutputDev.SetFont(*oFont);

        if (maFontColor != COL_AUTO)
            mrOutputDev.SetTextColor(maFontColor);
        else
        {
            if (maBackgroundColor != COL_AUTO)
               mrOutputDev.SetTextColor(maBackgroundColor.IsDark() ? COL_WHITE : COL_BLACK);
            else if (maHighlightColor != COL_AUTO)
               mrOutputDev.SetTextColor(maHighlightColor.IsDark() ? COL_WHITE : COL_BLACK);
        }

        if (maHighlightColor != COL_AUTO)
            mrOutputDev.SetTextFillColor(maHighlightColor);

        if (oFont)
            oFont->QuickDrawText(&mrOutputDev, aFontDrawPosition, rText, nStart, nEnd - nStart, {});
        else
            mrOutputDev.DrawText(aFontDrawPosition, rText, nStart, nEnd - nStart);

        mrOutputDev.Pop();

        aFontDrawPosition.AdjustX(maScriptChanges[nIdx++].textWidth);
        if (nEnd < rText.getLength() && nIdx < nCnt)
        {
            nStart = nEnd;
            nEnd = maScriptChanges[nIdx].changePos;
            aScript = maScriptChanges[nIdx].scriptType;
        }
        else
            break;
    }
    while(true);

    return true;
}

void CommonStylePreviewRenderer::CheckScript()
{
    assert(!maStyleName.isEmpty()); // must have a preview text here!
    if (maStyleName == maScriptText)
        return; // already initialized

    maScriptText = maStyleName;
    maScriptChanges.clear();

    auto aEditEngine = EditEngine(nullptr);
    aEditEngine.SetText(maScriptText);

    auto aScript = aEditEngine.GetScriptType({ 0, 0, 0, 0 });
    for (sal_Int32 i = 1; i <= maScriptText.getLength(); i++)
    {
        auto aNextScript = aEditEngine.GetScriptType({ 0, i, 0, i });
        if (aNextScript != aScript)
            maScriptChanges.emplace_back(aScript, i - 1);
        if (i == maScriptText.getLength())
            maScriptChanges.emplace_back(aScript, i);
        aScript = aNextScript;
    }
}

} // end svx namespace

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
