/*
    SPDX-FileCopyrightText: 2010-2020 Thomas Baumgart <tbaumgart@kde.org>
    SPDX-FileCopyrightText: 2017-2018 Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "amountedit.h"

// ----------------------------------------------------------------------------
// QT Includes

#include <QDebug>
#include <QFrame>
#include <QKeyEvent>
#include <QLocale>
#include <QStyle>
#include <QToolButton>

// ----------------------------------------------------------------------------
// KDE Includes

#include <KConfigGroup>
#include <KLocalizedString>
#include <KSharedConfig>

// ----------------------------------------------------------------------------
// Project Includes

#include "amountvalidator.h"
#include "kmymoneycalculator.h"
#include "mymoneysecurity.h"
#include "icons.h"
#include "popuppositioner.h"

using namespace Icons;

class AmountEditHelper
{
public:
    AmountEditHelper() : q(nullptr) {}
    ~AmountEditHelper() {
        delete q;
    }
    AmountEdit *q;
};

Q_GLOBAL_STATIC(AmountEditHelper, s_globalAmountEdit)

AmountEdit* AmountEdit::global()
{
    if (!s_globalAmountEdit()->q) {
        s_globalAmountEdit()->q = new AmountEdit(nullptr, 2);
    }

    return s_globalAmountEdit()->q;
}

class AmountEditPrivate
{
    Q_DISABLE_COPY(AmountEditPrivate)
    Q_DECLARE_PUBLIC(AmountEdit)

public:
    enum Item {
        NoItem = 0x0,
        ShowCalculator = 0x1,
        ShowCurrencySymbol = 0x02,
        ShowAll = 0x3,
    };
    Q_DECLARE_FLAGS(Items, Item)

    explicit AmountEditPrivate(AmountEdit* qq)
        : q_ptr(qq)
        , m_calculatorFrame(nullptr)
        , m_calculator(nullptr)
        , m_calculatorButton(nullptr)
        , m_prec(2)
        , m_allowEmpty(false)
        , m_precisionOverridesFraction(false)
        , m_isReadOnly(false)
        , m_allowModifyShares(true)
        , m_isEnabled(qq->isEnabled())
        , m_lastChangeWasRemove(false)
        , m_actionIcons(NoItem)
        , m_initialExchangeRate(MyMoneyMoney::ONE)
        , m_state(AmountEdit::DisplayValue)
        , m_lastChanged(AmountEdit::NothingChanged)
        , sharesSet(false)
        , valueSet(false)
        , m_isCashAmount(false)
    {
        m_calculatorFrame = new QFrame;
        m_calculatorFrame->setWindowFlags(Qt::Popup);

        m_calculatorFrame->setFrameStyle(QFrame::Panel | QFrame::Raised);
        m_calculatorFrame->setLineWidth(3);

        m_calculator = new KMyMoneyCalculator(m_calculatorFrame);
        m_calculatorFrame->hide();
    }

    void init()
    {
        Q_Q(AmountEdit);
        // Yes, just a simple double validator !
        auto validator = new AmountValidator(q);
        q->setValidator(validator);
        q->setAlignment(Qt::AlignRight | Qt::AlignVCenter);

        const auto btnSize = q->sizeHint().height() - 5;

        m_calculatorButton = new QToolButton(q);
        m_calculatorButton->setIcon(Icons::get(Icon::Calculator));
        m_calculatorButton->setCursor(Qt::ArrowCursor);
        m_calculatorButton->setStyleSheet("QToolButton { border: none; padding: 2px}");
        m_calculatorButton->setFixedSize(btnSize, btnSize);
        m_calculatorButton->setFocusPolicy(Qt::ClickFocus);

        q->connect(m_calculatorButton, &QAbstractButton::clicked, q, &AmountEdit::slotCalculatorOpen);

        m_currencyButton = new QToolButton(q);
        m_currencyButton->setCursor(Qt::ArrowCursor);
        m_currencyButton->setAutoRaise(true);
        m_currencyButton->hide();
        m_currencyButton->setFocusPolicy(Qt::ClickFocus);
        m_currencyButton->setFont(q->font());

        // setup items
        KSharedConfig::Ptr kconfig = KSharedConfig::openConfig();
        KConfigGroup grp = kconfig->group("General Options");
        q->setCalculatorButtonVisible(!grp.readEntry("DontShowCalculatorButton", false));
        q->setCurrencySymbol(QString(), QString());

        updateLineEditSize(m_currencyButton, true);

        q->connect(q, &QLineEdit::textChanged, q, &AmountEdit::theTextChanged);
        q->connect(m_calculator, &KMyMoneyCalculator::signalResultAvailable, q, &AmountEdit::slotCalculatorResult);
        q->connect(m_calculator, &KMyMoneyCalculator::signalQuit, q, &AmountEdit::slotCalculatorClose);
    }

    /**
      * Internal helper function for value() and ensureFractionalPart().
      */
    void ensureFractionalPart(QString& s, MultiCurrencyEdit::DisplayState state) const
    {
        s = MyMoneyMoney(s).formatMoney(QString(), precision(state), false);
    }

    /**
      * This method opens the calculator and replays the key
      * event pointed to by @p ev. If @p ev is 0, then no key
      * event is replayed.
      *
      * @param ev pointer to QKeyEvent that started the calculator.
      */
    void calculatorOpen(QKeyEvent* k)
    {
        Q_Q(AmountEdit);
        m_calculator->setInitialValues(q->text(), k);

        // do not open the calculator in read-only mode
        if (q->isReadOnly())
            return;

        // show calculator and update size
        m_calculatorFrame->show();
        m_calculatorFrame->setGeometry(m_calculator->geometry());

        PopupPositioner pos(q, m_calculatorFrame, PopupPositioner::BottemLeft);
        m_calculator->setFocus();
    }

    void updateLineEditSize(QWidget* widget, int w, int h)
    {
        Q_Q(AmountEdit);
        widget->resize(w, h);

        int height = q->sizeHint().height();
        int frameWidth = q->style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
        const int btnHeight = height - 2*frameWidth;
        const int btnX = (height - btnHeight) / 2;
        const int currencyX = (height - h) / 2;

        int gaps = 0;
        gaps += m_actionIcons.testFlag(ShowCalculator) ? 1 : 0;
        gaps += m_actionIcons.testFlag(ShowCurrencySymbol) ? 1 : 0;

        m_calculatorButton->move(q->width() - m_calculatorButton->width() - frameWidth, btnX);
        widget->move(q->width() - m_calculatorButton->width() - widget->width() - gaps * frameWidth, currencyX);

        // It was reported in https://bugs.kde.org/show_bug.cgi?id=497255 that
        // changing the font of the ledger cells updates all widgets in the
        // transaction editors except the amount widgets. While investigating
        // the problem, it has been noticed, that when setting the style sheet
        // a second time, a previously modified font size gets reset to the system
        // size. So in case we are about to change the stylesheet, we keep a copy
        // of the font and set it back to that copy after changing the stylesheet.
        //
        // The observed behavior may have something to do with the default setting of
        // Qt::AA_UseStyleSheetPropagationInWidgetStyles.
        const int padding = m_calculatorButton->width() + widget->width() + ((gaps - 1) * frameWidth);
        const auto font(q->font());
        q->setStyleSheet(QStringLiteral("QLineEdit { padding-right: %1px }").arg(padding));
        // avoid infinite recursive calls here
        q->QLineEdit::setFont(font);
        q->setMinimumHeight(height);
    }

    bool hasMultipleCurrencies() const
    {
        return m_sharesCommodity.id().compare(m_valueCommodity.id());
    }

    void updateLineEditSize(QToolButton* button, bool ofs = false)
    {
        Q_Q(AmountEdit);
        const int currencyWidth = q->fontMetrics().boundingRect(button->text()).width() + 10;
        const auto frameWidth = ofs ? q->style()->pixelMetric(QStyle::PM_DefaultFrameWidth) : 0;

        const int currencyHeight = button->height() - frameWidth;

        AmountEditPrivate::updateLineEditSize(button, currencyWidth, currencyHeight);
    }

    void setCurrencySymbol(const QString& symbol, const QString& name)
    {
        m_currencyButton->setText(symbol);
        m_currencyButton->setToolTip(name);
        m_currencyButton->setHidden(symbol.isEmpty());
        m_actionIcons.setFlag(AmountEditPrivate::ShowCurrencySymbol, !symbol.isEmpty());
        updateLineEditSize(m_currencyButton);
    }

    void updateWidgets()
    {
        Q_Q(AmountEdit);
        if (hasMultipleCurrencies()) {
            m_currencyButton->setEnabled(true);
            QString currentCurrency;
            QString otherCurrency;
            // prevent to change the values due to emitted textChanged() signals
            QSignalBlocker block(q);
            if (m_state == AmountEdit::DisplayShares) {
                currentCurrency = m_sharesCommodity.name();
                otherCurrency = m_valueCommodity.name();
                setCurrencySymbol(m_sharesCommodity.tradingSymbol(), currentCurrency);
                if (!m_sharesText.isEmpty()) {
                    m_sharesText = MyMoneyMoney(m_sharesText).formatMoney(QString(), precision(AmountEdit::DisplayShares), false);
                }
                q->QLineEdit::setText(m_sharesText);
                setReadOnly(!(m_isReadOnly || m_allowModifyShares));
            } else {
                currentCurrency = m_valueCommodity.name();
                otherCurrency = m_sharesCommodity.name();
                setCurrencySymbol(m_valueCommodity.tradingSymbol(), m_valueCommodity.name());
                if (!m_valueText.isEmpty()) {
                    m_valueText = MyMoneyMoney(m_valueText).formatMoney(QString(), precision(AmountEdit::DisplayValue), false);
                }
                q->QLineEdit::setText(m_valueText);
                setReadOnly(m_isReadOnly);
            }

            m_currencyButton->setToolTip(i18nc("@info:tooltip Swap currencies for entry/display",
                                               "Value are presented in %1. Press this button to switch the currency to %2.",
                                               currentCurrency,
                                               otherCurrency));

        } else {
            if (!m_valueCommodity.id().isEmpty()) {
                m_currencyButton->setEnabled(false);
                m_currencyButton->setToolTip(i18nc("@info:tooltip Swap currencies for entry/display", "Values are presented in %1.", m_valueCommodity.name()));
                setCurrencySymbol(m_valueCommodity.tradingSymbol(), m_valueCommodity.name());

            } else {
                // hide the currency symbol
                setCurrencySymbol(QString(), QString());
            }
        }

        const auto isAutoCalc = m_shares.isAutoCalc();
        q->setDisabled(isAutoCalc);
        if (isAutoCalc) {
            q->QLineEdit::setText(i18nc("@info:placeholder amount widget", "calculated"));
        }
    }

    void swapCommodities()
    {
        Q_Q(AmountEdit);
        q->setDisplayState((m_state == AmountEdit::DisplayShares) ? AmountEdit::DisplayValue : AmountEdit::DisplayShares);
    }

    MyMoneyMoney adjustToPrecision(AmountEdit::DisplayState state, const MyMoneyMoney& amount) const
    {
        auto money(amount);
        const MyMoneySecurity& sec((state == AmountEdit::DisplayValue) ? m_valueCommodity : m_sharesCommodity);
        if (!m_precisionOverridesFraction && !sec.id().isEmpty()) {
            const auto fraction = m_isCashAmount ? sec.smallestCashFraction() : sec.smallestAccountFraction();
            money = money.convert(fraction);
        } else if (m_prec != -1)
            money = money.convert(MyMoneyMoney::precToDenom(m_prec));
        return money;
    }

    int precision(AmountEdit::DisplayState state) const
    {
        const MyMoneySecurity& sec((state == AmountEdit::DisplayValue) ? m_valueCommodity : m_sharesCommodity);
        auto prec(m_prec);

        if (!m_precisionOverridesFraction && !sec.id().isEmpty()) {
            prec = MyMoneyMoney::denomToPrec(m_isCashAmount ? sec.smallestCashFraction() : sec.smallestAccountFraction());
        }
        return prec;
    }

    void setWidgetText(QString& widgetTextCache, const MyMoneyMoney& amount, const QString& txt, MultiCurrencyEdit::DisplayState state)
    {
        Q_Q(AmountEdit);
        bool enabled = false;
        if (!amount.isAutoCalc()) {
            enabled = m_isEnabled;
            widgetTextCache = txt;
            if (q->isEnabled() && !txt.isEmpty()) {
                ensureFractionalPart(widgetTextCache, state);
                // ensureFractionalPart is used to add digits but
                // it should not remove some. If that's the
                // case we revert what it has done
                if (widgetTextCache.length() < txt.length()) {
                    widgetTextCache = txt;
                }
            }
            // only update text if it really differs
            if (m_state == state && widgetTextCache.compare(q->QLineEdit::text())) {
                // prevent to change the values due to emitted textChanged() signals
                QSignalBlocker block(q);
                q->QLineEdit::setText(widgetTextCache);
            }
        } else {
            QSignalBlocker block(q);
            q->QLineEdit::setText(i18nc("@info:placeholder amount widget", "calculated"));
        }
        // use the base class version to keep m_isEnabled as it is
        q->QLineEdit::setEnabled(enabled);
    }

    void setValueText(const QString& txt)
    {
        setWidgetText(m_valueText, m_value, txt, AmountEdit::DisplayValue);
    }

    void setSharesText(const QString& txt)
    {
        setWidgetText(m_sharesText, m_shares, txt, AmountEdit::DisplayShares);
    }

    void setReadOnly(bool ro)
    {
        Q_Q(AmountEdit);
        m_calculatorButton->setEnabled(!ro);
        q->QLineEdit::setReadOnly(ro);
    }

    /**
     * This updates the widget defined by @a widgetSelector which
     * could be @c AmountEdit::DisplayValue or @c AmountEdit::DisplayShares.
     * In case the last change to the widget was the removal of a
     * character, the precision is unlimited and the value of the
     * updated string did not change, we keep what is in the widget
     * and do not modify it.
     */
    void updateWidgetText(const AmountEdit::DisplayState widgetSelector, const MyMoneyMoney& amount)
    {
        auto& widget = (widgetSelector == AmountEdit::DisplayValue) ? m_valueText : m_sharesText;
        const auto fract = precision(widgetSelector);
        auto txt(amount.formatMoney(QString(), fract, false));

        // the following logic checks that we keep a possible decimal
        // symbol in the display when the last digit after the decimal
        // symbol has been removed and no specific precision is known
        if (m_lastChangeWasRemove && (fract == -1) && (MyMoneyMoney(widget) == MyMoneyMoney(txt))) {
            txt = widget;
        }
        setWidgetText(widget, amount, txt, widgetSelector);
    }

    AmountEdit* q_ptr;
    QFrame* m_calculatorFrame;
    KMyMoneyCalculator* m_calculator;
    QToolButton* m_calculatorButton;
    QToolButton* m_currencyButton;
    int m_prec;
    bool m_allowEmpty;
    bool m_precisionOverridesFraction;
    bool m_isReadOnly; // read only state as set by application
    bool m_allowModifyShares; // allow to override shares even if read only state
    bool m_isEnabled;
    bool m_lastChangeWasRemove;
    QString m_previousText; // keep track of what has been typed

    QString m_valueText; // keep track of what was the original value
    QString m_sharesText; // keep track of what was the original value

    QFlags<Item> m_actionIcons;

    MyMoneyMoney m_value; // original amount when starting editing
    MyMoneyMoney m_shares; // original amount when starting editing
    MyMoneyMoney m_initialExchangeRate;

    MyMoneySecurity m_sharesCommodity;
    MyMoneySecurity m_valueCommodity;

    /**
     * Which part is displayed and which one was last modified
     */
    AmountEdit::DisplayState m_state;
    AmountEdit::LastValueChanged m_lastChanged;

    bool sharesSet;
    bool valueSet;
    bool m_isCashAmount;
};

