/* -*- 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 <memory>
#include <sal/config.h>

#include <cassert>

#include <comphelper/string.hxx>
#include <unotools/charclass.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>

#include <reffact.hxx>
#include <document.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <rangenam.hxx>
#include <globalnames.hxx>
#include <dbnamdlg.hxx>
#include <dbdocfun.hxx>

class DBSaveData;

static DBSaveData* pSaveObj = nullptr;

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

//  class DBSaveData

class DBSaveData
{
public:
    DBSaveData( formula::WeldRefEdit& rEd, weld::CheckButton& rHdr, weld::CheckButton& rTot, weld::CheckButton& rSize, weld::CheckButton& rFmt,
                            weld::CheckButton& rStrip, ScRange& rArea )
        : rEdAssign(rEd)
        , rBtnHeader(rHdr)
        , rBtnTotals(rTot)
        , rBtnSize(rSize)
        , rBtnFormat(rFmt)
        , rBtnStrip(rStrip)
        , rCurArea(rArea)
        , bHeader(false)
        , bTotals(false)
        , bSize(false)
        , bFormat(false)
        , bStrip(false)
        , bDirty(false)
    {
    }
    void Save();
    void Restore();

private:
    formula::WeldRefEdit& rEdAssign;
    weld::CheckButton& rBtnHeader;
    weld::CheckButton& rBtnTotals;
    weld::CheckButton& rBtnSize;
    weld::CheckButton& rBtnFormat;
    weld::CheckButton& rBtnStrip;
    ScRange&    rCurArea;
    OUString    aStr;
    ScRange     aArea;
    bool        bHeader:1;
    bool        bTotals:1;
    bool        bSize:1;
    bool        bFormat:1;
    bool        bStrip:1;
    bool        bDirty:1;
};

void DBSaveData::Save()
{
    aArea   = rCurArea;
    aStr    = rEdAssign.GetText();
    bHeader = rBtnHeader.get_active();
    bTotals = rBtnTotals.get_active();
    bSize   = rBtnSize.get_active();
    bFormat = rBtnFormat.get_active();
    bStrip  = rBtnStrip.get_active();
    bDirty  = true;
}

void DBSaveData::Restore()
{
    if ( bDirty )
    {
        rCurArea = aArea;
        rEdAssign.SetText( aStr );
        rBtnHeader.set_active ( bHeader );
        rBtnTotals.set_active ( bTotals );
        rBtnSize.set_active   ( bSize );
        rBtnFormat.set_active ( bFormat );
        rBtnStrip.set_active  ( bStrip );
        bDirty = false;
    }
}

//  class ScDbNameDlg

ScDbNameDlg::ScDbNameDlg(SfxBindings* pB, SfxChildWindow* pCW, weld::Window* pParent,
    ScViewData* ptrViewData)
    : ScAnyRefDlgController(pB, pCW, pParent,
        "modules/scalc/ui/definedatabaserangedialog.ui", "DefineDatabaseRangeDialog")
    , pViewData(ptrViewData)
    , pDoc(ptrViewData->GetDocument())
    , bRefInputMode(false)
    , aAddrDetails(pDoc->GetAddressConvention(), 0, 0)
    , aLocalDbCol(*(pDoc->GetDBCollection()))
    , m_xEdName(m_xBuilder->weld_entry_tree_view("entrygrid", "entry", "entry-list"))
    , m_xAssignFrame(m_xBuilder->weld_frame("RangeFrame"))
    , m_xEdAssign(new formula::WeldRefEdit(m_xBuilder->weld_entry("assign")))
    , m_xRbAssign(new formula::WeldRefButton(m_xBuilder->weld_button("assignrb")))
    , m_xOptions(m_xBuilder->weld_widget("Options"))
    , m_xBtnHeader(m_xBuilder->weld_check_button("ContainsColumnLabels"))
    , m_xBtnTotals(m_xBuilder->weld_check_button("ContainsTotalsRow"))
    , m_xBtnDoSize(m_xBuilder->weld_check_button("InsertOrDeleteCells"))
    , m_xBtnKeepFmt(m_xBuilder->weld_check_button("KeepFormatting"))
    , m_xBtnStripData(m_xBuilder->weld_check_button("DontSaveImportedData"))
    , m_xFTSource(m_xBuilder->weld_label("Source"))
    , m_xFTOperations(m_xBuilder->weld_label("Operations"))
    , m_xBtnOk(m_xBuilder->weld_button("ok"))
    , m_xBtnCancel(m_xBuilder->weld_button("cancel"))
    , m_xBtnAdd(m_xBuilder->weld_button("add"))
    , m_xBtnRemove(m_xBuilder->weld_button("delete"))
    , m_xModifyPB(m_xBuilder->weld_button("modify"))
    , m_xInvalidFT(m_xBuilder->weld_label("invalid"))
    , m_xFrameLabel(m_xAssignFrame->weld_label_widget())
    , m_xExpander(m_xBuilder->weld_expander("more"))
{
    m_xEdName->set_height_request_by_rows(8);
    m_xEdAssign->SetReferences(this, m_xFrameLabel.get());
    m_xRbAssign->SetReferences(this, m_xEdAssign.get());
    aStrAdd = m_xBtnAdd->get_label();
    aStrModify = m_xModifyPB->get_label();
    aStrInvalid = m_xInvalidFT->get_label();

    //  so that the strings in the resource can stay with fixed texts:
    aStrSource      = m_xFTSource->get_label();
    aStrOperations  = m_xFTOperations->get_label();

    pSaveObj = new DBSaveData( *m_xEdAssign, *m_xBtnHeader, *m_xBtnTotals,
                        *m_xBtnDoSize, *m_xBtnKeepFmt, *m_xBtnStripData, theCurArea );
    Init();
}

ScDbNameDlg::~ScDbNameDlg()
{
    DELETEZ( pSaveObj );
}

void ScDbNameDlg::Init()
{
    m_xBtnHeader->set_active(true);          // Default: with column headers
    m_xBtnTotals->set_active( false );   // Default: without totals row
    m_xBtnDoSize->set_active(true);
    m_xBtnKeepFmt->set_active(true);

    m_xBtnOk->connect_clicked      ( LINK( this, ScDbNameDlg, OkBtnHdl ) );
    m_xBtnCancel->connect_clicked  ( LINK( this, ScDbNameDlg, CancelBtnHdl ) );
    m_xBtnAdd->connect_clicked     ( LINK( this, ScDbNameDlg, AddBtnHdl ) );
    m_xBtnRemove->connect_clicked  ( LINK( this, ScDbNameDlg, RemoveBtnHdl ) );
    m_xEdName->connect_changed( LINK( this, ScDbNameDlg, NameModifyHdl ) );
    m_xEdAssign->SetModifyHdl  ( LINK( this, ScDbNameDlg, AssModifyHdl ) );
    UpdateNames();

    OUString  theAreaStr;

    if ( pViewData && pDoc )
    {
        SCCOL   nStartCol   = 0;
        SCROW   nStartRow   = 0;
        SCTAB   nStartTab   = 0;
        SCCOL   nEndCol     = 0;
        SCROW   nEndRow     = 0;
        SCTAB   nEndTab     = 0;

        ScDBCollection* pDBColl = pDoc->GetDBCollection();
        ScDBData*       pDBData = nullptr;

        pViewData->GetSimpleArea( nStartCol, nStartRow, nStartTab,
                                  nEndCol,   nEndRow,  nEndTab );

        theCurArea = ScRange( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab);

        theAreaStr = theCurArea.Format(ScRefFlags::RANGE_ABS_3D, pDoc, aAddrDetails);

        if ( pDBColl )
        {
            // determine if the defined DB area has been marked:
            pDBData = pDBColl->GetDBAtCursor( nStartCol, nStartRow, nStartTab, ScDBDataPortion::TOP_LEFT );
            if ( pDBData )
            {
                ScAddress&  rStart = theCurArea.aStart;
                ScAddress&  rEnd   = theCurArea.aEnd;
                SCCOL nCol1;
                SCCOL  nCol2;
                SCROW  nRow1;
                SCROW  nRow2;
                SCTAB  nTab;

                pDBData->GetArea( nTab, nCol1, nRow1, nCol2, nRow2 );

                if (   (rStart.Tab() == nTab)
                    && (rStart.Col() == nCol1) && (rStart.Row() == nRow1)
                    && (rEnd.Col()   == nCol2) && (rEnd.Row()   == nRow2 ) )
                {
                    OUString aDBName = pDBData->GetName();
                    if ( aDBName != STR_DB_LOCAL_NONAME )
                        m_xEdName->set_entry_text(aDBName);

                    m_xBtnHeader->set_active( pDBData->HasHeader() );
                    m_xBtnTotals->set_active( pDBData->HasTotals() );
                    m_xBtnDoSize->set_active( pDBData->IsDoSize() );
                    m_xBtnKeepFmt->set_active( pDBData->IsKeepFmt() );
                    m_xBtnStripData->set_active( pDBData->IsStripData() );
                    SetInfoStrings( pDBData );
                }
            }
        }
    }

    m_xEdAssign->SetText( theAreaStr );
    m_xEdName->grab_focus();
    bSaved = true;
    pSaveObj->Save();
    NameModifyHdl( *m_xEdName );
}

void ScDbNameDlg::SetInfoStrings( const ScDBData* pDBData )
{
    OUStringBuffer aBuf;
    aBuf.append(aStrSource);
    if (pDBData)
    {
        aBuf.append(' ');
        aBuf.append(pDBData->GetSourceString());
    }
    m_xFTSource->set_label(aBuf.makeStringAndClear());

    aBuf.append(aStrOperations);
    if (pDBData)
    {
        aBuf.append(' ');
        aBuf.append(pDBData->GetOperations());
    }
    m_xFTOperations->set_label(aBuf.makeStringAndClear());
}

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

void ScDbNameDlg::SetReference( const ScRange& rRef, ScDocument* pDocP )
{
    if (m_xEdAssign->GetWidget()->get_sensitive())
    {
        if ( rRef.aStart != rRef.aEnd )
            RefInputStart(m_xEdAssign.get());

        theCurArea = rRef;

        OUString aRefStr(theCurArea.Format(ScRefFlags::RANGE_ABS_3D, pDocP, aAddrDetails));
        m_xEdAssign->SetRefString( aRefStr );
        m_xOptions->set_sensitive(true);
        m_xBtnAdd->set_sensitive(true);
        bSaved = true;
        pSaveObj->Save();
    }
}

void ScDbNameDlg::Close()
{
    DoClose( ScDbNameDlgWrapper::GetChildWindowId() );
}

void ScDbNameDlg::SetActive()
{
    m_xEdAssign->GrabFocus();

    //  No NameModifyHdl, because otherwise areas can not be changed
    //  (the old content would be displayed again after the reference selection is pulled)
    //  (the selected DB name has not changed either)

    RefInputDone();
}

void ScDbNameDlg::UpdateNames()
{
    typedef ScDBCollection::NamedDBs DBsType;

    const DBsType& rDBs = aLocalDbCol.getNamedDBs();

    m_xEdName->freeze();

    m_xEdName->clear();
    m_xEdAssign->SetText( EMPTY_OUSTRING );

    if (!rDBs.empty())
    {
        for (const auto& rxDB : rDBs)
            m_xEdName->append_text(rxDB->GetName());
    }
    else
    {
        m_xBtnAdd->set_label( aStrAdd );
        m_xBtnAdd->set_sensitive(false);
        m_xBtnRemove->set_sensitive(false);
    }

    m_xEdName->thaw();
}

void ScDbNameDlg::UpdateDBData( const OUString& rStrName )
{

    const ScDBData* pData = aLocalDbCol.getNamedDBs().findByUpperName(ScGlobal::pCharClass->uppercase(rStrName));

    if ( pData )
    {
        SCCOL nColStart = 0;
        SCROW nRowStart = 0;
        SCCOL nColEnd    = 0;
        SCROW nRowEnd    = 0;
        SCTAB nTab       = 0;

        pData->GetArea( nTab, nColStart, nRowStart, nColEnd, nRowEnd );
        theCurArea = ScRange( ScAddress( nColStart, nRowStart, nTab ),
                              ScAddress( nColEnd,   nRowEnd,   nTab ) );
        OUString theArea(theCurArea.Format(ScRefFlags::RANGE_ABS_3D, pDoc, aAddrDetails));
        m_xEdAssign->SetText( theArea );
        m_xBtnAdd->set_label( aStrModify );
        m_xBtnHeader->set_active( pData->HasHeader() );
        m_xBtnTotals->set_active( pData->HasTotals() );
        m_xBtnDoSize->set_active( pData->IsDoSize() );
        m_xBtnKeepFmt->set_active( pData->IsKeepFmt() );
        m_xBtnStripData->set_active( pData->IsStripData() );
        SetInfoStrings( pData );
    }

    m_xBtnAdd->set_label( aStrModify );
    m_xBtnAdd->set_sensitive(true);
    m_xBtnRemove->set_sensitive(true);
    m_xOptions->set_sensitive(true);
}

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

// Handler:

IMPL_LINK_NOARG(ScDbNameDlg, OkBtnHdl, weld::Button&, void)
{
    AddBtnHdl(*m_xBtnAdd);

    // Pass the changes and the remove list to the view: both are
    // transferred as a reference only, so that no dead memory can
    // be created at this point:
    if ( pViewData )
    {
        ScDBDocFunc aFunc(*pViewData->GetDocShell());
        aFunc.ModifyAllDBData(aLocalDbCol, aRemoveList);
    }

    response(RET_OK);
}

IMPL_LINK_NOARG(ScDbNameDlg, CancelBtnHdl, weld::Button&, void)
{
    response(RET_CANCEL);
}

IMPL_LINK_NOARG(ScDbNameDlg, AddBtnHdl, weld::Button&, void)
{
    OUString aNewName = comphelper::string::strip(m_xEdName->get_active_text(), ' ');
    OUString aNewArea = m_xEdAssign->GetText();

    if ( !aNewName.isEmpty() && !aNewArea.isEmpty() )
    {
        if ( ScRangeData::IsNameValid( aNewName, pDoc ) == ScRangeData::NAME_VALID && aNewName != STR_DB_LOCAL_NONAME )
        {
            //  because editing can be done now, parsing is needed first
            ScRange aTmpRange;
            OUString aText = m_xEdAssign->GetText();
            if ( aTmpRange.ParseAny( aText, pDoc, aAddrDetails ) & ScRefFlags::VALID )
            {
                theCurArea = aTmpRange;
                ScAddress aStart = theCurArea.aStart;
                ScAddress aEnd   = theCurArea.aEnd;

                ScDBData* pOldEntry = aLocalDbCol.getNamedDBs().findByUpperName(ScGlobal::pCharClass->uppercase(aNewName));
                if (pOldEntry)
                {
                    //  modify area

                    pOldEntry->MoveTo( aStart.Tab(), aStart.Col(), aStart.Row(),
                                                        aEnd.Col(), aEnd.Row() );
                    pOldEntry->SetByRow( true );
                    pOldEntry->SetHeader( m_xBtnHeader->get_active() );
                    pOldEntry->SetTotals( m_xBtnTotals->get_active() );
                    pOldEntry->SetDoSize( m_xBtnDoSize->get_active() );
                    pOldEntry->SetKeepFmt( m_xBtnKeepFmt->get_active() );
                    pOldEntry->SetStripData( m_xBtnStripData->get_active() );
                }
                else
                {
                    //  insert new area

                    std::unique_ptr<ScDBData> pNewEntry(new ScDBData( aNewName, aStart.Tab(),
                                                        aStart.Col(), aStart.Row(),
                                                        aEnd.Col(), aEnd.Row(),
                                                        true, m_xBtnHeader->get_active(),
                                                        m_xBtnTotals->get_active() ));
                    pNewEntry->SetDoSize( m_xBtnDoSize->get_active() );
                    pNewEntry->SetKeepFmt( m_xBtnKeepFmt->get_active() );
                    pNewEntry->SetStripData( m_xBtnStripData->get_active() );

                    bool ins = aLocalDbCol.getNamedDBs().insert(std::move(pNewEntry));
                    assert(ins); (void)ins;
                }

                UpdateNames();

                m_xEdName->set_entry_text( EMPTY_OUSTRING );
                m_xEdName->grab_focus();
                m_xBtnAdd->set_label( aStrAdd );
                m_xBtnAdd->set_sensitive(false);
                m_xBtnRemove->set_sensitive(false);
                m_xEdAssign->SetText( EMPTY_OUSTRING );
                m_xBtnHeader->set_active(true);             // Default: with column headers
                m_xBtnTotals->set_active( false );      // Default: without totals row
                m_xBtnDoSize->set_active( false );
                m_xBtnKeepFmt->set_active( false );
                m_xBtnStripData->set_active( false );
                SetInfoStrings( nullptr );     // empty
                theCurArea = ScRange();
                bSaved = true;
                pSaveObj->Save();
                NameModifyHdl( *m_xEdName );
            }
            else
            {
                ERRORBOX(m_xDialog.get(), aStrInvalid);
                m_xEdAssign->SelectAll();
                m_xEdAssign->GrabFocus();
            }
        }
        else
        {
            ERRORBOX(m_xDialog.get(), ScResId(STR_INVALIDNAME));
            m_xEdName->select_entry_region(0, -1);
            m_xEdName->grab_focus();
        }
    }
}

namespace {

class FindByName
{
    const OUString& mrName;
public:
    explicit FindByName(const OUString& rName) : mrName(rName) {}
    bool operator() (std::unique_ptr<ScDBData> const& p) const
    {
        return p->GetName() == mrName;
    }
};

}

IMPL_LINK_NOARG(ScDbNameDlg, RemoveBtnHdl, weld::Button&, void)
{
    OUString aStrEntry = m_xEdName->get_active_text();
    ScDBCollection::NamedDBs& rDBs = aLocalDbCol.getNamedDBs();
    ScDBCollection::NamedDBs::iterator itr =
        ::std::find_if(rDBs.begin(), rDBs.end(), FindByName(aStrEntry));

    if (itr != rDBs.end())
    {
        OUString aStrDelMsg = ScResId( STR_QUERY_DELENTRY );
        OUString sMsg{ aStrDelMsg.getToken(0, '#') + aStrEntry + aStrDelMsg.getToken(1, '#') };
        std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(m_xDialog.get(),
                                                       VclMessageType::Question, VclButtonsType::YesNo,
                                                       sMsg));
        xQueryBox->set_default_response(RET_YES);
        if (RET_YES == xQueryBox->run())
        {
            SCTAB nTab;
            SCCOL nColStart, nColEnd;
            SCROW nRowStart, nRowEnd;
            (*itr)->GetArea( nTab, nColStart, nRowStart, nColEnd, nRowEnd );
            aRemoveList.emplace_back( ScAddress( nColStart, nRowStart, nTab ),
                         ScAddress( nColEnd,   nRowEnd,   nTab ) );

            rDBs.erase(itr);

            UpdateNames();

            m_xEdName->set_entry_text( EMPTY_OUSTRING );
            m_xEdName->grab_focus();
            m_xBtnAdd->set_label( aStrAdd );
            m_xBtnAdd->set_sensitive(false);
            m_xBtnRemove->set_sensitive(false);
            m_xEdAssign->SetText( EMPTY_OUSTRING );
            theCurArea = ScRange();
            m_xBtnHeader->set_active(true);             // Default: with column headers
            m_xBtnTotals->set_active( false );      // Default: without totals row
            m_xBtnDoSize->set_active( false );
            m_xBtnKeepFmt->set_active( false );
            m_xBtnStripData->set_active( false );
            SetInfoStrings( nullptr );     // empty
            bSaved=false;
            pSaveObj->Restore();
            NameModifyHdl( *m_xEdName );
        }
    }
}

IMPL_LINK_NOARG(ScDbNameDlg, NameModifyHdl, weld::ComboBox&, void)
{
    OUString  theName     = m_xEdName->get_active_text();
    bool    bNameFound  = m_xEdName->find_text(theName) != -1;

    if ( theName.isEmpty() )
    {
        if (m_xBtnAdd->get_label() != aStrAdd)
            m_xBtnAdd->set_label( aStrAdd );
        m_xBtnAdd->set_sensitive(false);
        m_xBtnRemove->set_sensitive(false);
        m_xAssignFrame->set_sensitive(false);
        m_xOptions->set_sensitive(false);
        //bSaved=sal_False;
        //pSaveObj->Restore();
        //@BugID 54702 enable/disable in the base class only
        //SFX_APPWINDOW->Disable(sal_False);        //! general method in ScAnyRefDlg
        bRefInputMode = false;
    }
    else
    {
        if ( bNameFound )
        {
            if (m_xBtnAdd->get_label() != aStrModify)
                m_xBtnAdd->set_label( aStrModify );

            if(!bSaved)
            {
                bSaved = true;
                pSaveObj->Save();
            }
            UpdateDBData( theName );
        }
        else
        {
            if (m_xBtnAdd->get_label() != aStrAdd)
                m_xBtnAdd->set_label( aStrAdd );

            bSaved=false;
            pSaveObj->Restore();

            if ( !m_xEdAssign->GetText().isEmpty() )
            {
                m_xBtnAdd->set_sensitive(true);
                m_xOptions->set_sensitive(true);
            }
            else
            {
                m_xBtnAdd->set_sensitive(false);
                m_xOptions->set_sensitive(false);
            }
            m_xBtnRemove->set_sensitive(false);
        }

        m_xAssignFrame->set_sensitive(true);

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

IMPL_LINK_NOARG(ScDbNameDlg, AssModifyHdl, formula::WeldRefEdit&, void)
{
    //  parse here for Save(), etc.

    ScRange aTmpRange;
    OUString aText = m_xEdAssign->GetText();
    if ( aTmpRange.ParseAny( aText, pDoc, aAddrDetails ) & ScRefFlags::VALID )
        theCurArea = aTmpRange;

    if (!aText.isEmpty() && !m_xEdName->get_active_text().isEmpty())
    {
        m_xBtnAdd->set_sensitive(true);
        m_xBtnHeader->set_sensitive(true);
        m_xBtnTotals->set_sensitive(true);
        m_xBtnDoSize->set_sensitive(true);
        m_xBtnKeepFmt->set_sensitive(true);
        m_xBtnStripData->set_sensitive(true);
        m_xFTSource->set_sensitive(true);
        m_xFTOperations->set_sensitive(true);
    }
    else
    {
        m_xBtnAdd->set_sensitive(false);
        m_xBtnHeader->set_sensitive(false);
        m_xBtnTotals->set_sensitive(false);
        m_xBtnDoSize->set_sensitive(false);
        m_xBtnKeepFmt->set_sensitive(false);
        m_xBtnStripData->set_sensitive(false);
        m_xFTSource->set_sensitive(false);
        m_xFTOperations->set_sensitive(false);
    }
}

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