/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office 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 .
 */

#pragma once

#include "scdllapi.h"
#include "refreshtimer.hxx"
#include "address.hxx"
#include "global.hxx"
#include "rangelst.hxx"

#include <svl/listener.hxx>
#include <svl/poolitem.hxx>

#include <memory>
#include <optional>
#include <set>
#include <vector>

class ScDocument;
struct ScSortParam;
struct ScQueryParam;
struct ScSubTotalParam;
class ScTokenArray;

class SC_DLLPUBLIC ScDatabaseSettingItem final : public SfxPoolItem
{
    bool mbHeaderRow;
    bool mbFirstCol;
    bool mbLastCol;
    bool mbStripedRows;
    bool mbStripedCols;
    bool mbShowFilters;
    OUString maStyleID;

public:
    static SfxPoolItem* CreateDefault();
    DECLARE_ITEM_TYPE_FUNCTION(ScDatabaseSettingItem)
    ScDatabaseSettingItem();
    ScDatabaseSettingItem(bool bHeaderRow, bool bFirstCol, bool bLastCol,
                          bool bStripedRows, bool bStripedCols, bool bShowFilter,
                          const OUString& aStyleID);
    ScDatabaseSettingItem( const ScDatabaseSettingItem& rItem );
    virtual ~ScDatabaseSettingItem() override;

    virtual bool            QueryValue( cpo::uno::Any& rVal, sal_uInt8 nMemberId = 0 ) const override;
    virtual bool            PutValue( const cpo::uno::Any& rVal, sal_uInt8 nMemberId ) override;

    ScDatabaseSettingItem& operator=( const ScDatabaseSettingItem& rItem );
    virtual bool operator==( const SfxPoolItem& ) const override;
    virtual ScDatabaseSettingItem* Clone( SfxItemPool *pPool = nullptr ) const override;

    bool HasHeaderRow() const;
    bool HasFirstCol() const;
    bool HasLastCol() const;
    bool HasStripedRows() const;
    bool HasStripedCols() const;
    bool HasShowFilters() const;
    const OUString& GetStyleID() const;
};

/** Enum used to indicate which portion of the DBArea is to be considered. */
enum class ScDBDataPortion
{
    TOP_LEFT,   ///< top left cell of area
    AREA        ///< entire area
};

// TODO: this can be merged with struct TableColumnModel
struct TableColumnAttributes
{
    std::optional<OUString> maTotalsRowLabel = std::nullopt;
    std::optional<OUString> maTotalsFunction = std::nullopt;
    std::optional<OUString> maCustomFunction = std::nullopt;
};

// xmlColumnPr attributes
struct XmlColumnPrModel
{
    sal_uInt32          mnMapId;
    OUString            msXpath;
    OUString            msXmlDataType;
    bool                mbDenormalized;

    explicit            XmlColumnPrModel();
};

struct TableColumnModel
{
    typedef std::unique_ptr<XmlColumnPrModel> XmlColumnPrModelPtr;
    XmlColumnPrModelPtr mxXmlColumnPr; // Special settings for XML Column Properties.
    XmlColumnPrModel&   createXmlColumnPr();

    OUString            maUniqueName; // unique name of the <tableColumn>

    explicit            TableColumnModel();
};

/** Container base class to provide selected access for ScDBData. */
class ScDBDataContainerBase
{
public:
    ScDBDataContainerBase( ScDocument& rDoc ) : mrDoc(rDoc) {}
    virtual ~ScDBDataContainerBase() {}
    ScDocument& GetDocument() const;
    ScRangeList& GetDirtyTableColumnNames();

protected:
    ScDocument& mrDoc;
    ScRangeList maDirtyTableColumnNames;
};


struct SAL_DLLPUBLIC_RTTI ScTableStyleParam
{
    OUString maStyleID;
    bool mbRowStripes;
    bool mbColumnStripes;
    bool mbFirstColumn;
    bool mbLastColumn;

