/* -*- 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 <comphelper/string.hxx>
#include <tools/bigint.hxx>
#include <sal/log.hxx>
 
#include <vcl/event.hxx>
#include <vcl/svapp.hxx>
#include <vcl/toolkit/longcurr.hxx>
#include <vcl/weldutils.hxx>
 
#include <unotools/localedatawrapper.hxx>
 
using namespace ::comphelper;
 
namespace
{
 
BigInt ImplPower10( sal_uInt16 n )
{
    sal_uInt16 i;
    BigInt   nValue = 1;
 
    for ( i=0; i < n; i++ )
        nValue *= 10;
 
    return nValue;
}
 
OUString ImplGetCurr( const LocaleDataWrapper& rLocaleDataWrapper, const BigInt &rNumber, sal_uInt16 nDigits, std::u16string_view rCurrSymbol, bool bShowThousandSep )
{
    SAL_WARN_IF( nDigits >= 10, "vcl", "LongCurrency may only have 9 decimal places" );
 
    if ( rNumber.IsZero() || static_cast<tools::Long>(rNumber) )
        return rLocaleDataWrapper.getCurr( static_cast<tools::Long>(rNumber), nDigits, rCurrSymbol, bShowThousandSep );
 
    BigInt aTmp( ImplPower10( nDigits ) );
    BigInt aInteger;
    BigInt aFraction;
    rNumber.Abs().DivMod(aTmp, &aInteger, &aFraction);
    if ( !aInteger.IsZero() )
    {
        aFraction += aTmp;
        aTmp       = 1000000000;
    }
    if ( rNumber.IsNeg() )
        aFraction *= -1;
 
    OUStringBuffer aTemplate(rLocaleDataWrapper.getCurr( static_cast<tools::Long>(aFraction), nDigits, rCurrSymbol, bShowThousandSep ));
    while( !aInteger.IsZero() )
    {
        aInteger.DivMod(aTmp, &aInteger, &aFraction);
        if( !aInteger.IsZero() )
            aFraction += aTmp;
 
        OUString aFractionStr = rLocaleDataWrapper.getNum( static_cast<tools::Long>(aFraction), 0 );
 
        sal_Int32 nSPos = aTemplate.indexOf( '1' );
        if (nSPos == -1)
            break;
        if ( aFractionStr.getLength() == 1 )
            aTemplate[ nSPos ] = aFractionStr[0];
        else
        {
            aTemplate.remove( nSPos, 1 );
            aTemplate.insert( nSPos, aFractionStr  );
        }
    }
 
    return aTemplate.makeStringAndClear();
}
 
bool ImplCurrencyGetValue( const OUString& rStr, BigInt& rValue,
                                 sal_uInt16 nDecDigits, const LocaleDataWrapper& rLocaleDataWrapper )
{
    OUString aStr = rStr;
    OUStringBuffer aStr1;
    OUStringBuffer aStr2;
    sal_Int32 nDecPos;
    bool bNegative = false;
 
    // On empty string
    if ( rStr.isEmpty() )
        return false;
 
    // Trim leading and trailing spaces
    aStr = string::strip(aStr, ' ');
 
    // Find decimal sign's position
    nDecPos = aStr.indexOf( rLocaleDataWrapper.getNumDecimalSep() );
    if (nDecPos < 0 && !rLocaleDataWrapper.getNumDecimalSepAlt().isEmpty())
        nDecPos = aStr.indexOf( rLocaleDataWrapper.getNumDecimalSepAlt() );
 
    if ( nDecPos != -1 )
    {
        aStr1 = aStr.subView( 0, nDecPos );
        aStr2.append(aStr.subView(nDecPos+1));
    }
    else
        aStr1 = aStr;
 
    // Negative?
    if ( (aStr[ 0 ] == '(') && (aStr[ aStr.getLength()-1 ] == ')') )
        bNegative = true;
    if ( !bNegative )
    {
        for (sal_Int32 i=0; i < aStr.getLength(); i++ )
        {
            if ( (aStr[ i ] >= '0') && (aStr[ i ] <= '9') )
                break;
            else if ( aStr[ i ] == '-' )
            {
                bNegative = true;
                break;
            }
        }
    }
    if ( !bNegative && !aStr.isEmpty() )
    {
        sal_uInt16 nFormat = rLocaleDataWrapper.getCurrNegativeFormat();
        if ( (nFormat == 3) || (nFormat == 6)  ||
             (nFormat == 7) || (nFormat == 10) )
        {
            for (sal_Int32 i = aStr.getLength()-1; i > 0; i++ )
            {
                if ( (aStr[ i ] >= '0') && (aStr[ i ] <= '9') )
                    break;
                else if ( aStr[ i ] == '-' )
                {
                    bNegative = true;
                    break;
                }
            }
        }
    }
 
    // delete unwanted characters
    for (sal_Int32 i=0; i < aStr1.getLength(); )
    {
        if ( (aStr1[ i ] >= '0') && (aStr1[ i ] <= '9') )
            i++;
        else
            aStr1.remove( i, 1 );
    }
    for (sal_Int32 i=0; i < aStr2.getLength(); )
    {
        if ((aStr2[i] >= '0') && (aStr2[i] <= '9'))
            ++i;
        else
            aStr2.remove(i, 1);
    }
 
    if ( aStr1.isEmpty() && aStr2.isEmpty())
        return false;
 
    if ( aStr1.isEmpty() )
        aStr1 = "0";
    if ( bNegative )
        aStr1.insert( 0, '-');
 
    // Cut down decimal part and round while doing so
    bool bRound = false;
    if (aStr2.getLength() > nDecDigits)
    {
        if (aStr2[nDecDigits] >= '5')
            bRound = true;
        string::truncateToLength(aStr2, nDecDigits);
    }
    string::padToLength(aStr2, nDecDigits, '0');
 
    aStr1.append(aStr2);
    aStr  = aStr1.makeStringAndClear();
 
    // check range
    BigInt nValue( aStr );
    if ( bRound )
    {
        if ( !bNegative )
            nValue+=1;
        else
            nValue-=1;
    }
 
    rValue = nValue;
 
    return true;
}
 
} // namespace
 
static bool ImplLongCurrencyGetValue( const OUString& rStr, BigInt& rValue,
                                      sal_uInt16 nDecDigits, const LocaleDataWrapper& rLocaleDataWrapper )
{
    return ImplCurrencyGetValue( rStr, rValue, nDecDigits, rLocaleDataWrapper );
}
 
namespace weld
{
    IMPL_LINK_NOARG(LongCurrencyFormatter, FormatOutputHdl, LinkParamNone*, bool)
    {
        const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
        const OUString& rCurrencySymbol = !m_aCurrencySymbol.isEmpty() ? m_aCurrencySymbol : rLocaleDataWrapper.getCurrSymbol();
        double fValue = GetValue() * weld::SpinButton::Power10(GetDecimalDigits());
        OUString aText = ImplGetCurr(rLocaleDataWrapper, fValue, GetDecimalDigits(), rCurrencySymbol, m_bThousandSep);
        ImplSetTextImpl(aText, nullptr);
        return true;
    }
 
    IMPL_LINK(LongCurrencyFormatter, ParseInputHdl, sal_Int64*, result, TriState)
    {
        const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper();
 
        BigInt value;
        bool bRet = ImplLongCurrencyGetValue(GetEntryText(), value, GetDecimalDigits(), rLocaleDataWrapper);
 
        if (bRet)
            *result = double(value);
 
        return bRet ? TRISTATE_TRUE : TRISTATE_FALSE;
    }
}
 
bool ImplLongCurrencyReformat( const OUString& rStr, BigInt const & nMin, BigInt const & nMax,
                               sal_uInt16 nDecDigits,
                               const LocaleDataWrapper& rLocaleDataWrapper, OUString& rOutStr,
                               LongCurrencyFormatter const & rFormatter )
{
    BigInt nValue;
    if ( !ImplCurrencyGetValue( rStr, nValue, nDecDigits, rLocaleDataWrapper ) )
        return true;
    else
    {
        BigInt nTempVal = nValue;
        if ( nTempVal > nMax )
            nTempVal = nMax;
        else if ( nTempVal < nMin )
            nTempVal = nMin;
 
        rOutStr = ImplGetCurr( rLocaleDataWrapper, nTempVal, nDecDigits, rFormatter.GetCurrencySymbol(), /*IsUseThousandSep*/true );
        return true;
    }
}
 
