/* -*- 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 <rtl/ustring.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <vcl/wrkwin.hxx>
#include <svtools/langtab.hxx>
 
#include <vcl/errinf.hxx>
#include <editeng/unolingu.hxx>
#include <linguistic/lngprops.hxx>
#include <com/sun/star/frame/XStorable.hpp>
 
#include <editeng/svxenum.hxx>
#include <editeng/splwrap.hxx>
#include <editeng/edtdlg.hxx>
#include <editeng/eerdll.hxx>
#include <editeng/editrids.hrc>
#include <editeng/editids.hrc>
#include <editeng/editerr.hxx>
 
#include <map>
#include <memory>
 
#define WAIT_ON() if(pWin != nullptr) { pWin->EnterWait(); }
 
#define WAIT_OFF() if(pWin != nullptr) { pWin->LeaveWait(); }
 
using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::linguistic2;
 
 
// misc functions ---------------------------------------------
 
void SvxPrepareAutoCorrect( OUString &rOldText, const OUString &rNewText )
{
    // This function should be used to strip (or add) trailing '.' from
    // the strings before passing them on to the autocorrect function in
    // order that the autocorrect function will hopefully
    // works properly with normal words and abbreviations (with trailing '.')
    // independent of if they are at the end of the sentence or not.
    //
    // rOldText: text to be replaced
    // rNewText: replacement text
 
    sal_Int32 nOldLen = rOldText.getLength();
    sal_Int32 nNewLen = rNewText.getLength();
    if (nOldLen && nNewLen)
    {
        bool bOldHasDot = '.' == rOldText[ nOldLen - 1 ],
             bNewHasDot = '.' == rNewText[ nNewLen - 1 ];
        if (bOldHasDot && !bNewHasDot
            /*this is: !(bOldHasDot && bNewHasDot) && bOldHasDot*/)
            rOldText = rOldText.copy( 0, nOldLen - 1 );
    }
}
 
#define SVX_LANG_NEED_CHECK         0
#define SVX_LANG_OK                 1
#define SVX_LANG_MISSING            2
#define SVX_LANG_MISSING_DO_WARN    3
 
typedef std::map< LanguageType, sal_uInt16 >   LangCheckState_map_t;
 
static LangCheckState_map_t & GetLangCheckState()
{
    static LangCheckState_map_t aLangCheckState;
    return aLangCheckState;
}
 
void SvxSpellWrapper::ShowLanguageErrors()
{
    // display message boxes for languages not available for
    // spellchecking or hyphenation
    LangCheckState_map_t &rLCS = GetLangCheckState();
    for (auto const& elem : rLCS)
    {
        LanguageType nLang = elem.first;
        sal_uInt16   nVal  = elem.second;
        sal_uInt16 nTmpSpell = nVal & 0x00FF;
        sal_uInt16 nTmpHyph  = (nVal >> 8) & 0x00FF;
 
        if (SVX_LANG_MISSING_DO_WARN == nTmpSpell)
        {
            OUString aErr( SvtLanguageTable::GetLanguageString( nLang ) );
            ErrorHandler::HandleError(
                *new StringErrorInfo( ERRCODE_SVX_LINGU_LANGUAGENOTEXISTS, aErr ) );
            nTmpSpell = SVX_LANG_MISSING;
        }
        if (SVX_LANG_MISSING_DO_WARN == nTmpHyph)
        {
            OUString aErr( SvtLanguageTable::GetLanguageString( nLang ) );
            ErrorHandler::HandleError(
                *new StringErrorInfo( ERRCODE_SVX_LINGU_LANGUAGENOTEXISTS, aErr ) );
            nTmpHyph = SVX_LANG_MISSING;
        }
 
        rLCS[ nLang ] = (nTmpHyph << 8) | nTmpSpell;
    }
 
}
 
SvxSpellWrapper::~SvxSpellWrapper()
{
}
 