Q_DECLARE_OPERATORS_FOR_FLAGS(AmountEditPrivate::Items)

void AmountEdit::setReadOnly(bool ro)
{
    Q_D(AmountEdit);
    d->m_isReadOnly = ro;
    d->m_allowModifyShares = !ro;
    d->setReadOnly(ro);
}

AmountEdit::AmountEdit(QWidget *parent, const int prec) :
    QLineEdit(parent),
    d_ptr(new AmountEditPrivate(this))
{
    Q_D(AmountEdit);
    d->m_prec = prec;
    if (prec < -1 || prec > 20) {
        d->m_prec = AmountEdit::global()->standardPrecision();
    }

    d->init();

    connect(d->m_currencyButton, &QToolButton::clicked, this, [&]() {
        Q_D(AmountEdit);
        d->swapCommodities();
    });
}

AmountEdit::AmountEdit(const MyMoneySecurity& sec, QWidget *parent) :
    QLineEdit(parent),
    d_ptr(new AmountEditPrivate(this))
{
    Q_D(AmountEdit);
    d->m_prec = MyMoneyMoney::denomToPrec(sec.smallestAccountFraction());
    d->init();
}

AmountEdit::AmountEdit(QWidget* parent, const int prec, AmountEditPrivate* dd)
    : QLineEdit(parent)
    , d_ptr(dd)
{
    Q_D(AmountEdit);
    d->m_prec = prec;
    if (prec < -1 || prec > 20) {
        d->m_prec = AmountEdit::global()->standardPrecision();
    }
    d->init();
}