    SC_DLLPUBLIC ScTableStyleParam();

    bool operator== (const ScTableStyleParam& rData) const;
};

class UNLESS_MERGELIBS(SAL_DLLPUBLIC_RTTI) ScDBData final : public SvtListener, public ScRefreshTimer
{
private:
    std::unique_ptr<ScSortParam> mpSortParam;
    std::unique_ptr<ScQueryParam> mpQueryParam;
    std::unique_ptr<ScSubTotalParam> mpSubTotal;
    std::unique_ptr<ScImportParam> mpImportParam;
    std::unique_ptr<ScTableStyleParam> mpTableStyles;

    ScDBDataContainerBase* mpContainer;

    /// DBParam
    const OUString aName;
    OUString aUpper;
    OUString        aTableType;
    SCTAB           nTable;
    SCCOL           nStartCol;
    SCROW           nStartRow;
    SCCOL           nEndCol;
    SCROW           nEndRow;
    bool            bByRow;
    bool            bHasHeader;
    bool            bHasTotals;
    bool            bDoSize;
    bool            bKeepFmt;
    bool            bStripData;

    /// QueryParam
    bool            bIsAdvanced;        ///< true if created by advanced filter
    ScRange         aAdvSource;         ///< source range

    bool            bDBSelection;       ///< not in Param: if selection, block update

    sal_uInt16      nIndex;             ///< unique index formulas
    bool            bAutoFilter;        ///< AutoFilter? (not saved)
    bool            bModified;          ///< is set/cleared for/by(?) UpdateReference

    ::std::vector< OUString > maTableColumnNames;   ///< names of table columns
    ::std::vector< TableColumnModel > maTableColumnModel;
    bool            mbTableColumnNamesDirty;
    SCSIZE          nFilteredRowCount;

    /// Pending auto-expansion state, set by Notify, consumed by the drain.
    /// Trigger range IsValid() is the "pending" signal; the overflow flags
    /// veto expansion when we overflow the band.
    bool            mbOverflowDown;
    bool            mbOverflowRight;
    ScRange         maPendingTriggerRangeDown;
    ScRange         maPendingTriggerRangeRight;

    /// Ranges currently passed to StartListeningArea, stored so the matching
    /// EndListeningArea call uses the exact same range (slot machine matches
    /// by exact range — see ScDocument::EndListeningArea).
    ScRange         maRegisteredHeaderRange;
    ScRange         maRegisteredRowBand;
    ScRange         maRegisteredColBand;

    /// Default header names ("Column#") wrote into empty header cells.
    std::set<OUString> maGeneratedHeaderNames;

    using ScRefreshTimer::operator==;

public:
    struct less
    {
        bool operator() (const std::unique_ptr<ScDBData>& left, const std::unique_ptr<ScDBData>& right) const;
    };

    SC_DLLPUBLIC ScDBData(const OUString& rName,
             SCTAB nTab,
             SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, bool bByR = true, bool bHasH = true, bool bTotals = false,
                          const OUString& rTableType = u""_ustr,
                          const OUString& rTableStyleID = u""_ustr);
    SC_DLLPUBLIC ScDBData(const ScDBData& rData);
    ScDBData(const OUString& rName, const ScDBData& rData);
    SC_DLLPUBLIC virtual ~ScDBData() override;

    virtual void Notify( const SfxHint& rHint ) override;

    ScDBData&   operator= (const ScDBData& rData) ;

    bool        operator== (const ScDBData& rData) const;

