/**************************************************************
 *
 * 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
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_framework.hxx"

#include <uielement/menubarmerger.hxx>
#include <framework/addonsoptions.hxx>

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

static const char SEPARATOR_STRING[]               = "private:separator";
static const sal_uInt32 SEPARATOR_STRING_LEN       = 17;

static const char MERGECOMMAND_ADDAFTER[]          = "AddAfter";
static const sal_uInt32 MERGECOMMAND_ADDAFTER_LEN  = 8;
static const char MERGECOMMAND_ADDBEFORE[]         = "AddBefore";
static const sal_uInt32 MERGECOMMAND_ADDBEFORE_LEN = 9;
static const char MERGECOMMAND_REPLACE[]           = "Replace";
static const sal_uInt32 MERGECOMMAND_REPLACE_LEN   = 7;
static const char MERGECOMMAND_REMOVE[]            = "Remove";
static const sal_uInt32 MERGECOMMAND_REMOVE_LEN    = 6;

static const char MERGEFALLBACK_ADDPATH[]           = "AddPath";
static const char MERGEFALLBACK_ADDPATH_LEN         = 7;
static const char MERGEFALLBACK_IGNORE[]            = "Ignore";
static const char MERGEFALLBACK_IGNORE_LEN          = 6;


namespace framework
{

/**
 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 com::sun::star::frame::XModuleManager

  @param
     rModuleIdentifier

     A string describing a module identifier. See IDL
     description of com::sun::star::frame::XModuleManager.

*/
bool MenuBarMerger::IsCorrectContext( const ::rtl::OUString& rContext, const ::rtl::OUString& rModuleIdentifier )
{
    return (( rContext.getLength() == 0 ) || ( rContext.indexOf( rModuleIdentifier ) >= 0 ));
}

void MenuBarMerger::RetrieveReferencePath(
    const ::rtl::OUString& rReferencePathString,
    ::std::vector< ::rtl::OUString >& rReferencePath )
{
    const sal_Char aDelimiter = '\\';

    rReferencePath.clear();
    sal_Int32 nIndex( 0 );
	do
	{
        ::rtl::OUString aToken = rReferencePathString.getToken( 0, aDelimiter, nIndex );
        if ( aToken.getLength() > 0 )
            rReferencePath.push_back( aToken );
    }
	while ( nIndex >= 0 );
}

ReferencePathInfo MenuBarMerger::FindReferencePath(
    const ::std::vector< ::rtl::OUString >& rReferencePath,
    Menu* pMenu )
{
    sal_uInt32       i( 0 );
    const sal_uInt32 nCount( rReferencePath.size() );
    Menu*            pCurrMenu( pMenu );
    RPResultInfo     eResult( RP_OK );

    sal_Int32  nLevel( - 1 );
    sal_uInt16 nPos( MENU_ITEM_NOTFOUND );
    do
    {
        ++nLevel;
        ::rtl::OUString aCmd( rReferencePath[i] );

        if ( i == nCount-1 )
        {
            // Check last reference path element. Must be a leave (menu item).
            sal_uInt16 nTmpPos = FindMenuItem( aCmd, pCurrMenu );
            if ( nTmpPos != MENU_ITEM_NOTFOUND )
                nPos = nTmpPos;
            eResult = ( nTmpPos != MENU_ITEM_NOTFOUND ) ? RP_OK : RP_MENUITEM_NOT_FOUND;
        }
        else
        {
            // Check reference path element. Must be a node (popup menu)!
            sal_uInt16 nTmpPos = FindMenuItem( aCmd, pCurrMenu );
            if ( nTmpPos != MENU_ITEM_NOTFOUND )
            {
                sal_uInt16 nItemId = pCurrMenu->GetItemId( nTmpPos );
                Menu* pTmpMenu     = pCurrMenu->GetPopupMenu( nItemId );
                if ( pTmpMenu != 0 )
                    pCurrMenu = pTmpMenu;
                else
                {
                    nPos    = nTmpPos;
                    eResult = RP_MENUITEM_INSTEAD_OF_POPUPMENU_FOUND;
                }
            }
            else
                eResult = RP_POPUPMENU_NOT_FOUND;
        }
        i++;
    }
    while (( pCurrMenu != 0 ) && ( i < nCount ) && ( eResult == RP_OK ));

    ReferencePathInfo aResult;
    aResult.pPopupMenu = pCurrMenu;
    aResult.nPos       = nPos;
    aResult.nLevel     = nLevel;
    aResult.eResult    = eResult;

    return aResult;
}