AmountEdit::~AmountEdit()
{
    Q_D(AmountEdit);
    delete d;
}

void AmountEdit::setStandardPrecision(int prec)
{
    if (prec >= 0 && prec < 20) {
        global()->d_ptr->m_prec = prec;
    }
}

int AmountEdit::standardPrecision()
{
    return global()->d_ptr->m_prec;
}


void AmountEdit::resizeEvent(QResizeEvent* event)
{
    Q_UNUSED(event);
    Q_D(AmountEdit);
    d->updateLineEditSize(d->m_currencyButton);
}

void AmountEdit::focusInEvent(QFocusEvent* event)
{
    QLineEdit::focusInEvent(event);
    if (event->reason() != Qt::ActiveWindowFocusReason) {
        if (!hasSelectedText()) {
            // we need to wait until all processing is done before
            // we can successfully call selectAll. Hence the
            // delayed execution when we return back to the event loop
            metaObject()->invokeMethod(this, &QLineEdit::selectAll, Qt::QueuedConnection);
        }
    }
}

void AmountEdit::focusOutEvent(QFocusEvent* event)
{
    Q_D(AmountEdit);
    QLineEdit::focusOutEvent(event);

    if ((event->reason() != Qt::ActiveWindowFocusReason) && !isReadOnly()) {
        // make sure we have a zero value in case the current text
        // is empty but this is not allowed
        if (text().isEmpty() && !d->m_allowEmpty) {
            QLineEdit::setText(QLatin1String("0"));
        }

        // make sure we have a fractional part
        if (!text().isEmpty())
            ensureFractionalPart();

        // in case the widget contains a different value we emit
        // the amountChanged signal
        if ((d->m_value != value()) || (d->m_shares != shares())) {
            d->m_value = value();
            d->m_shares = shares();
            d->valueSet = true;
            Q_EMIT amountChanged();
        }
    }
}

