/* -*- 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 <core_resource.hxx>
#include <indexfieldscontrol.hxx>
#include <strings.hrc>
#include <o3tl/safeint.hxx>
#include <osl/diagnose.h>
#include <helpids.h>
#include <toolkit/helper/vclunohelper.hxx>
#include <vcl/settings.hxx>
#include <vcl/svapp.hxx>

namespace dbaui
{

constexpr auto BROWSER_STANDARD_FLAGS = BrowserMode::COLUMNSELECTION | BrowserMode::HLINES | BrowserMode::VLINES |
                                    BrowserMode::HIDECURSOR | BrowserMode::HIDESELECT | BrowserMode::AUTO_HSCROLL | BrowserMode::AUTO_VSCROLL;

#define COLUMN_ID_FIELDNAME     1
#define COLUMN_ID_ORDER         2

    using namespace ::com::sun::star::uno;
    using namespace ::svt;

    // DbaMouseDownListBoxController
    class DbaMouseDownListBoxController : public ListBoxCellController
    {
    protected:
        Link<DbaMouseDownListBoxController&,void>  m_aAdditionalModifyHdl;

    public:
        explicit DbaMouseDownListBoxController(ListBoxControl* _pParent)
            :ListBoxCellController(_pParent)
        {
        }

        void SetAdditionalModifyHdl(const Link<DbaMouseDownListBoxController&,void>& _rHdl);

    protected:
        virtual void callModifyHdl() override;
    };

    void DbaMouseDownListBoxController::SetAdditionalModifyHdl(const Link<DbaMouseDownListBoxController&,void>& _rHdl)
    {
        m_aAdditionalModifyHdl = _rHdl;
    }

    void DbaMouseDownListBoxController::callModifyHdl()
    {
        m_aAdditionalModifyHdl.Call(*this);
        ListBoxCellController::callModifyHdl();
    }

    // IndexFieldsControl
    IndexFieldsControl::IndexFieldsControl(const css::uno::Reference<css::awt::XWindow> &rParent)
        : EditBrowseBox(VCLUnoHelper::GetWindow(rParent), EditBrowseBoxFlags::SMART_TAB_TRAVEL | EditBrowseBoxFlags::ACTIVATE_ON_BUTTONDOWN, WB_TABSTOP | WB_BORDER, BROWSER_STANDARD_FLAGS)
        , m_aSeekRow(m_aFields.end())
        , m_pSortingCell(nullptr)
        , m_pFieldNameCell(nullptr)
        , m_bAddIndexAppendix(false)
    {
    }

    IndexFieldsControl::~IndexFieldsControl()
    {
        disposeOnce();
    }

    void IndexFieldsControl::dispose()
    {
        m_pSortingCell.disposeAndClear();
        m_pFieldNameCell.disposeAndClear();
        ::svt::EditBrowseBox::dispose();
    }

    bool IndexFieldsControl::SeekRow(sal_Int32 nRow)
    {
        if (!EditBrowseBox::SeekRow(nRow))
            return false;

        if (nRow < 0)
        {
            m_aSeekRow = m_aFields.end();
        }
        else
        {
            m_aSeekRow = m_aFields.begin() + nRow;
            OSL_ENSURE(m_aSeekRow <= m_aFields.end(), "IndexFieldsControl::SeekRow: invalid row!");
        }

        return true;
    }

    void IndexFieldsControl::PaintCell( OutputDevice& _rDev, const tools::Rectangle& _rRect, sal_uInt16 _nColumnId ) const
    {
        Point aPos(_rRect.TopLeft());
        aPos.AdjustX(1 );

        OUString aText = GetRowCellText(m_aSeekRow,_nColumnId);
        Size TxtSize(GetDataWindow().GetTextWidth(aText), GetDataWindow().GetTextHeight());

        // clipping
        if (aPos.X() < _rRect.Right() || aPos.X() + TxtSize.Width() > _rRect.Right() ||
            aPos.Y() < _rRect.Top() || aPos.Y() + TxtSize.Height() > _rRect.Bottom())
            _rDev.SetClipRegion(vcl::Region(_rRect));

        // allow for a disabled control ...
        bool bEnabled = IsEnabled();
        Color aOriginalColor = _rDev.GetTextColor();
        if (!bEnabled)
            _rDev.SetTextColor(GetSettings().GetStyleSettings().GetDisableColor());

        // draw the text
        _rDev.DrawText(aPos, aText);

        // reset the color (if necessary)
        if (!bEnabled)
            _rDev.SetTextColor(aOriginalColor);

        if (_rDev.IsClipRegion())
            _rDev.SetClipRegion();
    }

    void IndexFieldsControl::initializeFrom(IndexFields&& _rFields)
    {
        // copy the field descriptions
        m_aFields = std::move(_rFields);
        m_aSeekRow = m_aFields.end();

        SetUpdateMode(false);
        // remove all rows
        RowRemoved(1, GetRowCount());
        // insert rows for the fields
        RowInserted(GetRowCount(), m_aFields.size(), false);
        // insert an additional row for a new field for that index
        RowInserted(GetRowCount(), 1, false);
        SetUpdateMode(true);

        GoToRowColumnId(0, COLUMN_ID_FIELDNAME);
    }

    void IndexFieldsControl::commitTo(IndexFields& _rFields)
    {
        // do not just copy the array, we may have empty field names (which should not be copied)
        _rFields.resize(m_aFields.size());
        IndexFields::iterator aDest = std::copy_if(m_aFields.begin(), m_aFields.end(), _rFields.begin(),
            [](const OIndexField& source) { return !source.sFieldName.isEmpty(); });

        _rFields.resize(aDest - _rFields.begin());
    }

    sal_uInt32 IndexFieldsControl::GetTotalCellWidth(sal_Int32 _nRow, sal_uInt16 _nColId)
    {
        if (COLUMN_ID_ORDER == _nColId)
        {
            sal_Int32 nWidthAsc = GetTextWidth(m_sAscendingText) + GetSettings().GetStyleSettings().GetScrollBarSize();
            sal_Int32 nWidthDesc = GetTextWidth(m_sDescendingText) + GetSettings().GetStyleSettings().GetScrollBarSize();
            // maximum plus some additional space
            return std::max(nWidthAsc, nWidthDesc) + GetTextWidth(OUString('0')) * 2;
        }
        return EditBrowseBox::GetTotalCellWidth(_nRow, _nColId);
    }

    void IndexFieldsControl::Init(const Sequence< OUString >& _rAvailableFields, bool _bAddIndexAppendix)
    {
        m_bAddIndexAppendix = _bAddIndexAppendix;

        RemoveColumns();

        // for the width: both columns together should be somewhat smaller than the whole window (without the scrollbar)
        sal_Int32 nFieldNameWidth = GetSizePixel().Width();

        if ( m_bAddIndexAppendix )
        {
            m_sAscendingText = DBA_RES(STR_ORDER_ASCENDING);
            m_sDescendingText = DBA_RES(STR_ORDER_DESCENDING);

            // the "sort order" column
            OUString sColumnName = DBA_RES(STR_TAB_INDEX_SORTORDER);
            // the width of the order column is the maximum widths of the texts used
            // (the title of the column)
            sal_Int32 nSortOrderColumnWidth = GetTextWidth(sColumnName);
            // ("ascending" + scrollbar width)
            sal_Int32 nOther = GetTextWidth(m_sAscendingText) + GetSettings().GetStyleSettings().GetScrollBarSize();
            nSortOrderColumnWidth = std::max(nSortOrderColumnWidth, nOther);
            // ("descending" + scrollbar width)
            nOther = GetTextWidth(m_sDescendingText) + GetSettings().GetStyleSettings().GetScrollBarSize();
            nSortOrderColumnWidth = std::max(nSortOrderColumnWidth, nOther);
            // (plus some additional space)
            nSortOrderColumnWidth += GetTextWidth(OUString('0')) * 2;
            InsertDataColumn(COLUMN_ID_ORDER, sColumnName, nSortOrderColumnWidth, HeaderBarItemBits::STDSTYLE, 1);

            m_pSortingCell = VclPtr<ListBoxControl>::Create(&GetDataWindow());
            weld::ComboBox& rSortingListBox = m_pSortingCell->get_widget();
            rSortingListBox.append_text(m_sAscendingText);
            rSortingListBox.append_text(m_sDescendingText);
            rSortingListBox.set_help_id(HID_DLGINDEX_INDEXDETAILS_SORTORDER);

            nFieldNameWidth -= nSortOrderColumnWidth;
        }
        StyleSettings aSystemStyle = Application::GetSettings().GetStyleSettings();
        nFieldNameWidth -= aSystemStyle.GetScrollBarSize();
        nFieldNameWidth -= 8;
        // the "field name" column
        OUString sColumnName = DBA_RES(STR_TAB_INDEX_FIELD);
        InsertDataColumn(COLUMN_ID_FIELDNAME, sColumnName, nFieldNameWidth, HeaderBarItemBits::STDSTYLE, 0);

        // create the cell controllers
        // for the field name cell
        m_pFieldNameCell = VclPtr<ListBoxControl>::Create(&GetDataWindow());
        weld::ComboBox& rNameListBox = m_pFieldNameCell->get_widget();
        rNameListBox.append_text(OUString());
        rNameListBox.set_help_id(HID_DLGINDEX_INDEXDETAILS_FIELD);
        for (auto& text : _rAvailableFields)
            rNameListBox.append_text(text);
    }

    CellController* IndexFieldsControl::GetController(sal_Int32 _nRow, sal_uInt16 _nColumnId)
    {
        if (!IsEnabled())
            return nullptr;

        IndexFields::const_iterator aRow;
        bool bNewField = !implGetFieldDesc(_nRow, aRow);

        DbaMouseDownListBoxController* pReturn = nullptr;
        switch (_nColumnId)
        {
            case COLUMN_ID_ORDER:
                if (!bNewField && m_pSortingCell && !aRow->sFieldName.isEmpty())
                    pReturn = new DbaMouseDownListBoxController(m_pSortingCell);
                break;

            case COLUMN_ID_FIELDNAME:
                pReturn = new DbaMouseDownListBoxController(m_pFieldNameCell);
                break;

            default:
                OSL_FAIL("IndexFieldsControl::GetController: invalid column id!");
        }

        if (pReturn)
            pReturn->SetAdditionalModifyHdl(LINK(this, IndexFieldsControl, OnListEntrySelected));

        return pReturn;
    }

    bool IndexFieldsControl::implGetFieldDesc(sal_Int32 _nRow, IndexFields::const_iterator& _rPos)
    {
        _rPos = m_aFields.end();
        if ((_nRow < 0) || (o3tl::make_unsigned(_nRow) >= m_aFields.size()))
            return false;
        _rPos = m_aFields.begin() + _nRow;
        return true;
    }

    bool IndexFieldsControl::SaveModified()
    {
        if (!IsModified())
            return true;

        switch (GetCurColumnId())
        {
            case COLUMN_ID_FIELDNAME:
            {
                weld::ComboBox& rNameListBox = m_pFieldNameCell->get_widget();
                OUString sFieldSelected = rNameListBox.get_active_text();
                bool bEmptySelected = sFieldSelected.isEmpty();
                if (isNewField())
                {
                    if (!bEmptySelected)
                    {
                        // add a new field to the collection
                        OIndexField aNewField;
                        aNewField.sFieldName = sFieldSelected;
                        m_aFields.push_back(aNewField);
                        RowInserted(GetRowCount());
                    }
                }
                else
                {
                    sal_Int32 nRow = GetCurRow();
                    OSL_ENSURE(nRow < static_cast<sal_Int32>(m_aFields.size()), "IndexFieldsControl::SaveModified: invalid current row!");
                    if (nRow >= 0)  // may be -1 in case the control was empty
                    {
                        // remove the field from the selection
                        IndexFields::iterator aPos = m_aFields.begin() + nRow;

                        if (bEmptySelected)
                        {
                            aPos->sFieldName.clear();

                            // invalidate the row to force repaint
                            Invalidate(GetRowRectPixel(nRow));
                            return true;
                        }

                        if (sFieldSelected == aPos->sFieldName)
                            // nothing changed
                            return true;

                        aPos->sFieldName = sFieldSelected;
                    }
                }

                Invalidate(GetRowRectPixel(GetCurRow()));
            }
            break;
            case COLUMN_ID_ORDER:
            {
                OSL_ENSURE(!isNewField(), "IndexFieldsControl::SaveModified: why the hell ...!!!");
                // selected entry
                weld::ComboBox& rSortingListBox = m_pSortingCell->get_widget();
                sal_Int32 nPos = rSortingListBox.get_active();
                OSL_ENSURE(nPos != -1, "IndexFieldsControl::SaveModified: how did you get this selection??");
                // adjust the sort flag in the index field description
                OIndexField& rCurrentField = m_aFields[GetCurRow()];
                rCurrentField.bSortAscending = (0 == nPos);

            }
            break;
            default:
                OSL_FAIL("IndexFieldsControl::SaveModified: invalid column id!");
        }
        return true;
    }

    void IndexFieldsControl::InitController(CellControllerRef& /*_rController*/, sal_Int32 _nRow, sal_uInt16 _nColumnId)
    {
        IndexFields::const_iterator aFieldDescription;
        bool bNewField = !implGetFieldDesc(_nRow, aFieldDescription);

        switch (_nColumnId)
        {
            case COLUMN_ID_FIELDNAME:
            {
                weld::ComboBox& rNameListBox = m_pFieldNameCell->get_widget();
                rNameListBox.set_active_text(bNewField ? OUString() : aFieldDescription->sFieldName);
                rNameListBox.save_value();
                break;
            }

            case COLUMN_ID_ORDER:
            {
                weld::ComboBox& rSortingListBox = m_pSortingCell->get_widget();
                rSortingListBox.set_active_text(aFieldDescription->bSortAscending ? m_sAscendingText : m_sDescendingText);
                rSortingListBox.save_value();
                break;
            }

            default:
                OSL_FAIL("IndexFieldsControl::InitController: invalid column id!");
        }
    }

    IMPL_LINK( IndexFieldsControl, OnListEntrySelected, DbaMouseDownListBoxController&, rController, void )
    {
        weld::ComboBox& rListBox = rController.GetListBox();
        if (!rListBox.get_popup_shown())
            m_aModifyHdl.Call(*this);

        if (&rListBox != &m_pFieldNameCell->get_widget())
            return;

// a field has been selected
        if (GetCurRow() >= GetRowCount() - 2)
        {   // and we're in one of the last two rows
            OUString sSelectedEntry = rListBox.get_active_text();
            sal_Int32 nCurrentRow = GetCurRow();
            sal_Int32 rowCount = GetRowCount();

            OSL_ENSURE((static_cast<sal_Int32>(m_aFields.size() + 1)) == rowCount, "IndexFieldsControl::OnListEntrySelected: inconsistence!");

            if (!sSelectedEntry.isEmpty() && (nCurrentRow == rowCount - 1) /*&& (!m_nMaxColumnsInIndex || rowCount < m_nMaxColumnsInIndex )*/ )
            {   // in the last row, a non-empty string has been selected
                // -> insert a new row
                m_aFields.emplace_back();
                RowInserted(GetRowCount());
                Invalidate(GetRowRectPixel(nCurrentRow));
            }
            else if (sSelectedEntry.isEmpty() && (nCurrentRow == rowCount - 2))
            {   // in the (last-1)th row, an empty entry has been selected
                // -> remove the last row
                m_aFields.pop_back();
                RowRemoved(GetRowCount() - 1);
                Invalidate(GetRowRectPixel(nCurrentRow));
            }
        }

        SaveModified();
    }
    OUString IndexFieldsControl::GetCellText(sal_Int32 _nRow,sal_uInt16 nColId) const
    {
        IndexFields::const_iterator aRow = m_aFields.end();
        if ( _nRow >= 0 )
        {
            aRow = m_aFields.begin() + _nRow;
            OSL_ENSURE(aRow <= m_aFields.end(), "IndexFieldsControl::SeekRow: invalid row!");
        }
        return GetRowCellText(aRow,nColId);
    }
    OUString IndexFieldsControl::GetRowCellText(const IndexFields::const_iterator& _rRow,sal_uInt16 nColId) const
    {
        if (_rRow < m_aFields.end())
        {
            switch (nColId)
            {
                case COLUMN_ID_FIELDNAME:
                    return _rRow->sFieldName;
                case COLUMN_ID_ORDER:
                    if (_rRow->sFieldName.isEmpty())
                        return OUString();
                    else
                        return _rRow->bSortAscending ? m_sAscendingText : m_sDescendingText;
                default:
                    OSL_FAIL("IndexFieldsControl::GetCurrentRowCellText: invalid column id!");
            }
        }
        return OUString();
    }
    bool IndexFieldsControl::IsTabAllowed(bool /*bForward*/) const
    {
        return false;
    }

}   // namespace dbaui

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