/*--------------------------------------------------------------------
 *  Description: Constructor, the test sequence is determined
 *
 *  !bStart && !bOtherCntnt:    BODY_END,   BODY_START, OTHER
 *  !bStart && bOtherCntnt:     OTHER,      BODY
 *  bStart && !bOtherCntnt:     BODY_END,   OTHER
 *  bStart && bOtherCntnt:      OTHER
 *
 --------------------------------------------------------------------*/
 
SvxSpellWrapper::SvxSpellWrapper( vcl::Window* pWn,
    const bool bStart, const bool bIsAllRight ) :
 
    pWin        ( pWn ),
    bOtherCntnt ( false ),
    bHyphen     ( false ),
    bStartChk   ( false ),
    bRevAllowed ( true ),
    bAllRight   ( bIsAllRight )
{
    Reference< linguistic2::XLinguProperties >  xProp( LinguMgr::GetLinguPropertySet() );
    bool bWrapReverse = xProp.is() && xProp->getIsWrapReverse();
    bReverse = bWrapReverse;
    bStartDone = !bReverse && bStart;
    bEndDone   = bReverse && bStart;
}
 
 
SvxSpellWrapper::SvxSpellWrapper( vcl::Window* pWn,
        Reference< XHyphenator > const &xHyphenator,
        const bool bStart, const bool bOther ) :
    pWin        ( pWn ),
    xHyph       ( xHyphenator ),
    bOtherCntnt ( bOther ),
    bHyphen     ( false ),
    bReverse    ( false ),
    bStartDone  ( bOther || ( !bReverse && bStart ) ),
    bEndDone    ( bReverse && bStart && !bOther ),
    bStartChk   ( bOther ),
    bRevAllowed ( false ),
    bAllRight   ( true )
{
}
 
 
sal_Int16 SvxSpellWrapper::CheckSpellLang(
        Reference< XSpellChecker1 > const & xSpell, LanguageType nLang)
{
    LangCheckState_map_t &rLCS = GetLangCheckState();
 
    LangCheckState_map_t::iterator aIt( rLCS.find( nLang ) );
    sal_uInt16 nVal = aIt == rLCS.end() ? SVX_LANG_NEED_CHECK : aIt->second;
 
    if (aIt == rLCS.end())
        rLCS[ nLang ] = nVal;
 
    if (SVX_LANG_NEED_CHECK == (nVal & 0x00FF))
    {
        sal_uInt16 nTmpVal = SVX_LANG_MISSING_DO_WARN;
        if (xSpell.is()  &&  xSpell->hasLanguage( static_cast<sal_uInt16>(nLang) ))
            nTmpVal = SVX_LANG_OK;
        nVal &= 0xFF00;
        nVal |= nTmpVal;
 
        rLCS[ nLang ] = nVal;
    }
 
    return static_cast<sal_Int16>(nVal);
}
 
sal_Int16 SvxSpellWrapper::CheckHyphLang(
        Reference< XHyphenator > const & xHyph, LanguageType nLang)
{
    LangCheckState_map_t &rLCS = GetLangCheckState();
 
    LangCheckState_map_t::iterator aIt( rLCS.find( nLang ) );
    sal_uInt16 nVal = aIt == rLCS.end() ? 0 : aIt->second;
 
    if (aIt == rLCS.end())
        rLCS[ nLang ] = nVal;
 
    if (SVX_LANG_NEED_CHECK == ((nVal >> 8) & 0x00FF))
    {
        sal_uInt16 nTmpVal = SVX_LANG_MISSING_DO_WARN;
        if (xHyph.is()  &&  xHyph->hasLocale( LanguageTag::convertToLocale( nLang ) ))
            nTmpVal = SVX_LANG_OK;
        nVal &= 0x00FF;
        nVal |= nTmpVal << 8;
 
        rLCS[ nLang ] = nVal;
    }
 
    return static_cast<sal_Int16>(nVal);
}
 
 
void SvxSpellWrapper::SpellStart( SvxSpellArea /*eSpell*/ )
{ // Here, the necessary preparations be made for SpellContinue in the
} // given area.
 
 
bool SvxSpellWrapper::HasOtherCnt()
{
    return false; // Is there a special area?
}
 
 
bool SvxSpellWrapper::SpellMore()
{
    return false; // Should additional documents be examined?
}
 
 
void SvxSpellWrapper::SpellEnd()
{   // Area is complete, tidy up if necessary
 
    // display error for last language not found
    ShowLanguageErrors();
}
 