void LongCurrencyFormatter::ImpInit()
{
    mnLastValue         = 0;
    mnMin               = 0;
    mnMax               = 0x7FFFFFFF;
    mnMax              *= 0x7FFFFFFF;
    mnDecimalDigits     = 0;
    SetDecimalDigits( 0 );
}
 
LongCurrencyFormatter::LongCurrencyFormatter(Edit* pEdit)
    : FormatterBase(pEdit)
{
    ImpInit();
}
 
LongCurrencyFormatter::~LongCurrencyFormatter()
{
}
 
OUString const & LongCurrencyFormatter::GetCurrencySymbol() const
{
    return GetLocaleDataWrapper().getCurrSymbol();
}
 
void LongCurrencyFormatter::SetValue(const BigInt& rNewValue)
{
    SetUserValue(rNewValue);
    SetEmptyFieldValueData( false );
}
 
void LongCurrencyFormatter::SetUserValue( BigInt nNewValue )
{
    if ( nNewValue > mnMax )
        nNewValue = mnMax;
    else if ( nNewValue < mnMin )
        nNewValue = mnMin;
    mnLastValue = nNewValue;
 
    if ( !GetField() )
        return;
 
    OUString aStr = ImplGetCurr( GetLocaleDataWrapper(), nNewValue, GetDecimalDigits(), GetCurrencySymbol(), /*UseThousandSep*/true );
    if ( GetField()->HasFocus() )
    {
        Selection aSelection = GetField()->GetSelection();
        GetField()->SetText( aStr );
        GetField()->SetSelection( aSelection );
    }
    else
        GetField()->SetText( aStr );
    MarkToBeReformatted( false );
}
 
