/* -*- 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 <sfx2/dispatch.hxx>

#include <tabvwsh.hxx>
#include <uiitems.hxx>
#include <dbdata.hxx>
#include <rangenam.hxx>
#include <rangeutl.hxx>
#include <reffact.hxx>
#include <document.hxx>
#include <scresid.hxx>

#include <globstr.hrc>
#include <strings.hrc>

#include <consdlg.hxx>
#include <o3tl/safeint.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>

namespace
{
    void INFOBOX(weld::Window* pWindow, TranslateId id)
    {
        std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pWindow,
                                                      VclMessageType::Info, VclButtonsType::Ok,
                                                      ScResId(id)));
        xInfoBox->run();
    }
}

class ScAreaData
{
public:
    ScAreaData()
    {
    }

    void Set( const OUString& rName, const OUString& rArea )
    {
        aStrName  = rName;
        aStrArea  = rArea;
    }

    OUString  aStrName;
    OUString  aStrArea;
};

ScConsolidateDlg::ScConsolidateDlg(SfxBindings* pB, SfxChildWindow* pCW, weld::Window* pParent,
                                   const SfxItemSet& rArgSet)

    : ScAnyRefDlgController(pB, pCW, pParent, u"modules/scalc/ui/consolidatedialog.ui"_ustr, u"ConsolidateDialog"_ustr)
    , aStrUndefined   ( ScResId( SCSTR_UNDEFINED ) )
    , theConsData     ( static_cast<const ScConsolidateItem&>(
                         rArgSet.Get( rArgSet.GetPool()->
                                          GetWhichIDFromSlotID( SID_CONSOLIDATE ) )
                                    ).GetData() )
    , rViewData       ( static_cast<ScTabViewShell*>(SfxViewShell::Current())->
                              GetViewData() )
    , rDoc            ( static_cast<ScTabViewShell*>(SfxViewShell::Current())->
                              GetViewData().GetDocument() )
    , nAreaDataCount  ( 0 )
    , nWhichCons      ( rArgSet.GetPool()->GetWhichIDFromSlotID( SID_CONSOLIDATE ) )
    , bDlgLostFocus   ( false )
    , m_xLbFunc(m_xBuilder->weld_combo_box(u"func"_ustr))
    , m_xLbConsAreas(m_xBuilder->weld_tree_view(u"consareas"_ustr))
    , m_xLbDataArea(m_xBuilder->weld_combo_box(u"lbdataarea"_ustr))
    , m_xEdDataArea(new formula::RefEdit(m_xBuilder->weld_entry(u"eddataarea"_ustr)))
    , m_xRbDataArea(new formula::RefButton(m_xBuilder->weld_button(u"rbdataarea"_ustr)))
    , m_xLbDestArea(m_xBuilder->weld_combo_box(u"lbdestarea"_ustr))
    , m_xEdDestArea(new formula::RefEdit(m_xBuilder->weld_entry(u"eddestarea"_ustr)))
    , m_xRbDestArea(new formula::RefButton(m_xBuilder->weld_button(u"rbdestarea"_ustr)))
    , m_xExpander(m_xBuilder->weld_expander(u"more"_ustr))
    , m_xBtnByRow(m_xBuilder->weld_check_button(u"byrow"_ustr))
    , m_xBtnByCol(m_xBuilder->weld_check_button(u"bycol"_ustr))
    , m_xBtnRefs(m_xBuilder->weld_check_button(u"refs"_ustr))
    , m_xBtnOk(m_xBuilder->weld_button(u"ok"_ustr))
    , m_xBtnCancel(m_xBuilder->weld_button(u"cancel"_ustr))
    , m_xBtnAdd(m_xBuilder->weld_button(u"add"_ustr))
    , m_xBtnRemove(m_xBuilder->weld_button(u"delete"_ustr))
    , m_xDataFT(m_xBuilder->weld_label(u"ftdataarea"_ustr))
    , m_xDestFT(m_xBuilder->weld_label(u"ftdestarea"_ustr))
{
    m_pRefInputEdit = m_xEdDataArea.get();
    Init();
}

ScConsolidateDlg::~ScConsolidateDlg()
{
}

void ScConsolidateDlg::Init()
{
    OUString aStr;
    sal_uInt16 i=0;

    m_xRbDataArea->SetReferences(this, m_xEdDataArea.get());
    m_xEdDataArea->SetReferences(this, m_xDataFT.get());
    m_xRbDestArea->SetReferences(this, m_xEdDestArea.get());
    m_xEdDestArea->SetReferences(this, m_xDestFT.get());

    m_xEdDataArea->SetGetFocusHdl( LINK( this, ScConsolidateDlg, GetEditFocusHdl ) );
    m_xEdDestArea->SetGetFocusHdl( LINK( this, ScConsolidateDlg, GetEditFocusHdl ) );
    m_xLbDataArea->connect_focus_in( LINK( this, ScConsolidateDlg, GetFocusHdl ) );
    m_xLbDestArea->connect_focus_in( LINK( this, ScConsolidateDlg, GetFocusHdl ) );
    m_xEdDataArea->SetModifyHdl( LINK( this, ScConsolidateDlg, ModifyHdl ) );
    m_xEdDestArea->SetModifyHdl( LINK( this, ScConsolidateDlg, ModifyHdl ) );
    m_xLbConsAreas->connect_selection_changed(LINK(this, ScConsolidateDlg, SelectTVHdl));
    m_xLbDataArea->connect_changed( LINK( this, ScConsolidateDlg, SelectCBHdl ) );
    m_xLbDestArea->connect_changed( LINK( this, ScConsolidateDlg, SelectCBHdl ) );
    m_xBtnOk->connect_clicked( LINK( this, ScConsolidateDlg, OkHdl ) );
    m_xBtnCancel->connect_clicked( LINK( this, ScConsolidateDlg, ClickHdl ) );
    m_xBtnAdd->connect_clicked( LINK( this, ScConsolidateDlg, ClickHdl ) );
    m_xBtnRemove->connect_clicked( LINK( this, ScConsolidateDlg, ClickHdl ) );

    m_xBtnAdd->set_sensitive(false);
    m_xBtnRemove->set_sensitive(false);

    m_xBtnByRow->set_active( theConsData.bByRow );
    m_xBtnByCol->set_active( theConsData.bByCol );
    m_xBtnRefs->set_active( theConsData.bReferenceData );

    m_xLbFunc->set_active( FuncToLbPos( theConsData.eFunction ) );

    m_xLbConsAreas->set_selection_mode(SelectionMode::Multiple);
    m_xLbConsAreas->set_size_request(m_xLbConsAreas->get_approximate_digit_width() * 16,
                                     m_xLbConsAreas->get_height_rows(5));

    // read consolidation areas
    m_xLbConsAreas->clear();
    const formula::FormulaGrammar::AddressConvention eConv = rDoc.GetAddressConvention();
    for ( i=0; i<theConsData.nDataAreaCount; i++ )
    {
        const ScArea& rArea = theConsData.pDataAreas[i];
        if ( rArea.nTab < rDoc.GetTableCount() )
        {
            aStr = ScRange( rArea.nColStart, rArea.nRowStart, rArea.nTab,
                    rArea.nColEnd, rArea.nRowEnd, rArea.nTab ).Format( rDoc,
                        ScRefFlags::RANGE_ABS_3D, eConv );
            m_xLbConsAreas->append_text(aStr);
        }
    }

    if ( theConsData.nTab < rDoc.GetTableCount() )
    {
        aStr = ScAddress( theConsData.nCol, theConsData.nRow, theConsData.nTab
                ).Format( ScRefFlags::ADDR_ABS_3D, &rDoc, eConv );
        m_xEdDestArea->SetText( aStr );
    }
    else
        m_xEdDestArea->SetText(OUString());

    // Use the ScAreaData helper class to save those range names from the
    // RangeNames and database ranges that appear in the ListBoxes.

    ScRangeName*    pRangeNames  = rDoc.GetRangeName();
    ScDBCollection* pDbNames     = rDoc.GetDBCollection();
    size_t nRangeCount = pRangeNames ? pRangeNames->size() : 0;
    size_t nDbCount = pDbNames ? pDbNames->getNamedDBs().size() : 0;

    nAreaDataCount = nRangeCount+nDbCount;
    pAreaData      = nullptr;

    if ( nAreaDataCount > 0 )
    {
        pAreaData.reset( new ScAreaData[nAreaDataCount] );

        OUString aStrName;
        sal_uInt16 nAt = 0;
        ScRange aRange;
        ScAreaNameIterator aIter( rDoc );
        while ( aIter.Next( aStrName, aRange ) )
        {
            OUString aStrArea(aRange.Format(rDoc, ScRefFlags::ADDR_ABS_3D, eConv));
            pAreaData[nAt++].Set( aStrName, aStrArea );
        }
    }

    FillAreaLists();
    ModifyHdl( *m_xEdDestArea );
    m_xLbDataArea->set_active( 0 );
    m_xEdDataArea->SetText(OUString());
    m_xEdDataArea->GrabFocus();

    //aFlSep.SetStyle( aFlSep.GetStyle() | WB_VERT );

    //@BugID 54702 enable/disable only in base class
    //SFX_APPWINDOW->set_sensitive(true);
}

void ScConsolidateDlg::FillAreaLists()
{
    m_xLbDataArea->clear();
    m_xLbDestArea->clear();
    m_xLbDataArea->append_text( aStrUndefined );
    m_xLbDestArea->append_text( aStrUndefined );

    if ( pAreaData && (nAreaDataCount > 0) )
    {
        for ( size_t i=0;
              (i<nAreaDataCount) && (!pAreaData[i].aStrName.isEmpty());
              i++ )
        {
            m_xLbDataArea->append_text(pAreaData[i].aStrName);
            m_xLbDestArea->append_text(pAreaData[i].aStrName);
        }
    }
}

// Handover of a range within a table that has been selected by the mouse.
// This range is then shown in the reference window as new selection.

void ScConsolidateDlg::SetReference( const ScRange& rRef, ScDocument& rDocP )
{
    if ( !m_pRefInputEdit )
        return;

    if ( rRef.aStart != rRef.aEnd )
        RefInputStart( m_pRefInputEdit );

    OUString      aStr;
    ScRefFlags      nFmt = ScRefFlags::RANGE_ABS_3D;       //!!! nCurTab is still missing
    const formula::FormulaGrammar::AddressConvention eConv = rDocP.GetAddressConvention();

    if ( rRef.aStart.Tab() != rRef.aEnd.Tab() )
        nFmt |= ScRefFlags::TAB2_3D;

    if ( m_pRefInputEdit == m_xEdDataArea.get())
        aStr = rRef.Format(rDocP, nFmt, eConv);
    else if ( m_pRefInputEdit == m_xEdDestArea.get() )
        aStr = rRef.aStart.Format(nFmt, &rDocP, eConv);

    m_pRefInputEdit->SetRefString( aStr );
    ModifyHdl( *m_pRefInputEdit );
}

void ScConsolidateDlg::Close()
{
    DoClose( ScConsolidateDlgWrapper::GetChildWindowId() );
}

void ScConsolidateDlg::SetActive()
{
    if ( bDlgLostFocus )
    {
        bDlgLostFocus = false;

        if ( m_pRefInputEdit )
        {
            m_pRefInputEdit->GrabFocus();
            ModifyHdl( *m_pRefInputEdit );
        }
    }
    else
        m_xDialog->grab_focus();

    RefInputDone();
}

void ScConsolidateDlg::Deactivate()
{
    bDlgLostFocus = true;
}

bool ScConsolidateDlg::VerifyEdit( formula::RefEdit* pEd )
{
    if (pEd != m_xEdDataArea.get() && pEd != m_xEdDestArea.get())
        return false;

    SCTAB nTab = rViewData.CurrentTabForData();
    bool bEditOk = false;
    OUString theCompleteStr;
    const formula::FormulaGrammar::AddressConvention eConv = rDoc.GetAddressConvention();

    if ( pEd == m_xEdDataArea.get() )
    {
        bEditOk = ScRangeUtil::IsAbsArea( pEd->GetText(), rDoc,
                                         nTab, &theCompleteStr, nullptr, nullptr, eConv );
    }
    else if ( pEd == m_xEdDestArea.get() )
    {
        OUString aPosStr;

        ScRangeUtil::CutPosString( pEd->GetText(), aPosStr );
        bEditOk = ScRangeUtil::IsAbsPos( aPosStr, rDoc,
                                        nTab, &theCompleteStr, nullptr, eConv );
    }

    if ( bEditOk )
        pEd->SetText( theCompleteStr );

    return bEditOk;
}

// Handler:

IMPL_LINK( ScConsolidateDlg, GetEditFocusHdl, formula::RefEdit&, rControl, void )
{
    m_pRefInputEdit = &rControl;
}

IMPL_LINK( ScConsolidateDlg, GetFocusHdl, weld::Widget&, rControl, void )
{
    if (&rControl == m_xLbDataArea.get())
        m_pRefInputEdit = m_xEdDataArea.get();
    else if (&rControl == m_xLbDestArea.get())
        m_pRefInputEdit = m_xEdDestArea.get();
}

IMPL_LINK_NOARG(ScConsolidateDlg, OkHdl, weld::Button&, void)
{
    const sal_Int32 nDataAreaCount = m_xLbConsAreas->n_children();

    if ( nDataAreaCount > 0 )
    {
        ScRefAddress aDestAddress;
        SCTAB nTab = rViewData.CurrentTabForData();
        OUString    aDestPosStr( m_xEdDestArea->GetText() );
        const formula::FormulaGrammar::AddressConvention eConv = rDoc.GetAddressConvention();

        if ( ScRangeUtil::IsAbsPos( aDestPosStr, rDoc, nTab, nullptr, &aDestAddress, eConv ) )
        {
            ScConsolidateParam  theOutParam( theConsData );
            std::unique_ptr<ScArea[]> pDataAreas(new ScArea[nDataAreaCount]);

            for ( sal_Int32 i=0; i<nDataAreaCount; ++i )
            {
                ScRangeUtil::MakeArea(m_xLbConsAreas->get_text(i),
                                      pDataAreas[i], rDoc, nTab, eConv);
            }

            theOutParam.nCol            = aDestAddress.Col();
            theOutParam.nRow            = aDestAddress.Row();
            theOutParam.nTab            = aDestAddress.Tab();
            theOutParam.eFunction       = LbPosToFunc( m_xLbFunc->get_active() );
            theOutParam.bByCol          = m_xBtnByCol->get_active();
            theOutParam.bByRow          = m_xBtnByRow->get_active();
            theOutParam.bReferenceData  = m_xBtnRefs->get_active();
            theOutParam.SetAreas( std::move(pDataAreas), nDataAreaCount );

            ScConsolidateItem aOutItem( nWhichCons, &theOutParam );

            SetDispatcherLock( false );
            SwitchToDocument();
            GetBindings().GetDispatcher()->ExecuteList(SID_CONSOLIDATE,
                                      SfxCallMode::SLOT | SfxCallMode::RECORD,
                                      { &aOutItem });
            response(RET_OK);
        }
        else
        {
            INFOBOX(m_xDialog.get(), STR_INVALID_TABREF);
            m_xEdDestArea->GrabFocus();
        }
    }
    else
        response(RET_CANCEL); // no area defined -> Cancel
}

IMPL_LINK( ScConsolidateDlg, ClickHdl, weld::Button&, rBtn, void )
{
    if ( &rBtn == m_xBtnCancel.get() )
        response(RET_CANCEL);
    else if ( &rBtn == m_xBtnAdd.get() )
    {
        if ( !m_xEdDataArea->GetText().isEmpty() )
        {
            OUString    aNewEntry( m_xEdDataArea->GetText() );
            std::unique_ptr<ScArea[]> ppAreas;
            sal_uInt16      nAreaCount = 0;
            const formula::FormulaGrammar::AddressConvention eConv = rDoc.GetAddressConvention();

            if ( ScRangeUtil::IsAbsTabArea( aNewEntry, &rDoc, &ppAreas, &nAreaCount, true, eConv ) )
            {
                // IsAbsTabArea() creates an array of ScArea pointers,
                // which have been created dynamically as well.
                // These objects need to be deleted here.

                for ( sal_uInt16 i=0; i<nAreaCount; i++ )
                {
                    const ScArea& rArea = ppAreas[i];
                    OUString aNewArea = ScRange( rArea.nColStart, rArea.nRowStart, rArea.nTab,
                            rArea.nColEnd, rArea.nRowEnd, rArea.nTab
                            ).Format(rDoc, ScRefFlags::RANGE_ABS_3D, eConv);

                    if (m_xLbConsAreas->find_text(aNewArea) == -1)
                    {
                        m_xLbConsAreas->append_text( aNewArea );
                    }
                }
            }
            else if ( VerifyEdit( m_xEdDataArea.get() ) )
            {
                OUString aNewArea( m_xEdDataArea->GetText() );

                if (m_xLbConsAreas->find_text(aNewArea) == -1)
                    m_xLbConsAreas->append_text(aNewArea);
                else
                    INFOBOX(m_xDialog.get(), STR_AREA_ALREADY_INSERTED);
            }
            else
            {
                INFOBOX(m_xDialog.get(), STR_INVALID_TABREF);
                m_xEdDataArea->GrabFocus();
            }
        }
    }
    else if ( &rBtn == m_xBtnRemove.get() )
    {
        std::vector<int> aSelectedRows(m_xLbConsAreas->get_selected_rows());
        std::sort(aSelectedRows.begin(), aSelectedRows.end());
        for (auto it = aSelectedRows.rbegin(); it != aSelectedRows.rend(); ++it)
            m_xLbConsAreas->remove(*it);
        m_xBtnRemove->set_sensitive(false);
    }
}

IMPL_LINK( ScConsolidateDlg, SelectTVHdl, weld::TreeView&, rLb, void )
{
    if (rLb.get_selected_index() != -1)
        m_xBtnRemove->set_sensitive(true);
    else
        m_xBtnRemove->set_sensitive(false);
}

IMPL_LINK( ScConsolidateDlg, SelectCBHdl, weld::ComboBox&, rLb, void )
{
    formula::RefEdit* pEd = (&rLb == m_xLbDataArea.get()) ? m_xEdDataArea.get() : m_xEdDestArea.get();
    const sal_Int32 nSelPos = rLb.get_active();

    if (   (nSelPos > 0)
        && (nAreaDataCount > 0)
        && (pAreaData != nullptr) )
    {
        if ( o3tl::make_unsigned(nSelPos) <= nAreaDataCount )
        {
            OUString aString( pAreaData[nSelPos-1].aStrArea );

            if ( &rLb == m_xLbDestArea.get() )
                ScRangeUtil::CutPosString( aString, aString );

            pEd->SetText( aString );

            if ( pEd == m_xEdDataArea.get() )
                m_xBtnAdd->set_sensitive(true);
        }
    }
    else
    {
        pEd->SetText( OUString() );
        if ( pEd == m_xEdDataArea.get() )
            m_xBtnAdd->set_sensitive(true);
    }
}

IMPL_LINK( ScConsolidateDlg, ModifyHdl, formula::RefEdit&, rEd, void )
{
    if ( &rEd == m_xEdDataArea.get() )
    {
        OUString aAreaStr( rEd.GetText() );
        if ( !aAreaStr.isEmpty() )
            m_xBtnAdd->set_sensitive(true);
        else
            m_xBtnAdd->set_sensitive(false);
    }
    else if ( &rEd == m_xEdDestArea.get() )
    {
        m_xLbDestArea->set_active(0);
    }
}

// TODO: generalize!
// Resource of the ListBox and these two conversion methods are also in
// tpsubt and everywhere, where StarCalc functions are selectable.

ScSubTotalFunc ScConsolidateDlg::LbPosToFunc( sal_Int32 nPos )
{
    switch ( nPos )
    {
        case  2:    return SUBTOTAL_FUNC_AVE;
        case  6:    return SUBTOTAL_FUNC_CNT;
        case  1:    return SUBTOTAL_FUNC_CNT2;
        case  3:    return SUBTOTAL_FUNC_MAX;
        case  4:    return SUBTOTAL_FUNC_MIN;
        case  5:    return SUBTOTAL_FUNC_PROD;
        case  7:    return SUBTOTAL_FUNC_STD;
        case  8:    return SUBTOTAL_FUNC_STDP;
        case  9:    return SUBTOTAL_FUNC_VAR;
        case 10:    return SUBTOTAL_FUNC_VARP;
        case  0:
        default:
            return SUBTOTAL_FUNC_SUM;
    }
}

sal_Int32 ScConsolidateDlg::FuncToLbPos( ScSubTotalFunc eFunc )
{
    switch ( eFunc )
    {
        case SUBTOTAL_FUNC_AVE:     return 2;
        case SUBTOTAL_FUNC_CNT:     return 6;
        case SUBTOTAL_FUNC_CNT2:    return 1;
        case SUBTOTAL_FUNC_MAX:     return 3;
        case SUBTOTAL_FUNC_MIN:     return 4;
        case SUBTOTAL_FUNC_PROD:    return 5;
        case SUBTOTAL_FUNC_STD:     return 7;
        case SUBTOTAL_FUNC_STDP:    return 8;
        case SUBTOTAL_FUNC_VAR:     return 9;
        case SUBTOTAL_FUNC_VARP:    return 10;
        case SUBTOTAL_FUNC_NONE:
        case SUBTOTAL_FUNC_SUM:
        default:
            return 0;
    }
}

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