void SvxSpellWrapper::SpellContinue()
{
}
 
void SvxSpellWrapper::ReplaceAll( const OUString & )
{   // Replace Word from the Replace list
}
 
void SvxSpellWrapper::InsertHyphen( const sal_Int32 )
{   // inserting and deleting Hyphen
}
 
// Testing of the document areas in the order specified by the flags
void SvxSpellWrapper::SpellDocument( )
{
    if ( bOtherCntnt )
    {
        bReverse = false;
        SpellStart( SvxSpellArea::Other );
    }
    else
    {
        bStartChk = bReverse;
        SpellStart( bReverse ? SvxSpellArea::BodyStart : SvxSpellArea::BodyEnd );
    }
 
    if ( FindSpellError() )
    {
        Reference< XHyphenatedWord >        xHyphWord( GetLast(), UNO_QUERY );
 
        vcl::Window *pOld = pWin;
        if (xHyphWord.is())
        {
            EditAbstractDialogFactory* pFact = EditAbstractDialogFactory::Create();
            ScopedVclPtr<AbstractHyphenWordDialog> pDlg(pFact->CreateHyphenWordDialog( pWin,
                            xHyphWord->getWord(),
                            LanguageTag( xHyphWord->getLocale() ).getLanguageType(),
                            xHyph, this ));
            pWin = pDlg->GetWindow();
            pDlg->Execute();
        }
        pWin = pOld;
    }
}
 
 
// Select the next area
 
 
bool SvxSpellWrapper::SpellNext( )
{
    Reference< linguistic2::XLinguProperties >  xProp( LinguMgr::GetLinguPropertySet() );
    bool bWrapReverse = xProp.is() && xProp->getIsWrapReverse();
    bool bActRev = bRevAllowed && bWrapReverse;
 
    // bActRev is the direction after Spell checking, bReverse is the one
    // at the beginning.
    if( bActRev == bReverse )
    {                           // No change of direction, thus is the
        if( bStartChk )         // desired area ( bStartChk )
            bStartDone = true;  // completely processed.
        else
            bEndDone = true;
    }
    else if( bReverse == bStartChk ) //For a change of direction, an area can
    {                          // be processed during certain circumstances
        if( bStartChk )        // If the first part is spell checked in backwards
            bEndDone = true;   // and this is reversed in the process, then
        else                   // then the end part is processed (and vice-versa).
            bStartDone = true;
    }
 
    bReverse = bActRev;
    if( bOtherCntnt && bStartDone && bEndDone ) // Document has been fully checked?
    {
        if ( SpellMore() )  // spell check another document?
        {
            bOtherCntnt = false;
            bStartDone = !bReverse;
            bEndDone  = bReverse;
            SpellStart( SvxSpellArea::Body );
            return true;
        }
        return false;
    }
 
    bool bGoOn = false;
 
    if ( bOtherCntnt )
    {
        bStartChk = false;
        SpellStart( SvxSpellArea::Body );
        bGoOn = true;
    }
    else if ( bStartDone && bEndDone )
    {
        bool bIsSpellSpecial = xProp.is() && xProp->getIsSpellSpecial();
        // Body area done, ask for special area
        if( !IsHyphen() && bIsSpellSpecial && HasOtherCnt() )
        {
            SpellStart( SvxSpellArea::Other );
            bOtherCntnt = bGoOn = true;
        }
        else if ( SpellMore() )  // check another document?
        {
            bOtherCntnt = false;
            bStartDone = !bReverse;
            bEndDone  = bReverse;
            SpellStart( SvxSpellArea::Body );
            return true;
        }
    }
    else
    {
        // a BODY_area done, ask for the other BODY_area
        WAIT_OFF();
 
        const char* pResId = bReverse ? RID_SVXSTR_QUERY_BW_CONTINUE : RID_SVXSTR_QUERY_CONTINUE;
        std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pWin ? pWin->GetFrameWeld() : nullptr,
                                                                 VclMessageType::Question, VclButtonsType::YesNo,
                                                                 EditResId(pResId)));
        if (xBox->run() != RET_YES)
        {
            // sacrifice the other area if necessary ask for special area
            WAIT_ON();
            bStartDone = bEndDone = true;
            return SpellNext();
        }
        else
        {
            bStartChk = !bStartDone;
            SpellStart( bStartChk ? SvxSpellArea::BodyStart : SvxSpellArea::BodyEnd );
            bGoOn = true;
        }
        WAIT_ON();
    }
    return bGoOn;
}
 
 
Reference< XDictionary >  SvxSpellWrapper::GetAllRightDic()
{
    Reference< XDictionary >  xDic;
 
    Reference< XSearchableDictionaryList >  xDicList( LinguMgr::GetDictionaryList() );
    if (xDicList.is())
    {
        Sequence< Reference< XDictionary >  > aDics( xDicList->getDictionaries() );
        const Reference< XDictionary >  *pDic = aDics.getConstArray();
        sal_Int32 nCount = aDics.getLength();
 
        sal_Int32 i = 0;
        while (!xDic.is()  &&  i < nCount)
        {
            Reference< XDictionary >  xTmp( pDic[i], UNO_QUERY );
            if (xTmp.is())
            {
                if ( xTmp->isActive() &&
                     xTmp->getDictionaryType() != DictionaryType_NEGATIVE &&
                     LanguageTag( xTmp->getLocale() ).getLanguageType() == LANGUAGE_NONE )
                {
                    Reference< frame::XStorable >  xStor( xTmp, UNO_QUERY );
                    if (xStor.is() && xStor->hasLocation() && !xStor->isReadonly())
                    {
                        xDic = xTmp;
                    }
                }
            }
            ++i;
        }
 
        if (!xDic.is())
        {
            xDic = LinguMgr::GetStandardDic();
            if (xDic.is())
                xDic->setActive( true );
        }
    }
 
    return xDic;
}
 
 
bool SvxSpellWrapper::FindSpellError()
{
    ShowLanguageErrors();
 
    WAIT_ON();
    bool bSpell = true;
 
    Reference< XDictionary >  xAllRightDic;
    if (IsAllRight())
        xAllRightDic = GetAllRightDic();
 
    while ( bSpell )
    {
        SpellContinue();
 
        Reference< XSpellAlternatives >     xAlt( GetLast(), UNO_QUERY );
        Reference< XHyphenatedWord >        xHyphWord( GetLast(), UNO_QUERY );
 
        if (xAlt.is())
        {
            if (IsAllRight() && xAllRightDic.is())
            {
                xAllRightDic->add( xAlt->getWord(), false, OUString() );
            }
            else
            {
                // look up in ChangeAllList for misspelled word
                Reference< XDictionary >    xChangeAllList(
                        LinguMgr::GetChangeAllList(), UNO_QUERY );
                Reference< XDictionaryEntry >   xEntry;
                if (xChangeAllList.is())
                    xEntry = xChangeAllList->getEntry( xAlt->getWord() );
 
                if (xEntry.is())
                {
                    // replace word without asking
                    ReplaceAll( xEntry->getReplacementText() );
                }
                else
                    bSpell = false;
            }
        }
        else if (xHyphWord.is())
            bSpell = false;
        else
        {
            SpellEnd();
            bSpell = SpellNext();
        }
    }
    WAIT_OFF();
    return GetLast().is();
}
 
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V560 A part of conditional expression is always true: !bReverse.