void AmountEdit::keyPressEvent(QKeyEvent* event)
{
    auto suppressVerticalMovement = [&](QKeyEvent* ev) {
        switch (ev->key()) {
        case Qt::Key_Up:
        case Qt::Key_Down:
        case Qt::Key_PageUp:
        case Qt::Key_PageDown:
            ev->accept();
            break;
        default:
            break;
        }
    };

    Q_D(AmountEdit);
    if (!isReadOnly()) {
        switch (event->key()) {
        case Qt::Key_Plus:
        case Qt::Key_Minus:
            if (text().length() == 0) {
                break;
            }
            // in case of '-' we do not enter the calculator when
            // the current position is the beginning and there is
            // no '-' sign at the first position.
            if (event->key() == Qt::Key_Minus) {
                if (cursorPosition() == 0 && text().at(0) != '-') {
                    break;
                }
            }
            // intentional fall through

        case Qt::Key_Slash:
        case Qt::Key_Asterisk:
        case Qt::Key_Percent:
            d->calculatorOpen(event);
            return;

        case Qt::Key_Return:
        case Qt::Key_Escape:
        case Qt::Key_Enter:
            break;

        case Qt::Key_Space:
            if (event->modifiers() == Qt::ControlModifier) {
                event->accept();
                d->m_currencyButton->animateClick();
                return;
            }
            break;

        default:
            // make sure to use the locale's decimalPoint when the
            // keypad comma/dot is pressed
            QString keyText = event->text();
            int key = event->key();
            if (event->modifiers() & Qt::KeypadModifier) {
                if ((key == Qt::Key_Period) || (key == Qt::Key_Comma)) {
                    key = MyMoneyMoney::decimalSeparator().unicode();
                    keyText = MyMoneyMoney::decimalSeparator();
                }
                // create a (possibly adjusted) copy of the event
                QKeyEvent newEvent(event->type(),
                                   key,
                                   event->modifiers(),
                                   event->nativeScanCode(),
                                   event->nativeVirtualKey(),
                                   event->nativeModifiers(),
                                   keyText,
                                   event->isAutoRepeat(),
                                   event->count());

                // in case all text is selected and the user presses the decimal point
                // we fill the widget with the leading "0". The outcome of this will be
                // that the widget then contains "0.".
                if ((newEvent.key() == MyMoneyMoney::decimalSeparator().unicode()) && (selectedText() == text())) {
                    QLineEdit::setText(QLatin1String("0"));
                }
                QLineEdit::keyPressEvent(&newEvent);

                // in case the key was not handled by the base class
                // we have to make sure that we do not report any
                // vertical movement to upper layers
                suppressVerticalMovement(&newEvent);

                // propagate acceptance state to the event that
                // is visible to the caller
                event->setAccepted(newEvent.isAccepted());
                return;
            }
            break;
        }
    }

    // in case we have not processed anything, we
    // need to call the base class implementation
    // we keep the current content so we can check
    // if the key pressure did change and was consumed
    const auto oldContent = text();
    QLineEdit::keyPressEvent(event);

    // in case the key was not handled by the base class
    // we have to make sure that we do not report any
    // vertical movement to upper layers
    suppressVerticalMovement(event);

    // if no change, we assume no consumption and
    // propagate the event to parent widgets. This
    // allows shortcuts to be handled in upstream
    // widgets.
    event->setAccepted(event->isAccepted() || (text() != oldContent));
}