    const OUString& GetName() const { return aName; }
    const OUString& GetUpperName() const { return aUpper; }
    SCTAB       GetTab() const                  { return nTable; }
    void        GetArea(SCTAB& rTab, SCCOL& rCol1, SCROW& rRow1, SCCOL& rCol2, SCROW& rRow2) const;
    SC_DLLPUBLIC void GetArea(ScRange& rRange) const;
    SC_DLLPUBLIC void SetArea(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2);
    void SetArea(ScRange const& rRange)
    {
        SetArea(rRange.aStart.Tab(), rRange.aStart.Col(), rRange.aStart.Row(),
                rRange.aEnd.Col(), rRange.aEnd.Row());
    }
    void        MoveTo(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
                       SCCOL nUpdateCol = -1);
    void        SetByRow(bool bByR)             { bByRow = bByR; }
    bool        HasHeader() const               { return bHasHeader; }
    /// On flag change, surgically re-registers the header listener.
    SC_DLLPUBLIC void SetHeader(bool bHasH);
    bool        HasTotals() const               { return bHasTotals; }
    /// On flag change, surgically re-registers the adjacency band listeners
    /// (the row band is gated on !bHasTotals).
    void        SetTotals(bool bTotals);
    void        SetIndex(sal_uInt16 nInd)           { nIndex = nInd; }
    sal_uInt16  GetIndex() const                { return nIndex; }
    bool        IsDoSize() const                { return bDoSize; }
    void        SetDoSize(bool bSet)            { bDoSize = bSet; }
    bool        IsKeepFmt() const               { return bKeepFmt; }
    void        SetKeepFmt(bool bSet)           { bKeepFmt = bSet; }
    bool        IsStripData() const             { return bStripData; }
    void        SetStripData(bool bSet)         { bStripData = bSet; }
    void        SetTableType(const OUString& sTableType) { aTableType = sTableType; }
    const OUString& GetTableType() const           { return aTableType; }

    void        SetContainer( ScDBDataContainerBase* pContainer ) { mpContainer = pContainer; }
    /** Returns header row range if has headers, else invalid range. */
    ScRange     GetHeaderArea() const;
    /// Register/unregister the header-row area listener used to keep
    /// column-name caching in sync.
    void        StartTableColumnNamesListener();
    void        EndTableColumnNamesListener();

    /// Register/unregister the adjacency band listeners used for MSO-style
    /// auto-expand. Row band is suppressed when bHasTotals is true.
    void        StartAdjacencyBandsListener();
    void        EndAdjacencyBandsListener();

    bool        HasPendingExpansion() const
                    { return maPendingTriggerRangeDown.IsValid()
                          || maPendingTriggerRangeRight.IsValid(); }
    void        ClearPendingExpansion();
    /// Re-evaluates the pending extension against runtime gates and
    /// returns the new area to apply, or std::nullopt if any gate fails.
    std::optional<ScRange> ResolvePendingExpansion(const ScDocument& rDoc) const;

    /// Sync the AutoFilter dropdown flag (ScMF::Auto) on the header row
    /// when a styled table's area changes. Caller must gate on
    /// HasHeader() && HasAutoFilter().
    static void SwapAutoFilterFlagOnHeader(ScDocument& rDoc,
                                           const ScRange& rOldArea,
                                           const ScRange& rNewArea);
    SC_DLLPUBLIC void SetTableColumnNames( ::std::vector< OUString >&& rNames );
    SC_DLLPUBLIC const ::std::vector< OUString >& GetTableColumnNames() const { return maTableColumnNames; }
    SC_DLLPUBLIC void SetTableColumnModel( TableColumnModel& rModel )
    {
        maTableColumnModel.push_back(std::move(rModel));
    }
    SC_DLLPUBLIC const ::std::vector< TableColumnModel >& GetTableColumnModel() const { return maTableColumnModel; }
    bool        AreTableColumnNamesDirty() const { return mbTableColumnNamesDirty; }

    /** Refresh/update the column names with the header row's cell contents. */
    SC_DLLPUBLIC void RefreshTableColumnNames( ScDocument* pDoc );

    /** Refresh/update the column names with the header row's cell contents
        within the given range. */
    void RefreshTableColumnNames( ScDocument& rDoc, const ScRangeList& rRangeList );

