/* -*- 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/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <config_wasm_strip.h>

#include <vcl/event.hxx>
#include <vcl/svapp.hxx>
#include <vcl/settings.hxx>
#include <vcl/virdev.hxx>

#include <svx/ucsubset.hxx>
#include <unordered_map>

#include <svx/searchcharmap.hxx>

#include <charmapacc.hxx>

#include <rtl/ustrbuf.hxx>

using namespace ::com::sun::star::accessibility;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star;


SvxSearchCharSet::SvxSearchCharSet(std::unique_ptr<weld::ScrolledWindow> pScrolledWindow, const VclPtr<VirtualDevice>& rVirDev)
    : SvxShowCharSet(std::move(pScrolledWindow), rVirDev)
{
}

int SvxSearchCharSet::LastInView() const
{
    int nIndex = FirstInView();
    nIndex += ROW_COUNT * COLUMN_COUNT - 1;
    return std::min<int>(nIndex, getMaxCharCount() -1);
}

bool SvxSearchCharSet::KeyInput(const KeyEvent& rKEvt)
{
    vcl::KeyCode aCode = rKEvt.GetKeyCode();

    if (aCode.GetModifier())
        return false;

    int tmpSelected = nSelectedIndex;

    bool bRet = true;

    switch (aCode.GetCode())
    {
        case KEY_RETURN:
            return SvxShowCharSet::KeyInput(rKEvt);
        case KEY_SPACE:
            aDoubleClkHdl.Call(this);
            return true;
        case KEY_LEFT:
            --tmpSelected;
            break;
        case KEY_RIGHT:
            ++tmpSelected;
            break;
        case KEY_UP:
            tmpSelected -= COLUMN_COUNT;
            break;
        case KEY_DOWN:
            tmpSelected += COLUMN_COUNT;
            break;
        case KEY_PAGEUP:
            tmpSelected -= ROW_COUNT * COLUMN_COUNT;
            break;
        case KEY_PAGEDOWN:
            tmpSelected += ROW_COUNT * COLUMN_COUNT;
            break;
        case KEY_HOME:
            tmpSelected = 0;
            break;
        case KEY_END:
            tmpSelected = getMaxCharCount() - 1;
            break;
        case KEY_TAB:   // some fonts have a character at these unicode control codes
        case KEY_ESCAPE:
            bRet = false;
            tmpSelected = - 1;  // mark as invalid
            break;
        default:
            tmpSelected = -1;
            bRet = false;
            break;
    }

    if ( tmpSelected >= 0 )
    {
        SelectIndex( tmpSelected, true );
        aPreSelectHdl.Call( this );
    }

    return bRet;
}

void SvxSearchCharSet::SelectCharacter( const Subset* sub )
{
    if (!mxFontCharMap.is())
        RecalculateFont(*mxVirDev);

    // get next available char of current font
    sal_UCS4 cChar = sub->GetRangeMin();
    int nMapIndex = 0;

    while(cChar <= sub->GetRangeMax() && nMapIndex == 0)
    {
        auto it = std::find_if(m_aItemList.begin(), m_aItemList.end(),
            [&cChar](const std::pair<const sal_Int32, sal_UCS4>& rItem) { return rItem.second == cChar; });
        if (it != m_aItemList.end())
            nMapIndex = it->first;
        cChar++;
    }

    if(nMapIndex == 0)
        SelectIndex( 0 );
    else
        SelectIndex( nMapIndex );
    aHighHdl.Call(this);
    // move selected item to top row if not in focus
    //TO.DO aVscrollSB->SetThumbPos( nMapIndex / COLUMN_COUNT );
    Invalidate();
}

void SvxSearchCharSet::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle&)
{
    InitSettings(rRenderContext);
    RecalculateFont(rRenderContext);
    DrawChars_Impl(rRenderContext, FirstInView(), LastInView());
}

void SvxSearchCharSet::DrawChars_Impl(vcl::RenderContext& rRenderContext, int n1, int n2)
{
    if (n1 > LastInView() || n2 < FirstInView())
        return;

    Size aOutputSize(GetOutputSizePixel());

    const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
    const Color aWindowTextColor(rStyleSettings.GetFieldTextColor());
    Color aHighlightColor(rStyleSettings.GetHighlightColor());
    Color aHighlightTextColor(rStyleSettings.GetHighlightTextColor());
    Color aFaceColor(rStyleSettings.GetFaceColor());
    Color aLightColor(rStyleSettings.GetLightColor());
    Color aShadowColor(rStyleSettings.GetShadowColor());

    int i;
    rRenderContext.SetLineColor(aShadowColor);
    for (i = 1; i < COLUMN_COUNT; ++i)
    {
        rRenderContext.DrawLine(Point(nX * i + m_nXGap, 0),
                          Point(nX * i + m_nXGap, aOutputSize.Height()));
    }
    for (i = 1; i < ROW_COUNT; ++i)
    {
        rRenderContext.DrawLine(Point(0, nY * i + m_nYGap),
                                Point(aOutputSize.Width(), nY * i + m_nYGap));
    }

    int nTextHeight = rRenderContext.GetTextHeight();
    tools::Rectangle aBoundRect;
    for (i = n1; i <= n2; ++i)
    {
        Point pix = MapIndexToPixel(i);
        int x = pix.X();
        int y = pix.Y();

        OUStringBuffer buf;
        std::unordered_map<sal_Int32, sal_UCS4>::const_iterator got = m_aItemList.find (i);
        sal_UCS4 sName;

        if(got == m_aItemList.end())
            continue;
        else
            sName = got->second;

        buf.appendUtf32(sName);
        OUString aCharStr(buf.makeStringAndClear());
        int nTextWidth = rRenderContext.GetTextWidth(aCharStr);
        int tx = x + (nX - nTextWidth + 1) / 2;
        int ty = y + (nY - nTextHeight + 1) / 2;
        Point aPointTxTy(tx, ty);

        // adjust position before it gets out of bounds
        if (rRenderContext.GetTextBoundRect(aBoundRect, aCharStr) && !aBoundRect.IsEmpty())
        {
            // zero advance width => use ink width to center glyph
            if (!nTextWidth)
            {
                aPointTxTy.setX( x - aBoundRect.Left() + (nX - aBoundRect.GetWidth() + 1) / 2 );
            }

            aBoundRect += aPointTxTy;

            // shift back vertically if needed
            int nYLDelta = aBoundRect.Top() - y;
            int nYHDelta = (y + nY) - aBoundRect.Bottom();
            if (nYLDelta <= 0)
                aPointTxTy.AdjustY( -(nYLDelta - 1) );
            else if (nYHDelta <= 0)
                aPointTxTy.AdjustY(nYHDelta - 1 );

            // shift back horizontally if needed
            int nXLDelta = aBoundRect.Left() - x;
            int nXHDelta = (x + nX) - aBoundRect.Right();
            if (nXLDelta <= 0)
                aPointTxTy.AdjustX( -(nXLDelta - 1) );
            else if (nXHDelta <= 0)
                aPointTxTy.AdjustX(nXHDelta - 1 );
        }

        // tdf#109214 - highlight the favorite characters
        if (isFavChar(aCharStr, mxVirDev->GetFont().GetFamilyName()))
        {
            const Color aLineCol = rRenderContext.GetLineColor();
            rRenderContext.SetLineColor(aHighlightColor);
            rRenderContext.SetFillColor(COL_TRANSPARENT);
            // Outer border
            rRenderContext.DrawRect(tools::Rectangle(Point(x - 1, y - 1), Size(nX + 3, nY + 3)), 1, 1);
            // Inner border
            rRenderContext.DrawRect(tools::Rectangle(Point(x, y), Size(nX + 1, nY + 1)), 1, 1);
            rRenderContext.SetLineColor(aLineCol);
        }

        Color aTextCol = rRenderContext.GetTextColor();
        if (i != nSelectedIndex)
        {
            rRenderContext.SetTextColor(aWindowTextColor);
            rRenderContext.DrawText(aPointTxTy, aCharStr);
        }
        else
        {
            Color aLineCol = rRenderContext.GetLineColor();
            Color aFillCol = rRenderContext.GetFillColor();
            rRenderContext.SetLineColor();
            Point aPointUL(x + 1, y + 1);
            if (HasFocus())
            {
                rRenderContext.SetFillColor(aHighlightColor);
                rRenderContext.DrawRect(getGridRectangle(aPointUL, aOutputSize));

                rRenderContext.SetTextColor(aHighlightTextColor);
                rRenderContext.DrawText(aPointTxTy, aCharStr);
            }
            else
            {
                rRenderContext.SetFillColor(aFaceColor);
                rRenderContext.DrawRect(getGridRectangle(aPointUL, aOutputSize));

                rRenderContext.SetLineColor(aLightColor);
                rRenderContext.DrawLine(aPointUL, Point(x + nX - 1, y + 1));
                rRenderContext.DrawLine(aPointUL, Point(x + 1, y + nY - 1));

                rRenderContext.SetLineColor(aShadowColor);
                rRenderContext.DrawLine(Point(x + 1, y + nY - 1), Point(x + nX - 1, y + nY - 1));
                rRenderContext.DrawLine(Point(x + nX - 1, y + nY - 1), Point(x + nX - 1, y + 1));

                rRenderContext.DrawText(aPointTxTy, aCharStr);
            }
            rRenderContext.SetLineColor(aLineCol);
            rRenderContext.SetFillColor(aFillCol);
        }
        rRenderContext.SetTextColor(aTextCol);
    }

    // tdf#141319 - mark empty/unused cells
    if (n2 - n1 < ROW_COUNT * COLUMN_COUNT)
    {
        rRenderContext.SetFillColor(rStyleSettings.GetDisableColor());
        for (i = n2 - n1 + 1; i < ROW_COUNT * COLUMN_COUNT; i++)
        {
            rRenderContext.DrawRect(
                tools::Rectangle(MapIndexToPixel(i + n1), Size(nX + 2, nY + 2)));
        }
    }
}

sal_UCS4 SvxSearchCharSet::GetSelectCharacter() const
{
    if( nSelectedIndex >= 0 )
    {
        std::unordered_map<sal_Int32,sal_UCS4>::const_iterator got = m_aItemList.find (nSelectedIndex);

        if(got == m_aItemList.end())
            return 1;
        else
            return got->second;
    }
    return 1;
}

void SvxSearchCharSet::RecalculateFont(vcl::RenderContext& rRenderContext)
{
    if (!mbRecalculateFont)
        return;

    Size aSize(GetOutputSizePixel());

    vcl::Font aFont = rRenderContext.GetFont();
    aFont.SetWeight(WEIGHT_LIGHT);
    aFont.SetAlignment(ALIGN_TOP);
    int nFontHeight = (aSize.Height() - 5) * 2 / (3 * ROW_COUNT);
    maFontSize = rRenderContext.PixelToLogic(Size(0, nFontHeight));
    aFont.SetFontSize(maFontSize);
    aFont.SetTransparent(true);
    rRenderContext.SetFont(aFont);
    rRenderContext.GetFontCharMap(mxFontCharMap);
    m_aItems.clear();
    getFavCharacterList();

    nX = aSize.Width() / COLUMN_COUNT;
    nY = aSize.Height() / ROW_COUNT;

    UpdateScrollRange();

    // rearrange CharSet element in sync with nX- and nY-multiples
    Size aDrawSize(nX * COLUMN_COUNT, nY * ROW_COUNT);
    m_nXGap = (aSize.Width() - aDrawSize.Width()) / 2;
    m_nYGap = (aSize.Height() - aDrawSize.Height()) / 2;

    mbRecalculateFont = false;
}

void SvxSearchCharSet::UpdateScrollRange()
{
    //scrollbar settings
    int nLastRow = (getMaxCharCount() - 1 + COLUMN_COUNT) / COLUMN_COUNT;
    mxScrollArea->vadjustment_configure(mxScrollArea->vadjustment_get_value(), 0, nLastRow, 1, ROW_COUNT - 1, ROW_COUNT);
}

void SvxSearchCharSet::SelectIndex(int nNewIndex, bool bFocus)
{
    if (!mxFontCharMap.is())
        RecalculateFont(*mxVirDev);

    if( nNewIndex < 0 )
    {
        mxScrollArea->vadjustment_set_value(0);
        nSelectedIndex = bFocus ? 0 : -1;
        Invalidate();
    }
    else if( nNewIndex < FirstInView() )
    {
        // need to scroll up to see selected item
        int nOldPos = mxScrollArea->vadjustment_get_value();
        int nDelta = (FirstInView() - nNewIndex + COLUMN_COUNT-1) / COLUMN_COUNT;
        mxScrollArea->vadjustment_set_value(nOldPos - nDelta);
        nSelectedIndex = nNewIndex;
        Invalidate();
    }
    else if( nNewIndex > LastInView() )
    {
        // need to scroll down to see selected item
        int nOldPos = mxScrollArea->vadjustment_get_value();
        int nDelta = (nNewIndex - LastInView() + COLUMN_COUNT) / COLUMN_COUNT;
        mxScrollArea->vadjustment_set_value(nOldPos + nDelta);

        if (nNewIndex < getMaxCharCount())
        {
            nSelectedIndex = nNewIndex;
            Invalidate();
        }
        else if (nOldPos != mxScrollArea->vadjustment_get_value())
        {
            Invalidate();
        }
    }
    else
    {
        nSelectedIndex = nNewIndex;
        Invalidate();
    }

    if( nSelectedIndex >= 0 )
    {
#if 0
        if( m_xAccessible.is() )
        {
            svx::SvxShowCharSetItem* pItem = ImplGetItem(nSelectedIndex);
            // Don't fire the focus event.
            if ( bFocus )
                m_xAccessible->fireEvent( AccessibleEventId::ACTIVE_DESCENDANT_CHANGED, Any(), makeAny(pItem->GetAccessible()) ); // this call assures that m_pItem is set
            else
                m_xAccessible->fireEvent( AccessibleEventId::ACTIVE_DESCENDANT_CHANGED_NOFOCUS, Any(), makeAny(pItem->GetAccessible()) ); // this call assures that m_pItem is set

            assert(pItem->m_xItem.is() && "No accessible created!");
            Any aOldAny, aNewAny;
            aNewAny <<= AccessibleStateType::FOCUSED;
            // Don't fire the focus event.
            if ( bFocus )
                pItem->m_xItem->fireEvent( AccessibleEventId::STATE_CHANGED, aOldAny, aNewAny );

            aNewAny <<= AccessibleStateType::SELECTED;
            pItem->m_xItem->fireEvent( AccessibleEventId::STATE_CHANGED, aOldAny, aNewAny );
        }
#endif
        aSelectHdl.Call(this);
    }
    aHighHdl.Call( this );
}

SvxSearchCharSet::~SvxSearchCharSet()
{
}

svx::SvxShowCharSetItem* SvxSearchCharSet::ImplGetItem( int _nPos )
{
    ItemsMap::iterator aFind = m_aItems.find(_nPos);
    if ( aFind == m_aItems.end() )
    {
#if !ENABLE_WASM_STRIP_ACCESSIBILITY
        OSL_ENSURE(m_xAccessible.is(), "Who wants to create a child of my table without a parent?");
#endif
        auto xItem = std::make_shared<svx::SvxShowCharSetItem>(*this,
            m_xAccessible.get(), sal::static_int_cast< sal_uInt16 >(_nPos));
        aFind = m_aItems.emplace(_nPos, xItem).first;
        OUStringBuffer buf;
        std::unordered_map<sal_Int32,sal_UCS4>::const_iterator got = m_aItemList.find (_nPos);
        if (got != m_aItemList.end())
            buf.appendUtf32(got->second);
        aFind->second->maText = buf.makeStringAndClear();
        Point pix = MapIndexToPixel( _nPos );
        aFind->second->maRect = tools::Rectangle( Point( pix.X() + 1, pix.Y() + 1 ), Size(nX-1,nY-1) );
    }

    return aFind->second.get();
}

sal_Int32 SvxSearchCharSet::getMaxCharCount() const
{
    return m_aItemList.size();
}

void SvxSearchCharSet::ClearPreviousData()
{
    m_aItemList.clear();
    Invalidate();
}

void SvxSearchCharSet::AppendCharToList(sal_UCS4 sChar)
{
    m_aItemList.insert(std::make_pair(m_aItemList.size(), sChar));
}

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