sal_uInt16 MenuBarMerger::FindMenuItem( const ::rtl::OUString& rCmd, Menu* pCurrMenu )
{
    for ( sal_uInt16 i = 0; i < pCurrMenu->GetItemCount(); i++ )
    {
        const sal_uInt16 nItemId = pCurrMenu->GetItemId( i );
        if ( nItemId > 0 )
        {
            if ( rCmd == ::rtl::OUString( pCurrMenu->GetItemCommand( nItemId )))
                return i;
        }
    }

    return MENU_ITEM_NOTFOUND;
}

bool MenuBarMerger::CreateSubMenu(
    Menu*                     pSubMenu,
    sal_uInt16&               nItemId,
    const ::rtl::OUString&    rModuleIdentifier,
    const AddonMenuContainer& rAddonSubMenu )
{
    const sal_uInt32 nSize = rAddonSubMenu.size();
    for ( sal_uInt32 i = 0; i < nSize; i++ )
    {
        const AddonMenuItem& rMenuItem = rAddonSubMenu[i];

        if ( IsCorrectContext( rMenuItem.aContext, rModuleIdentifier ))
        {
            if ( rMenuItem.aURL.equalsAsciiL( SEPARATOR_STRING, SEPARATOR_STRING_LEN ))
            {
                pSubMenu->InsertSeparator( MENU_APPEND );
            }
            else
            {
                pSubMenu->InsertItem( nItemId, rMenuItem.aTitle, 0, MENU_APPEND );
                pSubMenu->SetItemCommand( nItemId, rMenuItem.aURL );
                if ( !rMenuItem.aSubMenu.empty() )
                {
                    PopupMenu* pPopupMenu = new PopupMenu();
                    pSubMenu->SetPopupMenu( nItemId, pPopupMenu );
                    ++nItemId;

                    CreateSubMenu( pPopupMenu, nItemId, rModuleIdentifier, rMenuItem.aSubMenu );
                }
                else
                    ++nItemId;
            }
        }
    }

    return true;
}

bool MenuBarMerger::MergeMenuItems(
    Menu*                     pMenu,
    sal_uInt16                nPos,
    sal_uInt16                nModIndex,
    sal_uInt16&               nItemId,
    const ::rtl::OUString&    rModuleIdentifier,
    const AddonMenuContainer& rAddonMenuItems )
{
    sal_uInt16       nIndex( 0 );
    const sal_uInt32 nSize = rAddonMenuItems.size();
    for ( sal_uInt32 i = 0; i < nSize; i++ )
    {
        const AddonMenuItem& rMenuItem = rAddonMenuItems[i];

        if ( IsCorrectContext( rMenuItem.aContext, rModuleIdentifier ))
        {
            if ( rMenuItem.aURL.equalsAsciiL( SEPARATOR_STRING, SEPARATOR_STRING_LEN ))
            {
                pMenu->InsertSeparator( nPos+nModIndex+nIndex );
            }
            else
            {
                pMenu->InsertItem( nItemId, rMenuItem.aTitle, 0, nPos+nModIndex+nIndex );
                pMenu->SetItemCommand( nItemId, rMenuItem.aURL );
                if ( !rMenuItem.aSubMenu.empty() )
                {
                    PopupMenu* pSubMenu = new PopupMenu();
                    pMenu->SetPopupMenu( nItemId, pSubMenu );
                    ++nItemId;

                    CreateSubMenu( pSubMenu, nItemId, rModuleIdentifier, rMenuItem.aSubMenu );
                }
                else
                    ++nItemId;
            }
            ++nIndex;
        }
    }

    return true;
}

bool MenuBarMerger::ReplaceMenuItem(
    Menu*                     pMenu,
    sal_uInt16                nPos,
    sal_uInt16&               rItemId,
    const ::rtl::OUString&    rModuleIdentifier,
    const AddonMenuContainer& rAddonMenuItems )
{
    // There is no replace available. Therfore we first have to
    // remove the old menu entry,
    pMenu->RemoveItem( nPos );

    return MergeMenuItems( pMenu, nPos, 0, rItemId, rModuleIdentifier, rAddonMenuItems );
}