    /** Finds the column named rName and returns the corresponding offset
        within the table.
        @returns -1 if not found.

        XXX NOTE: there is no refresh of names or anything implemented yet, use
        this only during document load time.
     */
    sal_Int32   GetColumnNameOffset( const OUString& rName ) const;

    /** Returns table column name if nCol is within column range and name
        is stored, else empty string. */
    const OUString & GetTableColumnName( SCCOL nCol ) const;

    /** Record a default header name ("Column#") that was written in empty headers */
    void AddGeneratedHeaderName(const OUString& rName) { maGeneratedHeaderNames.insert(rName); }
    /** Stop tracking rName as a generated name (the user moved/overwrote it). */
    void RemoveGeneratedHeaderName(const OUString& rName) { maGeneratedHeaderNames.erase(rName); }
    /** Was rName auto-generated as a default header name by this table? */
    bool IsGeneratedHeaderName(const OUString& rName) const
    {
        return maGeneratedHeaderNames.find(rName) != maGeneratedHeaderNames.end();
    }

    OUString GetSourceString() const;
    OUString GetOperations() const;

    SC_DLLPUBLIC void GetSortParam(ScSortParam& rSortParam) const;
    SC_DLLPUBLIC void SetSortParam(const ScSortParam& rSortParam);

    /** Remember some more settings of ScSortParam, only to be called at
        anonymous DB ranges as it at least overwrites bHasHeader. */
    void        UpdateFromSortParam( const ScSortParam& rSortParam );

    SC_DLLPUBLIC void       GetQueryParam(ScQueryParam& rQueryParam) const;
    SC_DLLPUBLIC void       SetQueryParam(const ScQueryParam& rQueryParam);
    SC_DLLPUBLIC bool       GetAdvancedQuerySource(ScRange& rSource) const;
    SC_DLLPUBLIC void       SetAdvancedQuerySource(const ScRange* pSource);

    SC_DLLPUBLIC void       GetSubTotalParam(ScSubTotalParam& rSubTotalParam) const;
    SC_DLLPUBLIC void       SetSubTotalParam(const ScSubTotalParam& rSubTotalParam);

    // Total row param handling for Table Styles
    SC_DLLPUBLIC void       ImportTotalRowParam(ScSubTotalParam& rSubTotalParam,
                                                const std::vector<TableColumnAttributes>& rAttributesVector,
                                                formula::FormulaGrammar::Grammar eGrammar) const;
    SC_DLLPUBLIC void       CreateTotalRowParam(ScSubTotalParam& rSubTotalParam) const;

    /// Returns true if a column-bounded row shift in this table's column range starting
    /// at nShiftRow would tear an adjacent fixed-range structure: a pivot table, another
    /// ScDBData straddling the band boundary, or a merged cell that crosses it.
    bool        HasTearRiskAtBand(const ScDocument& rDoc, SCROW nShiftRow) const;

    /// Returns true if any cell in this table's column band at nShiftRow is non-empty.
    bool        IsBandBlockedAtRow(const ScDocument& rDoc, SCROW nShiftRow) const;

    /// True if extending this table to rNewArea (down/right) would overlap another ScDBData,
    /// pivot table, or merged cells — the cases HasTearRiskAtBand's from-afar checks miss (a
    /// same-width/narrower neighbour, or one reached by a right-drag). False for a shrink.
    bool WouldResizeOverlap(ScDocument& rDoc, const ScRange& rNewArea) const;

    /// Returns true if toggling Total Row on this table (bAddTotal = true to add, false
    /// to remove) would be refused because of tear-risk at the row immediately below
    /// the data combined with non-empty cells in the band.
    SC_DLLPUBLIC bool       WouldTableTotalsBeRefused(bool bAddTotal) const;

    SC_DLLPUBLIC std::vector<TableColumnAttributes>
                            GetTotalRowAttributes(formula::FormulaGrammar::Grammar eGrammar) const;

    OUString    GetSimpleSubTotalFunction(const ScTokenArray* pTokens, SCCOL nCol, SCROW nHeaderRow) const;