void AmountEdit::setPrecision(const int prec, bool forceUpdate)
{
    Q_D(AmountEdit);
    if (prec >= -1 && prec <= 20) {
        if (prec != d->m_prec || forceUpdate) {
            d->m_prec = prec;
            // force update of current display
            setValue(value(), true);
        }
    }
}

int AmountEdit::precision() const
{
    Q_D(const AmountEdit);
    return d->m_prec;
}

int AmountEdit::precision(MultiCurrencyEdit::DisplayState state) const
{
    Q_D(const AmountEdit);
    return d->precision(state);
}

MultiCurrencyEdit::DisplayState AmountEdit::displayState() const
{
    Q_D(const AmountEdit);
    return d->m_state;
}

bool AmountEdit::isValid() const
{
    return !(text().isEmpty());
}

MyMoneyMoney AmountEdit::value() const
{
    Q_D(const AmountEdit);
    MyMoneyMoney money(d->m_valueText);
    return d->adjustToPrecision(AmountEdit::DisplayValue, money);
}

void AmountEdit::setValue(const MyMoneyMoney& amount, bool forceUpdate)
{
    Q_D(AmountEdit);
    if (d->sharesSet && !d->valueSet) {
        qWarning() << objectName() << "Call AmountEdit::setValue() before AmountEdit::setShares(). Fix source code!";
    }
    if (d->valueSet && (amount == d->m_value) && !forceUpdate)
        return;

    d->valueSet = true;
    d->m_value = amount;
    d->updateWidgetText(AmountEdit::DisplayValue, amount);

    if (!hasMultipleCurrencies()) {
        setShares(amount);
    }
}