bool MenuBarMerger::RemoveMenuItems(
    Menu*                     pMenu,
    sal_uInt16                nPos,
    const ::rtl::OUString&    rMergeCommandParameter )
{
    const sal_uInt16 nParam( sal_uInt16( rMergeCommandParameter.toInt32() ));
    sal_uInt16       nCount( 1 );

    nCount = std::max( nParam, nCount );

    sal_uInt16 i = 0;
    while (( nPos < pMenu->GetItemCount() ) && ( i < nCount ))
    {
        pMenu->RemoveItem( nPos );
        ++i;
    }

    return true;
}

bool MenuBarMerger::ProcessMergeOperation(
    Menu*                     pMenu,
    sal_uInt16                nPos,
    sal_uInt16&               nItemId,
    const ::rtl::OUString&    rMergeCommand,
    const ::rtl::OUString&    rMergeCommandParameter,
    const ::rtl::OUString&    rModuleIdentifier,
    const AddonMenuContainer& rAddonMenuItems )
{
    sal_uInt16 nModIndex( 0 );

    if ( rMergeCommand.equalsAsciiL( MERGECOMMAND_ADDBEFORE, MERGECOMMAND_ADDBEFORE_LEN ))
    {
        nModIndex = 0;
        return MergeMenuItems( pMenu, nPos, nModIndex, nItemId, rModuleIdentifier, rAddonMenuItems );
    }
    else if ( rMergeCommand.equalsAsciiL( MERGECOMMAND_ADDAFTER, MERGECOMMAND_ADDAFTER_LEN ))
    {
        nModIndex = 1;
        return MergeMenuItems( pMenu, nPos, nModIndex, nItemId, rModuleIdentifier, rAddonMenuItems );
    }
    else if ( rMergeCommand.equalsAsciiL( MERGECOMMAND_REPLACE, MERGECOMMAND_REPLACE_LEN ))
    {
        return ReplaceMenuItem( pMenu, nPos, nItemId, rModuleIdentifier, rAddonMenuItems );
    }
    else if ( rMergeCommand.equalsAsciiL( MERGECOMMAND_REMOVE, MERGECOMMAND_REMOVE_LEN ))
    {
        return RemoveMenuItems( pMenu, nPos, rMergeCommandParameter );
    }

    return false;
}

bool MenuBarMerger::ProcessFallbackOperation(
    const ReferencePathInfo&                aRefPathInfo,
    sal_uInt16&                             rItemId,
    const ::rtl::OUString&                  rMergeCommand,
    const ::rtl::OUString&                  rMergeFallback,
    const ::std::vector< ::rtl::OUString >& rReferencePath,
    const ::rtl::OUString&                  rModuleIdentifier,
    const AddonMenuContainer&               rAddonMenuItems )
{
    if (( rMergeFallback.equalsAsciiL( MERGEFALLBACK_IGNORE, MERGEFALLBACK_IGNORE_LEN )) ||
        ( rMergeCommand.equalsAsciiL(  MERGECOMMAND_REPLACE, MERGECOMMAND_REPLACE_LEN )) ||
        ( rMergeCommand.equalsAsciiL(  MERGECOMMAND_REMOVE, MERGECOMMAND_REMOVE_LEN   )) )
    {
        return true;
    }
    else if ( rMergeFallback.equalsAsciiL( MERGEFALLBACK_ADDPATH, MERGEFALLBACK_ADDPATH_LEN ))
    {
        Menu*            pCurrMenu( aRefPathInfo.pPopupMenu );
        sal_Int32        nLevel( aRefPathInfo.nLevel );
        const sal_Int32  nSize( rReferencePath.size() );
        bool             bFirstLevel( true );

        while ( nLevel < nSize )
        {
            if ( nLevel == nSize-1 )
            {
                const sal_uInt32 nCount = rAddonMenuItems.size();
                for ( sal_uInt32 i = 0; i < nCount; ++i )
                {
                    const AddonMenuItem& rMenuItem = rAddonMenuItems[i];
                    if ( IsCorrectContext( rMenuItem.aContext, rModuleIdentifier ))
                    {
                        if ( rMenuItem.aURL.equalsAsciiL( SEPARATOR_STRING, SEPARATOR_STRING_LEN ))
                            pCurrMenu->InsertSeparator( MENU_APPEND );
                        else
                        {
                            pCurrMenu->InsertItem( rItemId, rMenuItem.aTitle, 0, MENU_APPEND );
                            pCurrMenu->SetItemCommand( rItemId, rMenuItem.aURL );
                            ++rItemId;
                        }
                    }
                }
            }
            else
            {
                const ::rtl::OUString aCmd( rReferencePath[nLevel] );

                sal_uInt16 nInsPos( MENU_APPEND );
                PopupMenu* pPopupMenu( new PopupMenu );

                if ( bFirstLevel && ( aRefPathInfo.eResult == RP_MENUITEM_INSTEAD_OF_POPUPMENU_FOUND ))
                {
                    // special case: menu item without popup
                    nInsPos = aRefPathInfo.nPos;
                    sal_uInt16 nSetItemId = pCurrMenu->GetItemId( nInsPos );
                    pCurrMenu->SetItemCommand( nSetItemId, aCmd );
                    pCurrMenu->SetPopupMenu( nSetItemId, pPopupMenu );
                }
                else
                {
                    // normal case: insert a new item with popup
                    pCurrMenu->InsertItem( rItemId, ::rtl::OUString(), 0, MENU_APPEND );
                    pCurrMenu->SetItemCommand( rItemId, aCmd );
                    pCurrMenu->SetPopupMenu( rItemId, pPopupMenu );
                }

                pCurrMenu = pPopupMenu;
                ++rItemId;
                bFirstLevel = false;
            }
            ++nLevel;
        }
        return true;
    }

    return false;
}