    void        GetImportParam(ScImportParam& rImportParam) const;
    void        SetImportParam(const ScImportParam& rImportParam);

    bool        IsDBAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, ScDBDataPortion ePortion) const;
    bool        IsDBAtArea(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) const;

    bool        HasImportParam() const;
    SC_DLLPUBLIC bool HasQueryParam() const;
    bool        HasSortParam() const;
    bool        HasSubTotalParam() const;

    bool        HasImportSelection() const      { return bDBSelection; }
    void        SetImportSelection(bool bSet)   { bDBSelection = bSet; }

    bool        HasAutoFilter() const       { return bAutoFilter; }
    void        SetAutoFilter(bool bSet)    { bAutoFilter = bSet; }

    bool        IsModified() const          { return bModified; }
    void        SetModified(bool bMod)      { bModified = bMod; }

    void    UpdateMoveTab( SCTAB nOldPos, SCTAB nNewPos );
    bool    UpdateReference(const ScDocument& rDoc, UpdateRefMode eUpdateRefMode, SCCOL nCol1,
                            SCROW nRow1, SCTAB nTab1, SCCOL nCol2, SCROW nRow2, SCTAB nTab2,
                            SCCOL nDx, SCROW nDy, SCTAB nDz);

    void ExtendDataArea(const ScDocument& rDoc);
    void CalcSaveFilteredCount(SCSIZE nNonFilteredRowCount);
    void GetFilterSelCount(SCSIZE& nSelected, SCSIZE& nTotal);

    SC_DLLPUBLIC void SetTableStyleInfo(const ScTableStyleParam& rParams);
    SC_DLLPUBLIC const ScTableStyleParam* GetTableStyleInfo() const;

    static ScSubTotalFunc GetSubTotalFuncFromString(std::u16string_view sFunction);
    static OUString GetStringFromSubTotalFunc(ScSubTotalFunc eFunc);

private:

    void AdjustTableColumnNames( UpdateRefMode eUpdateRefMode, SCCOL nDx, SCCOL nCol1,
            SCCOL nOldCol1, SCCOL nOldCol2, SCCOL nNewCol1, SCCOL nNewCol2 );
    void InvalidateTableColumnNames( bool bSwapToEmptyNames );

    bool BandReachesStructure(ScDocument& rDoc, const ScRange& rBand) const;

    /// Classify a single broadcast hit against the adjacency bands and their
    /// overflow buffers. Returns true if the cell was inside one of our
    /// bands' outer rectangles (so the per-cell Notify path can skip the
    /// header-dirty fallback). Called from Notify for both per-cell ScHint
    /// and aggregated ScBulkData paths.
    bool processBandHitAt(const ScAddress& rPos);
};

class ScDBCollection
{
public:
    enum RangeType { GlobalNamed, GlobalAnonymous, SheetAnonymous };

    /**
     * Stores global named database ranges.
     */
    class SC_DLLPUBLIC NamedDBs final : public ScDBDataContainerBase
    {
        friend class ScDBCollection;

        typedef ::std::set<std::unique_ptr<ScDBData>, ScDBData::less> DBsType;
        DBsType m_DBs;
        ScDBCollection& mrParent;
        NamedDBs(ScDBCollection& rParent, ScDocument& rDoc);
        NamedDBs(const NamedDBs& r, ScDBCollection& rParent);
        NamedDBs(const NamedDBs&) = delete;
        virtual ~NamedDBs() override;
        NamedDBs & operator=(NamedDBs const&) = delete;
        void initInserted( ScDBData* p );

    public:
        typedef DBsType::iterator iterator;
        typedef DBsType::const_iterator const_iterator;

        iterator begin();
        iterator end();
        const_iterator begin() const;
        const_iterator end() const;
        ScDBData* findByIndex(sal_uInt16 nIndex);
        ScDBData* findByUpperName(const OUString& rName);
        iterator findByUpperName2(const OUString& rName);
        iterator findByPointer(const ScDBData* p);
        ScDBData* findByName(const OUString& rName);