MyMoneyMoney AmountEdit::shares() const
{
    Q_D(const AmountEdit);
    MyMoneyMoney money(d->m_sharesText);
    return d->adjustToPrecision(AmountEdit::DisplayShares, money);
}

void AmountEdit::setShares(const MyMoneyMoney& amount)
{
    Q_D(AmountEdit);
    d->sharesSet = !d->valueSet;
    d->m_shares = amount;
    d->updateWidgetText(AmountEdit::DisplayShares, amount);

    if (!d->m_shares.isZero()) {
        d->m_initialExchangeRate = d->m_value / d->m_shares;
    }
}

void AmountEdit::clear()
{
    Q_D(AmountEdit);
    d->m_sharesText.clear();
    d->m_valueText.clear();
    QLineEdit::clear();
    d->m_previousText.clear();
}

void AmountEdit::theTextChanged(const QString & theText)
{
    Q_D(AmountEdit);

    const auto initiallyValid = isValid();
    QLocale locale;
    QString dec = locale.groupSeparator();
    QString l_text = theText;
    QString nsign, psign;
    nsign = locale.negativeSign();
    psign = locale.positiveSign();
    d->m_lastChangeWasRemove = false;

    auto i = 0;
    if (isEnabled()) {
        QValidator::State state =  validator()->validate(l_text, i);
        if (state == QValidator::Intermediate) {
            if (l_text.length() == 1) {
                if (l_text != dec && l_text != nsign && l_text != psign)
                    state = QValidator::Invalid;
            }
        }
        if (state == QValidator::Invalid)
            QLineEdit::setText(d->m_previousText);
        else {
            d->m_previousText = l_text;

            if (!l_text.isEmpty()) {
                // adjust value or shares depending on state
                // by using the initialExchangeRate
                if (d->m_state == AmountEdit::DisplayValue) {
                    d->m_lastChangeWasRemove = d->m_valueText.length() > l_text.length();
                    d->m_valueText = l_text;
                    MyMoneyMoney amount(l_text);
                    d->adjustToPrecision(AmountEdit::DisplayValue, amount);
                    amount /= d->m_initialExchangeRate;
                    d->m_sharesText = amount.formatMoney(QString(), d->precision(AmountEdit::DisplayShares), false);
                    d->m_lastChanged = ValueChanged;
                } else {
                    d->m_lastChangeWasRemove = d->m_sharesText.length() > l_text.length();
                    d->m_sharesText = l_text;
                    MyMoneyMoney amount(l_text);
                    d->adjustToPrecision(AmountEdit::DisplayShares, amount);
                    if (!d->m_isReadOnly) {
                        amount *= d->m_initialExchangeRate;
                        d->m_valueText = amount.formatMoney(QString(), d->precision(AmountEdit::DisplayValue), false);
                    }
                    d->m_lastChanged = SharesChanged;
                }
            }
            Q_EMIT validatedTextChanged(text());
        }
    }

    if (initiallyValid != isValid()) {
        Q_EMIT validityChanged(isValid());
    }
}

