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

#include <string_view>

#include <uielement/toolbarmerger.hxx>
#include <framework/generictoolbarcontroller.hxx>

#include <uielement/buttontoolbarcontroller.hxx>
#include <uielement/comboboxtoolbarcontroller.hxx>
#include <uielement/dropdownboxtoolbarcontroller.hxx>
#include <uielement/edittoolbarcontroller.hxx>
#include <uielement/imagebuttontoolbarcontroller.hxx>
#include <uielement/spinfieldtoolbarcontroller.hxx>
#include <uielement/togglebuttontoolbarcontroller.hxx>
#include <uielement/FixedTextToolbarController.hxx>
#include <uielement/FixedImageToolbarController.hxx>
#include <o3tl/string_view.hxx>

namespace framework
{

const char MERGE_TOOLBAR_URL[]             = "URL";
const char MERGE_TOOLBAR_TITLE[]           = "Title";
const char MERGE_TOOLBAR_CONTEXT[]         = "Context";
const char MERGE_TOOLBAR_TARGET[]          = "Target";
const char MERGE_TOOLBAR_CONTROLTYPE[]     = "ControlType";
const char MERGE_TOOLBAR_WIDTH[]           = "Width";

const char16_t MERGECOMMAND_ADDAFTER[]     = u"AddAfter";
const char16_t MERGECOMMAND_ADDBEFORE[]    = u"AddBefore";
const char16_t MERGECOMMAND_REPLACE[]      = u"Replace";
const char16_t MERGECOMMAND_REMOVE[]       = u"Remove";

const char16_t MERGEFALLBACK_ADDLAST[]     = u"AddLast";
const char16_t MERGEFALLBACK_ADDFIRST[]    = u"AddFirst";
const char16_t MERGEFALLBACK_IGNORE[]      = u"Ignore";

const char16_t TOOLBARCONTROLLER_BUTTON[]  = u"Button";
const char16_t TOOLBARCONTROLLER_COMBOBOX[] = u"Combobox";
const char16_t TOOLBARCONTROLLER_EDIT[]    = u"Editfield";
const char16_t TOOLBARCONTROLLER_SPINFIELD[] = u"Spinfield";
const char16_t TOOLBARCONTROLLER_IMGBUTTON[] = u"ImageButton";
const char16_t TOOLBARCONTROLLER_DROPDOWNBOX[] = u"Dropdownbox";
const char16_t TOOLBARCONTROLLER_DROPDOWNBTN[] = u"DropdownButton";
const char16_t TOOLBARCONTROLLER_TOGGLEDDBTN[] = u"ToggleDropdownButton";
const char16_t TOOLBARCONTROLLER_FIXEDIMAGE[] = u"FixedImage";
const char16_t TOOLBARCONTROLLER_FIXEDTEXT[] = u"FixedText";

const char   TOOLBOXITEM_SEPARATOR_STR[]   = "private:separator";

using namespace ::com::sun::star;

/**
 Check whether a module identifier is part of a context
 defined by a colon separated list of module identifier.

 @param
     rContext

     Describes a context string list where all contexts
     are delimited by a colon. For more information about
     the module identifier used as context strings see the
     IDL description of css::frame::XModuleManager

 @param
     rModuleIdentifier

     A string describing a module identifier. See IDL
     description of css::frame::XModuleManager.

 @result
     The result is true if the rContext is an empty string
     or rModuleIdentifier is part of the context string.

*/
bool ToolBarMerger::IsCorrectContext(
    std::u16string_view rContext,
    std::u16string_view rModuleIdentifier )
{
    return ( rContext.empty() || ( rContext.find( rModuleIdentifier ) != std::u16string_view::npos ));
}

/**
 Converts a sequence, sequence of property values to
 a vector of structs.

 @param
     rSequence

     Provides a sequence, sequence of property values.

 @param
     rContainer

     A vector of AddonToolbarItems which will hold the
     conversion from the rSequence argument.
*/
void ToolBarMerger::ConvertSeqSeqToVector(
    const uno::Sequence< uno::Sequence< beans::PropertyValue > >& rSequence,
    AddonToolbarItemContainer& rContainer )
{
    sal_Int32 nLen( rSequence.getLength() );
    for ( sal_Int32 i = 0; i < nLen; i++ )
    {
        AddonToolbarItem aAddonToolbarItem;
        ConvertSequenceToValues( rSequence[i],
                                 aAddonToolbarItem.aCommandURL,
                                 aAddonToolbarItem.aLabel,
                                 aAddonToolbarItem.aTarget,
                                 aAddonToolbarItem.aContext,
                                 aAddonToolbarItem.aControlType,
                                 aAddonToolbarItem.nWidth );
        rContainer.push_back( aAddonToolbarItem );
    }
}

/**
 Converts a sequence of property values to single
 values.

 @param
     rSequence

     Provides a sequence of property values.

 @param
     rCommandURL

     Contains the value of the property with
     Name="CommandURL".

 @param
     rLabel

     Contains the value of the property with
     Name="Title"

 @param
     rTarget

     Contains the value of the property with
     Name="Target"

 @param
     rContext

     Contains the value of the property with
     Name="Context"

 @param
     rControlType

     Contains the value of the property with
     Name="ControlType"

 @result
     All possible mapping between sequence of property
     values and the single values are done.

*/
void ToolBarMerger::ConvertSequenceToValues(
    const uno::Sequence< beans::PropertyValue >& rSequence,
    OUString& rCommandURL,
    OUString& rLabel,
    OUString& rTarget,
    OUString& rContext,
    OUString& rControlType,
    sal_uInt16& rWidth )
{
    for ( beans::PropertyValue const & prop : rSequence )
    {
        if ( prop.Name == MERGE_TOOLBAR_URL )
            prop.Value >>= rCommandURL;
        else if ( prop.Name == MERGE_TOOLBAR_TITLE )
            prop.Value >>= rLabel;
        else if ( prop.Name == MERGE_TOOLBAR_CONTEXT )
            prop.Value >>= rContext;
        else if ( prop.Name == MERGE_TOOLBAR_TARGET )
            prop.Value >>= rTarget;
        else if ( prop.Name == MERGE_TOOLBAR_CONTROLTYPE )
            prop.Value >>= rControlType;
        else if ( prop.Name == MERGE_TOOLBAR_WIDTH )
        {
            sal_Int32 aValue = 0;
            prop.Value >>= aValue;
            rWidth = sal_uInt16( aValue );
        }
    }
}

/**
 Tries to find the reference point provided and delivers
 position and result of the search process.

 @param
     pToolbar

     Must be a valid pointer to a toolbar with items which
     should be searched.
@param
     nFirstItem

     First toolbar item to search from.

 @param
     rReferencePoint

     A command URL which should be the reference point for
     the coming merge operation.

 @result
     Provides information about the search result, the
     position of the reference point and the toolbar used.
*/
ReferenceToolbarPathInfo ToolBarMerger::FindReferencePoint(
    const ToolBox* pToolbar, sal_uInt16 nFirstItem,
    std::u16string_view rReferencePoint )
{
    ReferenceToolbarPathInfo aResult;
    aResult.bResult  = false;
    aResult.nPos     = ToolBox::ITEM_NOTFOUND;

    const ToolBox::ImplToolItems::size_type nSize( pToolbar->GetItemCount() );

    for ( ToolBox::ImplToolItems::size_type i = nFirstItem; i < nSize; i++ )
    {
        const ToolBoxItemId nItemId = pToolbar->GetItemId( i );
        if ( nItemId > ToolBoxItemId(0) )
        {
            const OUString rCmd = pToolbar->GetItemCommand( nItemId );
            if ( rCmd == rReferencePoint )
            {
                aResult.bResult = true;
                aResult.nPos    = i;
                return aResult;
            }
        }
    }

    return aResult;
}

/**
 Processes a merge operation.

 @param
     pToolbar

     A valid pointer to the toolbar where the merge
     operation is applied to.

 @param
     nPos

     The reference position of the toolbar item for
     the merge operation. Value must be between
     0 and number of toolbar items - 1.

 @param
     rItemId

     A unique item ID.

 @param
     rModuleIdentifier

     The current application module context.

 @param
     rMergeCommand

     A merge command.

 @param
     rMergeCommandParameter.

     An optional argument for the merge command.

 @param
     rItems

     Toolbar items which are associated to the merge
     command.

 @result
     Returns true for a successful operation otherwise
     false.
*/
bool ToolBarMerger::ProcessMergeOperation(
    ToolBox*                               pToolbar,
    ToolBox::ImplToolItems::size_type      nPos,
    ToolBoxItemId&                         rItemId,
    CommandToInfoMap&                      rCommandMap,
    std::u16string_view                    rModuleIdentifier,
    std::u16string_view                    rMergeCommand,
    std::u16string_view                    rMergeCommandParameter,
    const AddonToolbarItemContainer&       rItems )
{
    if ( rMergeCommand == MERGECOMMAND_ADDAFTER )
        MergeItems( pToolbar, nPos, 1, rItemId, rCommandMap, rModuleIdentifier, rItems );
    else if ( rMergeCommand == MERGECOMMAND_ADDBEFORE )
        MergeItems( pToolbar, nPos, 0, rItemId, rCommandMap, rModuleIdentifier, rItems );
    else if ( rMergeCommand == MERGECOMMAND_REPLACE )
        ReplaceItem( pToolbar, nPos, rItemId, rCommandMap, rModuleIdentifier, rItems );
    else if ( rMergeCommand == MERGECOMMAND_REMOVE )
        RemoveItems( pToolbar, nPos, rMergeCommandParameter );
    else
        return false;
    return true;
}

/**
 Processes a merge fallback operation.

 @param
     pToolbar

     A valid pointer to the toolbar where the merge
     fall back operation is applied to.

 @param
     nPos

     The reference position of the toolbar item for
     the merge operation. Value must be between
     0 and number of toolbar items - 1.

 @param
     rItemId

     A unique item ID.

 @param
     rModuleIdentifier

     The current application module context.

 @param
     rMergeCommand

     A merge command.

 @param
     rItems

     Toolbar items which are associated to the merge
     command.

 @result
     Returns true for a successful operation otherwise
     false.
*/
bool ToolBarMerger::ProcessMergeFallback(
    ToolBox*                         pToolbar,
    ToolBoxItemId&                   rItemId,
    CommandToInfoMap&                rCommandMap,
    std::u16string_view       rModuleIdentifier,
    std::u16string_view       rMergeCommand,
    std::u16string_view       rMergeFallback,
    const AddonToolbarItemContainer& rItems )
{
    if (( rMergeFallback == MERGEFALLBACK_IGNORE ) ||
        ( rMergeCommand == MERGECOMMAND_REPLACE ) ||
        ( rMergeCommand == MERGECOMMAND_REMOVE ) )
    {
        return true;
    }
    else if (( rMergeCommand == MERGECOMMAND_ADDBEFORE ) ||
             ( rMergeCommand == MERGECOMMAND_ADDAFTER ) )
    {
        if ( rMergeFallback == MERGEFALLBACK_ADDFIRST )
            MergeItems( pToolbar, 0, 0, rItemId, rCommandMap, rModuleIdentifier, rItems );
        else if ( rMergeFallback == MERGEFALLBACK_ADDLAST )
            MergeItems( pToolbar, ToolBox::APPEND, 0, rItemId, rCommandMap, rModuleIdentifier, rItems );
        else
            return false;
        return true;
    }

    return false;
}

/**
 Merges (adds) toolbar items into an existing toolbar.

 @param
     pToolbar

     A valid pointer to the toolbar where the merge
     fall back operation is applied to.

 @param
     nPos

     The reference position of the toolbar item for
     the merge operation. Value must be between
     0 and number of toolbar items - 1.

 @param
     rItemId

     A unique item ID.

 @param
     rModuleIdentifier

     The current application module context.

 @param
     rItems

     Toolbar items which are associated to the merge
     command.
*/
void ToolBarMerger::MergeItems(
    ToolBox*                               pToolbar,
    ToolBox::ImplToolItems::size_type      nPos,
    sal_uInt16                             nModIndex,
    ToolBoxItemId&                         rItemId,
    CommandToInfoMap&                      rCommandMap,
    std::u16string_view                    rModuleIdentifier,
    const AddonToolbarItemContainer&       rAddonToolbarItems )
{
    const sal_Int32 nSize( rAddonToolbarItems.size() );

    for ( sal_Int32 i = 0; i < nSize; i++ )
    {
        const AddonToolbarItem& rItem = rAddonToolbarItems[i];
        if ( IsCorrectContext( rItem.aContext, rModuleIdentifier ))
        {
            ToolBox::ImplToolItems::size_type nInsPos = nPos;
            if (nInsPos != ToolBox::APPEND)
            {
                nInsPos += nModIndex+i;
                if ( nInsPos > pToolbar->GetItemCount() )
                    nInsPos = ToolBox::APPEND;
            }

            if ( rItem.aCommandURL == TOOLBOXITEM_SEPARATOR_STR )
                pToolbar->InsertSeparator( nInsPos );
            else
            {
                CommandToInfoMap::iterator pIter = rCommandMap.find( rItem.aCommandURL );
                if ( pIter == rCommandMap.end())
                {
                    CommandInfo aCmdInfo;
                    aCmdInfo.nId = rItemId;
                    const CommandToInfoMap::value_type aValue( rItem.aCommandURL, aCmdInfo );
                    rCommandMap.insert( aValue );
                }
                else
                {
                    pIter->second.aIds.push_back( rItemId );
                }

                ToolBarMerger::CreateToolbarItem( pToolbar, nInsPos, rItemId, rItem );
            }

            ++rItemId;
        }
    }
}

/**
 Replaces a toolbar item with new items for an
 existing toolbar.

 @param
     pToolbar

     A valid pointer to the toolbar where the merge
     fall back operation is applied to.

 @param
     nPos

     The reference position of the toolbar item for
     the merge operation. Value must be between
     0 and number of toolbar items - 1.

 @param
     rItemId

     A unique item ID.

 @param
     rModuleIdentifier

     The current application module context.

 @param
     rItems

     Toolbar items which are associated to the merge
     command.
*/
void ToolBarMerger::ReplaceItem(
    ToolBox*                               pToolbar,
    ToolBox::ImplToolItems::size_type      nPos,
    ToolBoxItemId&                         rItemId,
    CommandToInfoMap&                      rCommandMap,
    std::u16string_view                    rModuleIdentifier,
    const AddonToolbarItemContainer&       rAddonToolbarItems )
{
    pToolbar->RemoveItem( nPos );
    MergeItems( pToolbar, nPos, 0, rItemId, rCommandMap, rModuleIdentifier, rAddonToolbarItems );
}

/**
 Removes toolbar items from an existing toolbar.

 @param
     pToolbar

     A valid pointer to the toolbar where the merge
     fall back operation is applied to.

 @param
     nPos

     The reference position of the toolbar item for
     the merge operation. Value must be between
     0 and number of toolbar items - 1.

 @param
     rMergeCommandParameter.

     An optional argument for the merge command.
*/
void ToolBarMerger::RemoveItems(
    ToolBox*                  pToolbar,
    ToolBox::ImplToolItems::size_type nPos,
    std::u16string_view rMergeCommandParameter )
{
    sal_Int32 nCount = o3tl::toInt32(rMergeCommandParameter);
    if ( nCount > 0 )
    {
        for ( sal_Int32 i = 0; i < nCount; i++ )
        {
            if ( nPos < pToolbar->GetItemCount() )
                pToolbar->RemoveItem( nPos );
        }
    }
}

/**
 Removes toolbar items from an existing toolbar.

 @param
     pToolbar

     A valid pointer to the toolbar where the merge
     fall back operation is applied to.

 @param
     nPos

     The reference position of the toolbar item for
     the merge operation. Value must be between
     0 and number of toolbar items - 1.

 @param
     rMergeCommandParameter.

     An optional argument for the merge command.

 @result
     Returns true for a successful operation otherwise
     false.
*/
rtl::Reference<::cppu::OWeakObject> ToolBarMerger::CreateController(
    const uno::Reference< uno::XComponentContext >& rxContext,
    const uno::Reference< frame::XFrame > & xFrame,
    ToolBox*               pToolbar,
    const OUString& rCommandURL,
    ToolBoxItemId          nId,
    sal_uInt16             nWidth,
    std::u16string_view rControlType )
{
    rtl::Reference<::cppu::OWeakObject> pResult;

    if ( rControlType == TOOLBARCONTROLLER_BUTTON )
        pResult = new ButtonToolbarController( rxContext, pToolbar, rCommandURL );
    else if ( rControlType == TOOLBARCONTROLLER_COMBOBOX )
        pResult = new ComboboxToolbarController( rxContext, xFrame, pToolbar, nId, nWidth, rCommandURL );
    else if ( rControlType == TOOLBARCONTROLLER_EDIT )
        pResult = new EditToolbarController( rxContext, xFrame, pToolbar, nId, nWidth, rCommandURL );
    else if ( rControlType == TOOLBARCONTROLLER_SPINFIELD )
        pResult = new SpinfieldToolbarController( rxContext, xFrame, pToolbar, nId, nWidth, rCommandURL );
    else if ( rControlType == TOOLBARCONTROLLER_IMGBUTTON )
        pResult = new ImageButtonToolbarController( rxContext, xFrame, pToolbar, nId, rCommandURL );
    else if ( rControlType == TOOLBARCONTROLLER_DROPDOWNBOX )
        pResult = new DropdownToolbarController( rxContext, xFrame, pToolbar, nId, nWidth, rCommandURL );
    else if ( rControlType == TOOLBARCONTROLLER_DROPDOWNBTN )
        pResult = new ToggleButtonToolbarController( rxContext, xFrame, pToolbar, nId,
                                                     ToggleButtonToolbarController::Style::DropDownButton, rCommandURL );
    else if ( rControlType == TOOLBARCONTROLLER_FIXEDIMAGE )
        pResult = new FixedImageToolbarController( rxContext, xFrame, pToolbar, nId, rCommandURL );
    else if ( rControlType == TOOLBARCONTROLLER_FIXEDTEXT )
        pResult = new FixedTextToolbarController( rxContext, xFrame, pToolbar, nId, rCommandURL );
    else if ( rControlType == TOOLBARCONTROLLER_TOGGLEDDBTN )
        pResult = new ToggleButtonToolbarController( rxContext, xFrame, pToolbar, nId,
                                                     ToggleButtonToolbarController::Style::ToggleDropDownButton, rCommandURL );
    else
        pResult = new GenericToolbarController( rxContext, xFrame, pToolbar, nId, rCommandURL );

    return pResult;
}

void ToolBarMerger::CreateToolbarItem( ToolBox* pToolbar, ToolBox::ImplToolItems::size_type nPos, ToolBoxItemId nItemId, const AddonToolbarItem& rItem )
{
    assert(pToolbar->GetItemData(nItemId) == nullptr); // that future would contain a double free

    pToolbar->InsertItem( nItemId, rItem.aLabel, rItem.aCommandURL, ToolBoxItemBits::NONE, nPos );
    pToolbar->SetQuickHelpText( nItemId, rItem.aLabel );
    pToolbar->SetItemText( nItemId, rItem.aLabel );
    pToolbar->EnableItem( nItemId );
    pToolbar->SetItemState( nItemId, TRISTATE_FALSE );

    // Use the user data to store add-on specific data with the toolbar item
    AddonsParams* pAddonParams = new AddonsParams;
    pAddonParams->aControlType = rItem.aControlType;
    pAddonParams->nWidth       = rItem.nWidth;
    pToolbar->SetItemData( nItemId, pAddonParams );
}

} // namespace framework

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