        /** Takes ownership of p and attempts to insert it into the collection.
            Deletes p if it could not be inserted, i.e. duplicate name.
            @return <TRUE/> if inserted, else <FALSE/>.
         */
        bool insert(std::unique_ptr<ScDBData> p);

        iterator erase(const iterator& itr);
        bool empty() const;
        size_t size() const;
        bool operator== (const NamedDBs& r) const;
    };

    /**
     * Stores global anonymous database ranges.
     */
    class AnonDBs
    {
        typedef ::std::vector<std::unique_ptr<ScDBData>> DBsType;
        DBsType m_DBs;

        AnonDBs& operator=(AnonDBs const&) = delete;

    public:
        AnonDBs();
        AnonDBs(AnonDBs const&);

        typedef DBsType::iterator iterator;
        typedef DBsType::const_iterator const_iterator;

        iterator begin();
        iterator end();
        const_iterator begin() const;
        const_iterator end() const;
        const ScDBData* findAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, ScDBDataPortion ePortion) const;
        const ScDBData* findByRange(const ScRange& rRange) const;
        void deleteOnTab(SCTAB nTab);
        ScDBData* getByRange(const ScRange& rRange);
        void insert(ScDBData* p);
        iterator erase(const iterator& itr);
        bool empty() const;
        bool has( const ScDBData* p ) const;
        bool operator== (const AnonDBs& r) const;
    };

private:
    Link<Timer *, void> aRefreshHandler;
    ScDocument& rDoc;
    sal_uInt16 nEntryIndex;         ///< counter for unique indices
    NamedDBs maNamedDBs;
    AnonDBs maAnonDBs;

public:
    ScDBCollection(ScDocument& rDocument);
    ScDBCollection(const ScDBCollection& r);

    NamedDBs& getNamedDBs() { return maNamedDBs;}
    const NamedDBs& getNamedDBs() const { return maNamedDBs;}

    AnonDBs& getAnonDBs() { return maAnonDBs;}
    const AnonDBs& getAnonDBs() const { return maAnonDBs;}

    const ScDBData* GetTableDBAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, ScDBDataPortion ePortion) const;
    ScDBData* GetTableDBAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, ScDBDataPortion ePortion);
    const ScDBData* GetDBAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, ScDBDataPortion ePortion) const;
    ScDBData* GetDBAtCursor(SCCOL nCol, SCROW nRow, SCTAB nTab, ScDBDataPortion ePortion);
    const ScDBData* GetDBAtArea(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) const;
    SC_DLLPUBLIC ScDBData* GetDBAtArea(SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2);
    SC_DLLPUBLIC ScDBData* GetDBNearCursor(SCCOL nCol, SCROW nRow, SCTAB nTab );
    SC_DLLPUBLIC std::vector<ScDBData*> GetAllDBsFromTab(SCTAB nTab);
    std::vector<const ScDBData*> GetAllNamedDBsInArea(const ScRange& rRange) const;

    void RefreshDirtyTableColumnNames();

    void    DeleteOnTab( SCTAB nTab );
    void    UpdateReference(UpdateRefMode eUpdateRefMode,
                                SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
                                SCCOL nCol2, SCROW nRow2, SCTAB nTab2,
                                SCCOL nDx, SCROW nDy, SCTAB nDz);
    void    UpdateMoveTab( SCTAB nOldPos, SCTAB nNewPos );
    void    CopyToTable(SCTAB nOldPos, SCTAB nNewPos);

    void            SetRefreshHandler( const Link<Timer *, void>& rLink )
                        { aRefreshHandler = rLink; }
    const Link<Timer *, void>& GetRefreshHandler() const { return aRefreshHandler; }

    SC_DLLPUBLIC bool empty() const;
    bool operator== (const ScDBCollection& r) const;
};

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