void AmountEdit::slotCalculatorOpen()
{
    Q_D(AmountEdit);
    d->calculatorOpen(nullptr);
}

void AmountEdit::slotCalculatorClose()
{
    Q_D(AmountEdit);
    if (d->m_calculator != nullptr) {
        d->m_calculatorFrame->hide();
    }
}

void AmountEdit::setText(const QString& txt)
{
    Q_D(AmountEdit);
    if (!d->hasMultipleCurrencies()) {
        QLineEdit::setText(txt);
    } else {
        switch (d->m_state) {
        case MultiCurrencyEdit::DisplayShares:
            d->m_sharesText = txt;
            break;
        case MultiCurrencyEdit::DisplayValue:
            d->m_valueText = txt;
            break;
        }
        d->updateWidgets();
    }
}

void AmountEdit::slotCalculatorResult()
{
    Q_D(AmountEdit);
    slotCalculatorClose();
    if (d->m_calculator != nullptr) {
        MyMoneyMoney amount(d->m_calculator->result());
        amount = d->adjustToPrecision(d->m_state, amount);
        setText(amount.formatMoney(QString(), d->precision(d->m_state), false));
        Q_EMIT amountChanged();
    }
}

void AmountEdit::setCalculatorButtonVisible(const bool show)
{
    Q_D(AmountEdit);
    if (d->m_calculatorButton->isVisible() != show) {
        d->m_calculatorButton->setVisible(show);
        d->m_actionIcons.setFlag(AmountEditPrivate::ShowCalculator, show);
        d->updateLineEditSize(d->m_currencyButton);
        Q_EMIT calculatorButtonVisibilityChanged(show);
    }
}

void AmountEdit::setAllowEmpty(bool allowed)
{
    Q_D(AmountEdit);
    if (d->m_allowEmpty != allowed) {
        d->m_allowEmpty = allowed;
        Q_EMIT allowEmptyChanged(allowed);
    }
}

