/* -*- 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 <uiitems.hxx>
#include <rangenam.hxx>
#include <reffact.hxx>
#include <viewdata.hxx>
#include <document.hxx>
#include <docsh.hxx>
#include <scresid.hxx>

#include <foptmgr.hxx>

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

#include <filtdlg.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>

// DEFINE --------------------------------------------------------------------

namespace
{
    void ERRORBOX(weld::Window* pParent, TranslateId rid)
    {
        std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent,
                                                  VclMessageType::Warning, VclButtonsType::Ok,
                                                  ScResId(rid)));
        xBox->run();
    }
}


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

    : ScAnyRefDlgController(pB, pCW, pParent, u"modules/scalc/ui/advancedfilterdialog.ui"_ustr, u"AdvancedFilterDialog"_ustr)
    , aStrUndefined   ( ScResId(SCSTR_UNDEFINED) )
    , nWhichQuery     ( rArgSet.GetPool()->GetWhichIDFromSlotID( SID_QUERY ) )
    , theQueryData    ( static_cast<const ScQueryItem&>(
                           rArgSet.Get( nWhichQuery )).GetQueryData() )
    , rViewData(rData)
    , rDoc(rViewData.GetDocument())
    , bRefInputMode(false)
    , m_pRefInputEdit(nullptr)
    , m_xLbFilterArea(m_xBuilder->weld_combo_box(u"lbfilterarea"_ustr))
    , m_xEdFilterArea(new formula::RefEdit(m_xBuilder->weld_entry(u"edfilterarea"_ustr)))
    , m_xRbFilterArea(new formula::RefButton(m_xBuilder->weld_button(u"rbfilterarea"_ustr)))
    , m_xExpander(m_xBuilder->weld_expander(u"more"_ustr))
    , m_xBtnCase(m_xBuilder->weld_check_button(u"case"_ustr))
    , m_xBtnRegExp(m_xBuilder->weld_check_button(u"regexp"_ustr))
    , m_xBtnHeader(m_xBuilder->weld_check_button(u"header"_ustr))
    , m_xBtnUnique(m_xBuilder->weld_check_button(u"unique"_ustr))
    , m_xBtnCopyResult(m_xBuilder->weld_check_button(u"copyresult"_ustr))
    , m_xLbCopyArea(m_xBuilder->weld_combo_box(u"lbcopyarea"_ustr))
    , m_xEdCopyArea(new formula::RefEdit(m_xBuilder->weld_entry(u"edcopyarea"_ustr)))
    , m_xRbCopyArea(new formula::RefButton(m_xBuilder->weld_button(u"rbcopyarea"_ustr)))
    , m_xBtnDestPers(m_xBuilder->weld_check_button(u"destpers"_ustr))
    , m_xFtDbAreaLabel(m_xBuilder->weld_label(u"dbarealabel"_ustr))
    , m_xFtDbArea(m_xBuilder->weld_label(u"dbarea"_ustr))
    , m_xBtnOk(m_xBuilder->weld_button(u"ok"_ustr))
    , m_xBtnCancel(m_xBuilder->weld_button(u"cancel"_ustr))
    , m_xFilterFrame(m_xBuilder->weld_frame(u"filterframe"_ustr))
{
    m_xEdFilterArea->SetReferences(this, m_xFilterFrame.get());
    m_xRbFilterArea->SetReferences(this, m_xEdFilterArea.get());
    m_xEdCopyArea->SetReferences(this, m_xFtDbAreaLabel.get());
    m_xRbCopyArea->SetReferences(this, m_xEdCopyArea.get());

    Init( rArgSet );

    Link<formula::RefEdit&, void> aLinkEdit = LINK(this, ScSpecialFilterDlg, RefInputEditHdl);
    Link<formula::RefButton&, void> aLinkButton = LINK(this, ScSpecialFilterDlg, RefInputButtonHdl);
    m_xEdCopyArea->SetGetFocusHdl(aLinkEdit);
    m_xRbCopyArea->SetGetFocusHdl(aLinkButton);
    m_xEdFilterArea->SetGetFocusHdl(aLinkEdit);
    m_xRbFilterArea->SetGetFocusHdl(aLinkButton);
    m_xEdCopyArea->SetLoseFocusHdl(aLinkEdit);
    m_xRbCopyArea->SetLoseFocusHdl(aLinkButton);
    m_xEdFilterArea->SetLoseFocusHdl(aLinkEdit);
    m_xRbFilterArea->SetLoseFocusHdl(aLinkButton);

    m_xEdFilterArea->GrabFocus();
}

ScSpecialFilterDlg::~ScSpecialFilterDlg()
{
    pOptionsMgr.reset();

    pOutItem.reset();
}

void ScSpecialFilterDlg::Init( const SfxItemSet& rArgSet )
{
    m_xBtnOk->connect_clicked( LINK( this, ScSpecialFilterDlg, EndDlgHdl ) );
    m_xBtnCancel->connect_clicked( LINK( this, ScSpecialFilterDlg, EndDlgHdl ) );
    m_xLbFilterArea->connect_changed( LINK( this, ScSpecialFilterDlg, FilterAreaSelHdl ) );
    m_xEdFilterArea->SetModifyHdl  ( LINK( this, ScSpecialFilterDlg, FilterAreaModHdl ) );

    m_xEdFilterArea->SetText( OUString() );      // may be overwritten below

    if(rDoc.GetChangeTrack()!=nullptr) m_xBtnCopyResult->set_sensitive(false);

    ScRangeName* pRangeNames = rDoc.GetRangeName();
    m_xLbFilterArea->clear();
    m_xLbFilterArea->append_text(aStrUndefined);

    for (const auto& rEntry : *pRangeNames)
    {
        if (!rEntry.second->HasType(ScRangeData::Type::Criteria))
            continue;

        OUString aSymbol = rEntry.second->GetSymbol();
        m_xLbFilterArea->append(aSymbol, rEntry.second->GetName());
    }

    //  is there a stored source range?

    ScRange aAdvSource;
    const ScQueryItem& rQueryItem = static_cast<const ScQueryItem&>(
                                    rArgSet.Get( nWhichQuery ));

    if (rQueryItem.GetAdvancedQuerySource(aAdvSource))
    {
        OUString aRefStr(aAdvSource.Format(rDoc, ScRefFlags::RANGE_ABS_3D, rDoc.GetAddressConvention()));
        m_xEdFilterArea->SetRefString( aRefStr );
    }

    m_xLbFilterArea->set_active( 0 );

    // let options be initialized:

    pOptionsMgr.reset( new ScFilterOptionsMgr(
                            rViewData,
                            theQueryData,
                            m_xBtnCase.get(),
                            m_xBtnRegExp.get(),
                            m_xBtnHeader.get(),
                            m_xBtnUnique.get(),
                            m_xBtnCopyResult.get(),
                            m_xBtnDestPers.get(),
                            m_xLbCopyArea.get(),
                            m_xEdCopyArea.get(),
                            m_xRbCopyArea.get(),
                            m_xFtDbAreaLabel.get(),
                            m_xFtDbArea.get(),
                            aStrUndefined ) );

    //  special filter always needs column headers
    m_xBtnHeader->set_active(true);
    m_xBtnHeader->set_sensitive(false);

    // turn on modal mode
    // SetDispatcherLock( true );
    //@BugID 54702 enable/disable in base class only
    //SFX_APPWINDOW->Disable(false);        //! general method in ScAnyRefDlg
}

void ScSpecialFilterDlg::Close()
{
    rViewData.GetDocShell()->CancelAutoDBRange();

    DoClose( ScSpecialFilterDlgWrapper::GetChildWindowId() );
}

// Transfer of a table area selected with the mouse, which is then displayed
// as a new selection in the reference edit.

void ScSpecialFilterDlg::SetReference( const ScRange& rRef, ScDocument& rDocP )
{
    if ( !(bRefInputMode && m_pRefInputEdit) )       // only possible if in the reference edit mode
        return;

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

    OUString aRefStr;
    const formula::FormulaGrammar::AddressConvention eConv = rDocP.GetAddressConvention();

    if (m_pRefInputEdit == m_xEdCopyArea.get())
        aRefStr = rRef.aStart.Format(ScRefFlags::ADDR_ABS_3D, &rDocP, eConv);
    else if (m_pRefInputEdit == m_xEdFilterArea.get())
        aRefStr = rRef.Format(rDocP, ScRefFlags::RANGE_ABS_3D, eConv);

    m_pRefInputEdit->SetRefString( aRefStr );
}

void ScSpecialFilterDlg::SetActive()
{
    if ( bRefInputMode )
    {
        if (m_pRefInputEdit == m_xEdCopyArea.get())
        {
            m_xEdCopyArea->GrabFocus();
            m_xEdCopyArea->GetModifyHdl().Call( *m_xEdCopyArea );
        }
        else if (m_pRefInputEdit == m_xEdFilterArea.get())
        {
            m_xEdFilterArea->GrabFocus();
            FilterAreaModHdl( *m_xEdFilterArea );
        }
    }
    else
        m_xDialog->grab_focus();

    RefInputDone();
}

ScQueryItem* ScSpecialFilterDlg::GetOutputItem( const ScQueryParam& rParam,
                                                const ScRange& rSource )
{
    pOutItem.reset(new ScQueryItem( nWhichQuery, &rParam ));
    pOutItem->SetAdvancedQuerySource( &rSource );
    return pOutItem.get();
}

bool ScSpecialFilterDlg::IsRefInputMode() const
{
    return bRefInputMode;
}

// Handler:

IMPL_LINK(ScSpecialFilterDlg, EndDlgHdl, weld::Button&, rBtn, void)
{
    if (&rBtn == m_xBtnOk.get())
    {
        OUString          theCopyStr( m_xEdCopyArea->GetText() );
        OUString          theAreaStr( m_xEdFilterArea->GetText() );
        ScQueryParam    theOutParam( theQueryData );
        ScAddress       theAdrCopy;
        bool            bEditInputOk    = true;
        bool            bQueryOk        = false;
        ScRange         theFilterArea;
        const formula::FormulaGrammar::AddressConvention eConv = rDoc.GetAddressConvention();

        if ( m_xBtnCopyResult->get_active() )
        {
            sal_Int32 nColonPos = theCopyStr.indexOf( ':' );

            if ( -1 != nColonPos )
                theCopyStr = theCopyStr.copy( 0, nColonPos );

            ScRefFlags nResult = theAdrCopy.Parse( theCopyStr, rDoc, eConv );

            if ( (nResult & ScRefFlags::VALID) == ScRefFlags::ZERO )
            {
                if (!m_xExpander->get_expanded())
                    m_xExpander->set_expanded(true);

                ERRORBOX(m_xDialog.get(), STR_INVALID_TABREF);
                m_xEdCopyArea->GrabFocus();
                bEditInputOk = false;
            }
        }

        if ( bEditInputOk )
        {
            ScRefFlags nResult = ScRange().Parse( theAreaStr, rDoc, eConv );

            if ( (nResult & ScRefFlags::VALID) == ScRefFlags::ZERO )
            {
                ERRORBOX(m_xDialog.get(), STR_INVALID_TABREF);
                m_xEdFilterArea->GrabFocus();
                bEditInputOk = false;
            }
        }

        if ( bEditInputOk )
        {
            /*
             * All edit fields contain valid areas. Now try to create
             * a ScQueryParam from the filter area:
             */

            ScRefFlags  nResult = theFilterArea.Parse( theAreaStr, rDoc, eConv );

            if ( (nResult & ScRefFlags::VALID) == ScRefFlags::VALID )
            {
                ScAddress& rStart = theFilterArea.aStart;
                ScAddress& rEnd   = theFilterArea.aEnd;

                if ( m_xBtnCopyResult->get_active() )
                {
                    theOutParam.bInplace    = false;
                    theOutParam.nDestTab    = theAdrCopy.Tab();
                    theOutParam.nDestCol    = theAdrCopy.Col();
                    theOutParam.nDestRow    = theAdrCopy.Row();
                }
                else
                {
                    theOutParam.bInplace    = true;
                    theOutParam.nDestTab    = 0;
                    theOutParam.nDestCol    = 0;
                    theOutParam.nDestRow    = 0;
                }

                theOutParam.bHasHeader = m_xBtnHeader->get_active();
                theOutParam.bByRow     = true;
                theOutParam.bCaseSens  = m_xBtnCase->get_active();
                theOutParam.eSearchType = m_xBtnRegExp->get_active() ? utl::SearchParam::SearchType::Regexp :
                    utl::SearchParam::SearchType::Normal;
                theOutParam.bDuplicate = !m_xBtnUnique->get_active();
                theOutParam.bDestPers  = m_xBtnDestPers->get_active();

                bQueryOk = rDoc.CreateQueryParam(ScRange(rStart,rEnd), theOutParam);
            }
        }

        if ( bQueryOk )
        {
            SetDispatcherLock( false );
            SwitchToDocument();
            GetBindings().GetDispatcher()->ExecuteList(FID_FILTER_OK,
                    SfxCallMode::SLOT | SfxCallMode::RECORD,
                    { GetOutputItem(theOutParam, theFilterArea) });
            response(RET_OK);
        }
        else
        {
            ERRORBOX(m_xDialog.get(), STR_INVALID_QUERYAREA);
            m_xEdFilterArea->GrabFocus();
        }
    }
    else if (&rBtn == m_xBtnCancel.get())
    {
        response(RET_CANCEL);
    }
}

