/* -*- 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 <comphelper/string.hxx>
#include <vcl/svapp.hxx>
#include <sal/log.hxx>

#include "parawin.hxx"
#include <formula/IFunctionDescription.hxx>
#include <formula/funcvarargs.h>
#include <strings.hrc>
#include <bitmaps.hlst>
#include <core_resource.hxx>
#include <rtl/math.hxx>

namespace formula
{

// Formula token argument count is sal_uInt8, max 255, edit offset 254.
constexpr sal_uInt16 kMaxArgCount = 255;
constexpr sal_uInt16 kMaxArgOffset = kMaxArgCount - 1;

ParaWin::ParaWin(weld::Container* pParent,IControlReferenceHandler* _pDlg)
    : pFuncDesc(nullptr)
    , pMyParent(_pDlg)
    , m_sOptional(ForResId(STR_OPTIONAL))
    , m_sRequired(ForResId(STR_REQUIRED))
    , m_xBuilder(Application::CreateBuilder(pParent, u"formula/ui/parameter.ui"_ustr))
    , m_xContainer(m_xBuilder->weld_container(u"ParameterPage"_ustr))
    , m_xSlider(m_xBuilder->weld_scrolled_window(u"scrollbar"_ustr, true))
    , m_xParamGrid(m_xBuilder->weld_widget(u"paramgrid"_ustr))
    , m_xGrid(m_xBuilder->weld_widget(u"grid"_ustr))
    , m_xFtEditDesc(m_xBuilder->weld_label(u"editdesc"_ustr))
    , m_xFtArgName(m_xBuilder->weld_label(u"parname"_ustr))
    , m_xFtArgDesc(m_xBuilder->weld_label(u"pardesc"_ustr))
    , m_xBtnFx1(m_xBuilder->weld_button(u"FX1"_ustr))
    , m_xBtnFx2(m_xBuilder->weld_button(u"FX2"_ustr))
    , m_xBtnFx3(m_xBuilder->weld_button(u"FX3"_ustr))
    , m_xBtnFx4(m_xBuilder->weld_button(u"FX4"_ustr))
    , m_xFtArg1(m_xBuilder->weld_label(u"FT_ARG1"_ustr))
    , m_xFtArg2(m_xBuilder->weld_label(u"FT_ARG2"_ustr))
    , m_xFtArg3(m_xBuilder->weld_label(u"FT_ARG3"_ustr))
    , m_xFtArg4(m_xBuilder->weld_label(u"FT_ARG4"_ustr))
    , m_xEdArg1(new ArgEdit(m_xBuilder->weld_entry(u"ED_ARG1"_ustr)))
    , m_xEdArg2(new ArgEdit(m_xBuilder->weld_entry(u"ED_ARG2"_ustr)))
    , m_xEdArg3(new ArgEdit(m_xBuilder->weld_entry(u"ED_ARG3"_ustr)))
    , m_xEdArg4(new ArgEdit(m_xBuilder->weld_entry(u"ED_ARG4"_ustr)))
    , m_xRefBtn1(new RefButton(m_xBuilder->weld_button(u"RB_ARG1"_ustr)))
    , m_xRefBtn2(new RefButton(m_xBuilder->weld_button(u"RB_ARG2"_ustr)))
    , m_xRefBtn3(new RefButton(m_xBuilder->weld_button(u"RB_ARG3"_ustr)))
    , m_xRefBtn4(new RefButton(m_xBuilder->weld_button(u"RB_ARG4"_ustr)))
{
    // Space for three lines of text in function description.
    m_xFtEditDesc->set_label(u"X\nX\nX\n"_ustr);
    auto nEditHeight = m_xFtEditDesc->get_preferred_size().Height();
    m_xFtEditDesc->set_size_request(-1, nEditHeight);
    m_xFtEditDesc->set_label(u""_ustr);
    // Space for two lines of text in parameter description.
    m_xFtArgDesc->set_label(u"X\nX\n"_ustr);
    auto nArgHeight = m_xFtArgDesc->get_preferred_size().Height();
    m_xFtArgDesc->set_size_request(-1, nArgHeight);
    m_xFtArgDesc->set_label(u""_ustr);

    m_xBtnFx1->set_from_icon_name(BMP_FX);
    m_xBtnFx2->set_from_icon_name(BMP_FX);
    m_xBtnFx3->set_from_icon_name(BMP_FX);
    m_xBtnFx4->set_from_icon_name(BMP_FX);

    //lock down initial preferences
    m_xParamGrid->set_size_request(-1, m_xParamGrid->get_preferred_size().Height());
    Size aSize(m_xContainer->get_preferred_size());
    m_xContainer->set_size_request(aSize.Width(), aSize.Height());

    aDefaultString = m_xFtEditDesc->get_label();
    nEdFocus = NOT_FOUND;
    nActiveLine = 0;

    m_xSlider->connect_vadjustment_value_changed(LINK(this, ParaWin, ScrollHdl));

    InitArgInput( 0, *m_xFtArg1, *m_xBtnFx1, *m_xEdArg1, *m_xRefBtn1);
    InitArgInput( 1, *m_xFtArg2, *m_xBtnFx2, *m_xEdArg2, *m_xRefBtn2);
    InitArgInput( 2, *m_xFtArg3, *m_xBtnFx3, *m_xEdArg3, *m_xRefBtn3);
    InitArgInput( 3, *m_xFtArg4, *m_xBtnFx4, *m_xEdArg4, *m_xRefBtn4);
    ClearAll();
}

void ParaWin::UpdateArgDesc( sal_uInt16 nArg )
{
    if (nArg == NOT_FOUND)
        return;

    if (nMaxArgs > 4)
        nArg = sal::static_int_cast<sal_uInt16>( nArg + GetSliderPos() );

    if ((nMaxArgs <= 0) || (nArg >= nMaxArgs))
        return;

    OUString  aArgDesc;
    OUString  aArgName;

    SetArgumentDesc( OUString() );
    SetArgumentText( OUString() );

    if ( nArgs < VAR_ARGS )
    {
        sal_uInt16 nRealArg = (nArg < aVisibleArgMapping.size()) ? aVisibleArgMapping[nArg] : nArg;
        aArgDesc  = pFuncDesc->getParameterDescription(nRealArg);
        aArgName  = pFuncDesc->getParameterName(nRealArg) + " " +
            ((pFuncDesc->isParameterOptional(nRealArg)) ? m_sOptional : m_sRequired);
    }
    else if ( nArgs < PAIRED_VAR_ARGS )
    {
        sal_uInt16 nFix = nArgs - VAR_ARGS;
        sal_uInt16 nPos = std::min( nArg, nFix );
        sal_uInt16 nRealArg = (nPos < aVisibleArgMapping.size() ?
                aVisibleArgMapping[nPos] : aVisibleArgMapping.back());
        aArgDesc  = pFuncDesc->getParameterDescription(nRealArg);
        aArgName  = pFuncDesc->getParameterName(nRealArg);
        sal_uInt16 nVarArgsStart = pFuncDesc->getVarArgsStart();
        if ( nArg >= nVarArgsStart )
            aArgName += OUString::number( nArg-nVarArgsStart+1 );
        aArgName += " " + ((nArg > nFix || pFuncDesc->isParameterOptional(nRealArg)) ? m_sOptional : m_sRequired) ;
    }
    else
    {
        sal_uInt16 nFix = nArgs - PAIRED_VAR_ARGS;
        sal_uInt16 nPos;
        if ( nArg < nFix )
            nPos = nArg;
        else
            nPos = nFix + ( (nArg-nFix) % 2);
        sal_uInt16 nRealArg = (nPos < aVisibleArgMapping.size() ?
                aVisibleArgMapping[nPos] : aVisibleArgMapping.back());
        aArgDesc  = pFuncDesc->getParameterDescription(nRealArg);
        aArgName  = pFuncDesc->getParameterName(nRealArg);
        sal_uInt16 nVarArgsStart = pFuncDesc->getVarArgsStart();
        if ( nArg >= nVarArgsStart )
        {
            sal_Int16 nShifted = pFuncDesc->getFunctionName().equalsIgnoreAsciiCase(u"LET") ? nPos / 2 : 0;
            aArgName += OUString::number( (nArg-nVarArgsStart)/2 + 1 + nShifted );
        }
        aArgName += " " + ((nArg > (nFix+1) || pFuncDesc->isParameterOptional(nRealArg)) ? m_sOptional : m_sRequired) ;
    }

    SetArgumentDesc(aArgDesc);
    SetArgumentText(aArgName);
}

void ParaWin::UpdateArgInput( sal_uInt16 nOffset, sal_uInt16 i )
{
    sal_uInt16 nArg = nOffset + i;
    if (nArg > kMaxArgOffset)
        return;

    if ( nArgs < VAR_ARGS)
    {
        if (nArg < nMaxArgs)
        {
            sal_uInt16 nRealArg = aVisibleArgMapping[nArg];
            SetArgNameFont  (i,(pFuncDesc->isParameterOptional(nRealArg))
                                            ? aFntLight : aFntBold );
            SetArgName      (i,pFuncDesc->getParameterName(nRealArg));
        }
    }
    else if ( nArgs < PAIRED_VAR_ARGS)
    {
        sal_uInt16 nFix = nArgs - VAR_ARGS;
        sal_uInt16 nPos = std::min( nArg, nFix );
        sal_uInt16 nRealArg = (nPos < aVisibleArgMapping.size() ?
                aVisibleArgMapping[nPos] : aVisibleArgMapping.back());
        SetArgNameFont( i,
                (nArg > nFix || pFuncDesc->isParameterOptional(nRealArg)) ?
                aFntLight : aFntBold );
        sal_uInt16 nVarArgsStart = pFuncDesc->getVarArgsStart();
        if ( nArg >= nVarArgsStart )
        {
            OUString aArgName = pFuncDesc->getParameterName(nRealArg) +
                OUString::number(nArg-nVarArgsStart+1);
            SetArgName( i, aArgName );
        }
        else
            SetArgName( i, pFuncDesc->getParameterName(nRealArg) );
    }
    else
    {
        sal_uInt16 nFix = nArgs - PAIRED_VAR_ARGS;
        sal_uInt16 nPos;
        if ( nArg < nFix )
            nPos = nArg;
        else
            nPos = nFix + ( (nArg-nFix) % 2);
        sal_uInt16 nRealArg = (nPos < aVisibleArgMapping.size() ?
                aVisibleArgMapping[nPos] : aVisibleArgMapping.back());
        SetArgNameFont( i,
                (nArg > (nFix+1) || pFuncDesc->isParameterOptional(nRealArg)) ?
                aFntLight : aFntBold );
        sal_uInt16 nVarArgsStart = pFuncDesc->getVarArgsStart();
        if ( nArg >= nVarArgsStart )
        {
            sal_Int16 nShifted = pFuncDesc->getFunctionName().equalsIgnoreAsciiCase(u"LET") ? nPos / 2 : 0;
            OUString aArgName = pFuncDesc->getParameterName(nRealArg) +
                OUString::number( (nArg-nVarArgsStart)/2 + 1 + nShifted);
            SetArgName( i, aArgName );
        }
        else
            SetArgName( i, pFuncDesc->getParameterName(nRealArg) );
    }
    if (nArg < nMaxArgs)
        aArgInput[i].SetArgVal(aParaArray[nArg]);
}

ParaWin::~ParaWin()
{
    // #i66422# if the focus changes during destruction of the controls,
    // don't call the focus handlers
    Link<weld::Widget&,void> aEmptyLink;
    m_xBtnFx1->connect_focus_in(aEmptyLink);
    m_xBtnFx2->connect_focus_in(aEmptyLink);
    m_xBtnFx3->connect_focus_in(aEmptyLink);
    m_xBtnFx4->connect_focus_in(aEmptyLink);
}

void ParaWin::SetActiveLine(sal_uInt16 no)
{
    if (no >= nMaxArgs)
        return;

    tools::Long nOffset = GetSliderPos();
    nActiveLine=no;
    tools::Long nNewEdPos=static_cast<tools::Long>(nActiveLine)-nOffset;
    if(nNewEdPos<0 || nNewEdPos>3)
    {
        nOffset+=nNewEdPos;
        SetSliderPos(static_cast<sal_uInt16>(nOffset));
        nOffset=GetSliderPos();
    }
    nEdFocus=no-static_cast<sal_uInt16>(nOffset);
    UpdateArgDesc( nEdFocus );
}

RefEdit* ParaWin::GetActiveEdit()
{
    if (nMaxArgs > 0 && nEdFocus != NOT_FOUND)
    {
        return aArgInput[nEdFocus].GetArgEdPtr();
    }
    else
    {
        return nullptr;
    }
}


OUString ParaWin::GetArgument(sal_uInt16 no)
{
    OUString aStr;
    if(no<aParaArray.size())
    {
        aStr=aParaArray[no];
        if(no==nActiveLine && aStr.isEmpty())
            aStr += " ";
    }
    return aStr;
}

OUString  ParaWin::GetActiveArgName() const
{
    OUString aStr;
    if (nMaxArgs > 0 && nEdFocus != NOT_FOUND)
    {
        aStr=aArgInput[nEdFocus].GetArgName();
    }
    return aStr;
}


void ParaWin::SetArgument(sal_uInt16 no, std::u16string_view aString)
{
    if (no < aParaArray.size())
        aParaArray[no] = comphelper::string::stripStart(aString, ' ');
}

void ParaWin::SetArgumentFonts(const vcl::Font&aBoldFont,const vcl::Font&aLightFont)
{
    aFntBold=aBoldFont;
    aFntLight=aLightFont;
}

void ParaWin::SetFunctionDesc(const IFunctionDescription* pFDesc)
{
    pFuncDesc=pFDesc;

    SetArgumentDesc( OUString() );
    SetArgumentText( OUString() );
    SetEditDesc( OUString() );
    nMaxArgs = nArgs = 0;
    if ( pFuncDesc!=nullptr)
    {
        if ( !pFuncDesc->getDescription().isEmpty() )
        {
            SetEditDesc(pFuncDesc->getDescription());
        }
        else
        {
            SetEditDesc(aDefaultString);
        }
        nArgs = pFuncDesc->getSuppressedArgumentCount();
        nMaxArgs = std::min( nArgs, kMaxArgCount);
        if (sal_uInt16 nVarArgsLimit = pFuncDesc->getVarArgsLimit())
            nMaxArgs = std::min( nVarArgsLimit, nMaxArgs);
        pFuncDesc->fillVisibleArgumentMapping(aVisibleArgMapping);
        m_xSlider->set_vpolicy(VclPolicyType::NEVER);
        m_xSlider->set_size_request(-1, -1);
        OUString sHelpId = pFuncDesc->getHelpId();
        m_xContainer->set_help_id(sHelpId);
        m_xEdArg1->GetWidget()->set_help_id(sHelpId);
        m_xEdArg2->GetWidget()->set_help_id(sHelpId);
        m_xEdArg3->GetWidget()->set_help_id(sHelpId);
        m_xEdArg4->GetWidget()->set_help_id(sHelpId);

        SetActiveLine(0);
    }
    else
    {
        nActiveLine=0;
    }

}

void ParaWin::SetArgumentText(const OUString& aText)
{
    m_xFtArgName->set_label(aText);
}

void ParaWin::SetArgumentDesc(const OUString& aText)
{
    m_xFtArgDesc->set_label(aText);
}

void ParaWin::SetEditDesc(const OUString& aText)
{
    m_xFtEditDesc->set_label(aText);
}

void ParaWin::SetArgName(sal_uInt16 no,const OUString& aText)
{
    aArgInput[no].SetArgName(aText);
    aArgInput[no].UpdateAccessibleNames();
}

void ParaWin::SetArgNameFont(sal_uInt16 no,const vcl::Font& aFont)
{
    aArgInput[no].SetArgNameFont(aFont);
}

void ParaWin::SetEdFocus()
{
    UpdateArgDesc(0);
    if(!aParaArray.empty())
        aArgInput[0].GetArgEdPtr()->GrabFocus();
}

void ParaWin::InitArgInput(sal_uInt16 nPos, weld::Label& rFtArg, weld::Button& rBtnFx,
                           ArgEdit& rEdArg, RefButton& rRefBtn)
{

    rRefBtn.SetReferences(pMyParent, &rEdArg);
    rEdArg.SetReferences(pMyParent, &rFtArg);

    aArgInput[nPos].InitArgInput (&rFtArg,&rBtnFx,&rEdArg,&rRefBtn);

    aArgInput[nPos].Hide();

    aArgInput[nPos].SetFxClickHdl   ( LINK( this, ParaWin, GetFxHdl ) );
    aArgInput[nPos].SetFxFocusHdl   ( LINK( this, ParaWin, GetFxFocusHdl ) );
    aArgInput[nPos].SetEdFocusHdl   ( LINK( this, ParaWin, GetEdFocusHdl ) );
    aArgInput[nPos].SetEdModifyHdl  ( LINK( this, ParaWin, ModifyHdl ) );
    aArgInput[nPos].UpdateAccessibleNames();
}

void ParaWin::ClearAll()
{
    SetFunctionDesc(nullptr);
    SetArgumentOffset(0);
}

void ParaWin::SetArgumentOffset(sal_uInt16 nOffset)
{
    aParaArray.clear();
    m_xSlider->vadjustment_set_value(0);

    aParaArray.resize(nMaxArgs);

    if (nMaxArgs > 0)
    {
        for ( int i=0; i<4 && i<nMaxArgs; i++ )
        {
            aArgInput[i].SetArgVal(OUString());
            aArgInput[i].GetArgEdPtr()->Init(
                (i==0)                  ? nullptr : aArgInput[i-1].GetArgEdPtr(),
                (i==3 || i==nMaxArgs-1) ? nullptr : aArgInput[i+1].GetArgEdPtr(),
                                          *m_xSlider, *this, nMaxArgs );
        }
    }

    UpdateParas();

    if (nMaxArgs < 5)
    {
        m_xSlider->set_vpolicy(VclPolicyType::NEVER);
        m_xSlider->set_size_request(-1, -1);
    }
    else
    {
        m_xSlider->vadjustment_configure(nOffset, nMaxArgs, 1, 4, 4);
        m_xSlider->set_vpolicy(VclPolicyType::ALWAYS);
        Size aPrefSize(m_xGrid->get_preferred_size());
        m_xSlider->set_size_request(aPrefSize.Width(), aPrefSize.Height());
    }
}

void ParaWin::UpdateParas()
{
    sal_uInt16 i;
    sal_uInt16 nOffset = GetSliderPos();

    if ( nMaxArgs > 0 )
    {
        for ( i=0; (i<nMaxArgs) && (i<4); i++ )
        {
            UpdateArgInput( nOffset, i );
            aArgInput[i].Show();
        }
    }

    for ( i=nMaxArgs; i<4; i++ )
        aArgInput[i].Hide();
}


sal_uInt16 ParaWin::GetSliderPos() const
{
    return static_cast<sal_uInt16>(m_xSlider->vadjustment_get_value());
}

void ParaWin::SetSliderPos(sal_uInt16 nSliderPos)
{
    sal_uInt16 nOffset = GetSliderPos();

    if(m_xSlider->get_visible() && nOffset!=nSliderPos)
    {
        m_xSlider->vadjustment_set_value(nSliderPos);
        for ( sal_uInt16 i=0; i<4; i++ )
        {
            UpdateArgInput( nSliderPos, i );
        }
    }
}

void ParaWin::SliderMoved()
{
    sal_uInt16 nOffset = GetSliderPos();

    for ( sal_uInt16 i=0; i<4; i++ )
    {
        UpdateArgInput( nOffset, i );
    }
    if(nEdFocus!=NOT_FOUND)
    {
        UpdateArgDesc( nEdFocus );
        aArgInput[nEdFocus].SelectAll();
        nActiveLine=nEdFocus+nOffset;
        ArgumentModified();
        aArgInput[nEdFocus].SelectAll(); // ensure all is still selected
        aArgInput[nEdFocus].UpdateAccessibleNames();
    }
}

void ParaWin::ArgumentModified()
{
    aArgModifiedLink.Call(*this);
}

IMPL_LINK( ParaWin, GetFxHdl, ArgInput&, rPtr, void )
{
    sal_uInt16 nOffset = GetSliderPos();
    nEdFocus=NOT_FOUND;
    for (size_t nPos=0; nPos < std::size(aArgInput); ++nPos)
    {
        if(&rPtr == &aArgInput[nPos])
        {
            nEdFocus=nPos;
            break;
        }
    }

    if(nEdFocus!=NOT_FOUND)
    {
        aArgInput[nEdFocus].SelectAll();
        nActiveLine=nEdFocus+nOffset;
        aFxLink.Call(*this);
    }
}

IMPL_LINK( ParaWin, GetFxFocusHdl, ArgInput&, rPtr, void )
{
    sal_uInt16 nOffset = GetSliderPos();
    nEdFocus=NOT_FOUND;
    for (size_t nPos=0; nPos < SAL_N_ELEMENTS(aArgInput); ++nPos)
    {
        if(&rPtr == &aArgInput[nPos])
        {
            nEdFocus=nPos;
            break;
        }
    }

    if(nEdFocus!=NOT_FOUND)
    {
        aArgInput[nEdFocus].SelectAll();
        UpdateArgDesc( nEdFocus );
        nActiveLine=nEdFocus+nOffset;
    }
}

IMPL_LINK( ParaWin, GetEdFocusHdl, ArgInput&, rPtr, void )
{
    sal_uInt16 nOffset = GetSliderPos();
    nEdFocus=NOT_FOUND;
    for (size_t nPos=0; nPos < SAL_N_ELEMENTS(aArgInput); ++nPos)
    {
        if(&rPtr == &aArgInput[nPos])
        {
            nEdFocus=nPos;
            break;
        }
    }

    if(nEdFocus!=NOT_FOUND)
    {
        aArgInput[nEdFocus].SelectAll();
        UpdateArgDesc( nEdFocus );
        nActiveLine=nEdFocus+nOffset;
        ArgumentModified();
        aArgInput[nEdFocus].SelectAll(); // ensure all is still selected
        aArgInput[nEdFocus].UpdateAccessibleNames();
    }
}

IMPL_LINK_NOARG(ParaWin, ScrollHdl, weld::ScrolledWindow&, void)
{
    SliderMoved();
}

IMPL_LINK( ParaWin, ModifyHdl, ArgInput&, rPtr, void )
{
    sal_uInt16 nOffset = GetSliderPos();
    nEdFocus=NOT_FOUND;
    for (size_t nPos=0; nPos < SAL_N_ELEMENTS(aArgInput); ++nPos)
    {
        if(&rPtr == &aArgInput[nPos])
        {
            nEdFocus=nPos;
            break;
        }
    }
    if(nEdFocus!=NOT_FOUND)
    {
        size_t nPara = nEdFocus + nOffset;
        if (nPara < aParaArray.size())
            aParaArray[nPara] = aArgInput[nEdFocus].GetArgVal();
        else
        {
            SAL_WARN("formula.ui","ParaWin::ModifyHdl - shot in foot: nPara " <<
                    nPara << " >= aParaArray.size() " << aParaArray.size() <<
                    " with nEdFocus " << nEdFocus <<
                    " and aArgInput[nEdFocus].GetArgVal() '" << aArgInput[nEdFocus].GetArgVal() << "'");
        }
        UpdateArgDesc( nEdFocus);
        nActiveLine = static_cast<sal_uInt16>(nPara);
    }

    ArgumentModified();
}


} // formula

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