BigInt LongCurrencyFormatter::GetValue() const
{
    if ( !GetField() )
        return 0;
 
    BigInt nTempValue;
    if ( ImplLongCurrencyGetValue( GetField()->GetText(), nTempValue, GetDecimalDigits(), GetLocaleDataWrapper() ) )
    {
        if ( nTempValue > mnMax )
            nTempValue = mnMax;
        else if ( nTempValue < mnMin )
            nTempValue = mnMin;
        return nTempValue;
    }
    else
        return mnLastValue;
}
 
void LongCurrencyFormatter::Reformat()
{
    if ( !GetField() )
        return;
 
    if ( GetField()->GetText().isEmpty() && ImplGetEmptyFieldValue() )
        return;
 
    OUString aStr;
    bool bOK = ImplLongCurrencyReformat( GetField()->GetText(), mnMin, mnMax,
                                         GetDecimalDigits(), GetLocaleDataWrapper(), aStr, *this );
    if ( !bOK )
        return;
 
    if ( !aStr.isEmpty() )
    {
        GetField()->SetText( aStr );
        MarkToBeReformatted( false );
        ImplLongCurrencyGetValue( aStr, mnLastValue, GetDecimalDigits(), GetLocaleDataWrapper() );
    }
    else
        SetValue( mnLastValue );
}
 
void LongCurrencyFormatter::ReformatAll()
{
    Reformat();
}
 
void LongCurrencyFormatter::SetDecimalDigits( sal_uInt16 nDigits )
{
    if ( nDigits > 9 )
        nDigits = 9;
 
    mnDecimalDigits = nDigits;
    ReformatAll();
}
 
 
 
LongCurrencyBox::LongCurrencyBox(vcl::Window* pParent, WinBits nWinStyle)
    : ComboBox(pParent, nWinStyle)
    , LongCurrencyFormatter(this)
{
    Reformat();
}
 
bool LongCurrencyBox::EventNotify( NotifyEvent& rNEvt )
{
    if( rNEvt.GetType() == NotifyEventType::GETFOCUS )
    {
        MarkToBeReformatted( false );
    }
    else if( rNEvt.GetType() == NotifyEventType::LOSEFOCUS )
    {
        if ( MustBeReformatted() )
        {
            Reformat();
            ComboBox::Modify();
        }
    }
    return ComboBox::EventNotify( rNEvt );
}
 
void LongCurrencyBox::Modify()
{
    MarkToBeReformatted( true );
    ComboBox::Modify();
}
 
void LongCurrencyBox::ReformatAll()
{
    OUString aStr;
    SetUpdateMode( false );
    const sal_Int32 nEntryCount = GetEntryCount();
    for ( sal_Int32 i=0; i < nEntryCount; ++i )
    {
        ImplLongCurrencyReformat( GetEntry( i ), mnMin, mnMax,
                                  GetDecimalDigits(), GetLocaleDataWrapper(),
                                  aStr, *this );
        RemoveEntryAt(i);
        InsertEntry( aStr, i );
    }
    LongCurrencyFormatter::Reformat();
    SetUpdateMode( true );
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'remove' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'remove' is required to be utilized.

V530 The return value of function 'remove' is required to be utilized.

V530 The return value of function 'insert' is required to be utilized.

V530 The return value of function 'truncateToLength' is required to be utilized.

V530 The return value of function 'padToLength' is required to be utilized.

V1053 Calling the 'ReformatAll' virtual function indirectly in the constructor may lead to unexpected result at runtime. Check lines: 'longcurr.cxx:278', 'longcurr.cxx:272', 'longcurr.cxx:372', 'longcurr.hxx:39'.