IMPL_LINK_NOARG(ScSpecialFilterDlg, RefInputEditHdl, formula::RefEdit&, void)
{
    RefInputHdl();
}

IMPL_LINK_NOARG(ScSpecialFilterDlg, RefInputButtonHdl, formula::RefButton&, void)
{
    RefInputHdl();
}

void ScSpecialFilterDlg::RefInputHdl()
{
    if (!m_xDialog->has_toplevel_focus())
        return;

    if( m_xEdCopyArea->GetWidget()->has_focus() || m_xRbCopyArea->GetWidget()->has_focus() )
    {
        m_pRefInputEdit = m_xEdCopyArea.get();
        bRefInputMode = true;
    }
    else if( m_xEdFilterArea->GetWidget()->has_focus() || m_xRbFilterArea->GetWidget()->has_focus() )
    {
        m_pRefInputEdit = m_xEdFilterArea.get();
        bRefInputMode = true;
    }
    else if( bRefInputMode )
    {
        m_pRefInputEdit = nullptr;
        bRefInputMode = false;
    }
}

IMPL_LINK(ScSpecialFilterDlg, FilterAreaSelHdl, weld::ComboBox&, rLb, void)
{
    if (&rLb == m_xLbFilterArea.get())
    {
        OUString  aString;
        const sal_Int32 nSelPos = m_xLbFilterArea->get_active();

        if ( nSelPos > 0 )
            aString = m_xLbFilterArea->get_id(nSelPos);

        m_xEdFilterArea->SetText( aString );
    }
}

IMPL_LINK( ScSpecialFilterDlg, FilterAreaModHdl, formula::RefEdit&, rEd, void )
{
    if (&rEd != m_xEdFilterArea.get())
        return;

    OUString  theCurAreaStr = rEd.GetText();
    ScRefFlags  nResult = ScRange().Parse( theCurAreaStr, rDoc );

    if ( (nResult & ScRefFlags::VALID) == ScRefFlags::VALID )
    {
        const sal_Int32 nCount  = m_xLbFilterArea->get_count();
        for (sal_Int32 i = 1; i < nCount; ++i)
        {
            OUString aStr = m_xLbFilterArea->get_id(i);
            if (theCurAreaStr == aStr)
            {
                m_xLbFilterArea->set_active( i );
                return;
            }
        }
        m_xLbFilterArea->set_active( 0 );
    }
}

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