bool AmountEdit::isEmptyAllowed() const
{
    Q_D(const AmountEdit);
    return d->m_allowEmpty;
}

bool AmountEdit::isCalculatorButtonVisible() const
{
    Q_D(const AmountEdit);
    return d->m_calculatorButton->isVisible();
}

void AmountEdit::ensureFractionalPart()
{
    Q_D(AmountEdit);
    if (isEnabled()) {
        QString s(text());
        d->ensureFractionalPart(s, d->m_state);
        // by setting the text only when it's different then the one that it is already there
        // we preserve the edit widget's state (like the selection for example) during a
        // call to ensureFractionalPart() that does not change anything
        if (s != text())
            QLineEdit::setText(s);
    }
}

void AmountEdit::setCurrencySymbol(const QString& symbol, const QString& name)
{
    Q_D(AmountEdit);
    d->setCurrencySymbol(symbol, name);
}

void AmountEdit::setDisplayState(AmountEdit::DisplayState state)
{
    Q_D(AmountEdit);
    if (state != d->m_state) {
        d->m_state = state;
        d->updateWidgets();
        Q_EMIT displayStateChanged(state);
    }
}

void AmountEdit::setShowShares(bool show)
{
    if (!show) {
        setShowValue();
        return;
    }
    setDisplayState(AmountEdit::DisplayShares);
}

void AmountEdit::setShowValue(bool show)
{
    if (!show) {
        setShowShares();
        return;
    }
    setDisplayState(AmountEdit::DisplayValue);
}

void AmountEdit::setCommodity(const MyMoneySecurity& commodity)
{
    Q_D(AmountEdit);
    d->m_sharesCommodity = commodity;
    d->m_valueCommodity = commodity;
    d->updateWidgets();
}

void AmountEdit::setSharesCommodity(const MyMoneySecurity& commodity)
{
    Q_D(AmountEdit);
    if (d->m_sharesCommodity.id() != commodity.id()) {
        d->m_sharesCommodity = commodity;
        d->updateWidgets();
    }
}

MyMoneySecurity AmountEdit::sharesCommodity() const
{
    Q_D(const AmountEdit);
    return d->m_sharesCommodity;
}

void AmountEdit::setValueCommodity(const MyMoneySecurity& commodity)
{
    Q_D(AmountEdit);
    if (d->m_valueCommodity.id() != commodity.id()) {
        d->m_valueCommodity = commodity;
        d->updateWidgets();
    }
}

MyMoneySecurity AmountEdit::valueCommodity() const
{
    Q_D(const AmountEdit);
    return d->m_valueCommodity;
}

void AmountEdit::setInitialExchangeRate(const MyMoneyMoney& price)
{
    Q_D(AmountEdit);
    d->m_initialExchangeRate = price;
}

MyMoneyMoney AmountEdit::initialExchangeRate() const
{
    Q_D(const AmountEdit);
    return d->m_initialExchangeRate;
}

QWidget* AmountEdit::widget()
{
    return this;
}

bool AmountEdit::hasMultipleCurrencies() const
{
    Q_D(const AmountEdit);
    return d->hasMultipleCurrencies();
}

void AmountEdit::setPrecisionOverridesFraction(bool override)
{
    Q_D(AmountEdit);
    d->m_precisionOverridesFraction = override;
}

AmountEdit::LastValueChanged AmountEdit::lastChangedByUser() const
{
    Q_D(const AmountEdit);
    return d->m_lastChanged;
}

void AmountEdit::setAllowModifyShares(bool allowModifyShares)
{
    Q_D(AmountEdit);
    d->m_allowModifyShares = allowModifyShares;
    if (d->m_isReadOnly && displayState() == DisplayState::DisplayShares) {
        d->setReadOnly(!allowModifyShares);
    }
}

void AmountEdit::setFont(const QFont& font)
{
    Q_D(AmountEdit);
    QLineEdit::setFont(font);
    d->updateLineEditSize(d->m_calculatorButton);
}

void AmountEdit::setEnabled(bool enable)
{
    Q_D(AmountEdit);
    d->m_isEnabled = enable;
    QLineEdit::setEnabled(enable);
}