void MenuBarMerger::GetMenuEntry(
    const uno::Sequence< beans::PropertyValue >& rAddonMenuEntry,
    AddonMenuItem&                               rAddonMenuItem )
{
	// Reset submenu member
    rAddonMenuItem.aSubMenu.clear();

	for ( sal_Int32 i = 0; i < rAddonMenuEntry.getLength(); i++ )
	{
        ::rtl::OUString aMenuEntryPropName = rAddonMenuEntry[i].Name;
		if ( aMenuEntryPropName.equalsAsciiL( ADDONSMENUITEM_STRING_URL, ADDONSMENUITEM_URL_LEN ))
			rAddonMenuEntry[i].Value >>= rAddonMenuItem.aURL;
		else if ( aMenuEntryPropName.equalsAsciiL( ADDONSMENUITEM_STRING_TITLE, ADDONSMENUITEM_TITLE_LEN ))
			rAddonMenuEntry[i].Value >>= rAddonMenuItem.aTitle;
		else if ( aMenuEntryPropName.equalsAsciiL( ADDONSMENUITEM_STRING_TARGET, ADDONSMENUITEM_TARGET_LEN ))
			rAddonMenuEntry[i].Value >>= rAddonMenuItem.aTarget;
		else if ( aMenuEntryPropName.equalsAsciiL( ADDONSMENUITEM_STRING_SUBMENU, ADDONSMENUITEM_SUBMENU_LEN ))
        {
            uno::Sequence< uno::Sequence< beans::PropertyValue > > aSubMenu;
            rAddonMenuEntry[i].Value >>= aSubMenu;
            GetSubMenu( aSubMenu, rAddonMenuItem.aSubMenu );
        }
		else if ( aMenuEntryPropName.equalsAsciiL( ADDONSMENUITEM_STRING_CONTEXT, ADDONSMENUITEM_CONTEXT_LEN ))
			rAddonMenuEntry[i].Value >>= rAddonMenuItem.aContext;
		else if ( aMenuEntryPropName.equalsAsciiL( ADDONSMENUITEM_STRING_IMAGEIDENTIFIER, ADDONSMENUITEM_IMAGEIDENTIFIER_LEN ))
			rAddonMenuEntry[i].Value >>= rAddonMenuItem.aImageId;
	}
}

void MenuBarMerger::GetSubMenu(
    const uno::Sequence< uno::Sequence< beans::PropertyValue > >& rSubMenuEntries,
    AddonMenuContainer&                                           rSubMenu )
{
    rSubMenu.clear();

    const sal_Int32 nCount = rSubMenuEntries.getLength();
    rSubMenu.reserve(rSubMenu.size() + nCount);
    for ( sal_Int32 i = 0; i < nCount; i++ )
    {
        const uno::Sequence< beans::PropertyValue >& rMenuEntry = rSubMenuEntries[ i ];

        AddonMenuItem aMenuItem;
        GetMenuEntry( rMenuEntry, aMenuItem );
        rSubMenu.push_back( aMenuItem );
    }
}

} // namespace framework
