/*
    SPDX-FileCopyrightText: 2015 Thomas Baumgart <tbaumgart@kde.org>
    SPDX-License-Identifier: GPL-2.0-or-later
*/

#include "ledgerdelegate.h"

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

#include <QApplication>
#include <QScrollBar>
#include <QPainter>
#include <QDebug>
#include <QDate>

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

#include <KLocalizedString>
#include <KColorScheme>

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

#include "ledgerview.h"
#include "ledgermodel.h"
#include "newtransactioneditor.h"

static unsigned char attentionSign[] = {
    0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
    0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
    0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14,
    0x08, 0x06, 0x00, 0x00, 0x00, 0x8D, 0x89, 0x1D,
    0x0D, 0x00, 0x00, 0x00, 0x04, 0x73, 0x42, 0x49,
    0x54, 0x08, 0x08, 0x08, 0x08, 0x7C, 0x08, 0x64,
    0x88, 0x00, 0x00, 0x00, 0x19, 0x74, 0x45, 0x58,
    0x74, 0x53, 0x6F, 0x66, 0x74, 0x77, 0x61, 0x72,
    0x65, 0x00, 0x77, 0x77, 0x77, 0x2E, 0x69, 0x6E,
    0x6B, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2E, 0x6F,
    0x72, 0x67, 0x9B, 0xEE, 0x3C, 0x1A, 0x00, 0x00,
    0x02, 0x05, 0x49, 0x44, 0x41, 0x54, 0x38, 0x8D,
    0xAD, 0x95, 0xBF, 0x4B, 0x5B, 0x51, 0x14, 0xC7,
    0x3F, 0x2F, 0xBC, 0x97, 0x97, 0x97, 0x97, 0x77,
    0xF3, 0xF2, 0x1C, 0xA4, 0x54, 0x6B, 0x70, 0x10,
    0x44, 0x70, 0x2A, 0x91, 0x2E, 0x52, 0x02, 0x55,
    0x8A, 0xB5, 0xA3, 0xAB, 0x38, 0x08, 0x66, 0xCC,
    0xEE, 0xE0, 0xE2, 0x20, 0xB8, 0x38, 0xB8, 0xB8,
    0xF8, 0x1F, 0x38, 0x29, 0xA5, 0x29, 0x74, 0x90,
    0x0E, 0x0D, 0x0E, 0x22, 0x1D, 0x44, 0xA8, 0xD0,
    0xD4, 0xB4, 0x58, 0x4B, 0x09, 0xF9, 0xF1, 0x4A,
    0x3B, 0xD4, 0xD3, 0xE1, 0x55, 0xD3, 0x34, 0xAF,
    0x49, 0x6C, 0x3D, 0xF0, 0x85, 0x7B, 0xCF, 0xFD,
    0x9E, 0xEF, 0x3D, 0xE7, 0xFE, 0xD4, 0x44, 0x84,
    0xDB, 0xB4, 0x48, 0x2F, 0xA4, 0x94, 0xAB, 0xE5,
    0x52, 0xAE, 0x96, 0xEB, 0x49, 0x51, 0x44, 0x3A,
    0x02, 0x18, 0x88, 0xC7, 0xF1, 0xE3, 0x71, 0x7C,
    0x60, 0xA0, 0x1B, 0xBF, 0x6B, 0x86, 0x49, 0xC5,
    0x46, 0x3E, 0x47, 0x34, 0x9F, 0x23, 0x9A, 0x54,
    0x6C, 0xFC, 0x57, 0x86, 0x40, 0xC6, 0x4B, 0xE1,
    0x37, 0xCA, 0x48, 0xA3, 0x8C, 0x78, 0x29, 0x7C,
    0x20, 0xD3, 0x31, 0xA6, 0xD3, 0xA0, 0x52, 0x1C,
    0x6D, 0x6F, 0x72, 0xD9, 0x28, 0x23, 0xFE, 0x07,
    0x64, 0x7B, 0x93, 0x4B, 0xA5, 0x38, 0xFA, 0x27,
    0x41, 0x60, 0x6E, 0x74, 0x84, 0x7A, 0xE5, 0x1D,
    0x92, 0x54, 0x88, 0xE7, 0x22, 0xD5, 0x12, 0x32,
    0x3A, 0x42, 0x1D, 0x98, 0xBB, 0x91, 0x20, 0x60,
    0xDA, 0x36, 0x17, 0xFB, 0x7B, 0xC8, 0xC1, 0x4B,
    0x04, 0x02, 0xBC, 0x7E, 0x81, 0xEC, 0xEF, 0x21,
    0xB6, 0xCD, 0x05, 0x60, 0xF6, 0x2C, 0x68, 0x9A,
    0x2C, 0xCF, 0x4C, 0xE1, 0x4B, 0x05, 0x39, 0x3F,
    0x69, 0x0A, 0xBE, 0x7F, 0x83, 0x48, 0x05, 0x99,
    0x99, 0xC2, 0x37, 0x4D, 0x96, 0x7B, 0x12, 0x04,
    0xFA, 0x2D, 0x8B, 0xC6, 0xE9, 0x61, 0x10, 0x2C,
    0x15, 0xC4, 0x8A, 0x21, 0x86, 0x8E, 0xFC, 0xF8,
    0x12, 0xF4, 0x4F, 0x0F, 0x11, 0xCB, 0xA2, 0x01,
    0xF4, 0x77, 0x3D, 0x36, 0x4E, 0x82, 0xF5, 0xA5,
    0x05, 0x8C, 0xE1, 0x74, 0xD3, 0x37, 0x34, 0x18,
    0x20, 0xF2, 0x8B, 0x3D, 0x9C, 0x86, 0xA5, 0x05,
    0x0C, 0x27, 0xC1, 0x7A, 0xC7, 0x63, 0x03, 0x8C,
    0x2B, 0x07, 0xBF, 0x5A, 0x6A, 0x66, 0x27, 0x15,
    0x64, 0x3A, 0x8B, 0x3C, 0x7A, 0xD8, 0xEA, 0xAB,
    0x96, 0x10, 0xE5, 0xE0, 0x03, 0xE3, 0x7F, 0xCD,
    0x50, 0x39, 0x6C, 0xAD, 0xAD, 0x10, 0x53, 0xAA,
    0x75, 0xD2, 0xF4, 0xBD, 0x00, 0x2D, 0x5C, 0x05,
    0x6B, 0x2B, 0xC4, 0x94, 0xC3, 0xD6, 0xEF, 0xFE,
    0x6B, 0x41, 0x4D, 0xD3, 0x66, 0xFB, 0x3C, 0xC6,
    0x16, 0xE7, 0xDB, 0x97, 0x61, 0xE2, 0x3E, 0x3C,
    0xC8, 0xB4, 0x15, 0xC7, 0xE2, 0x3C, 0x91, 0x3E,
    0x8F, 0x31, 0x4D, 0xD3, 0x66, 0x5B, 0x4A, 0x06,
    0x8C, 0x84, 0xCD, 0x59, 0x61, 0xA7, 0xB5, 0xAC,
    0x2B, 0x9C, 0x1C, 0x04, 0x08, 0x1B, 0x2B, 0xEC,
    0x20, 0x09, 0x9B, 0x33, 0xC0, 0xB8, 0xDE, 0x65,
    0x43, 0x27, 0x9F, 0x9D, 0xA4, 0x1E, 0x16, 0xF0,
    0xF9, 0x6D, 0xB0, 0xC3, 0x86, 0x1E, 0xB4, 0xC3,
    0x38, 0xD9, 0x49, 0xEA, 0x86, 0x4E, 0xFE, 0xEA,
    0x29, 0xF4, 0x2C, 0x8B, 0xDA, 0x71, 0x31, 0x9C,
    0xFC, 0xF5, 0x23, 0x32, 0x34, 0x88, 0xDC, 0xBD,
    0x13, 0x5C, 0xBF, 0x30, 0xCE, 0x71, 0x11, 0xB1,
    0x2C, 0x6A, 0x80, 0xA7, 0xDB, 0x36, 0xAB, 0x4F,
    0xA6, 0x89, 0xBA, 0x49, 0x38, 0xFF, 0xD4, 0xBE,
    0x4E, 0x00, 0xAF, 0x9E, 0x81, 0x08, 0xD4, 0xEA,
    0x01, 0xFE, 0x34, 0x37, 0x09, 0x4F, 0x1F, 0x13,
    0xDD, 0x7D, 0xCE, 0xAA, 0x96, 0x72, 0x29, 0x7C,
    0xFB, 0xCE, 0x44, 0xB8, 0xD4, 0xCD, 0x2C, 0x66,
    0x52, 0xD4, 0x6E, 0xFB, 0x0B, 0xF8, 0x09, 0x63,
    0x63, 0x31, 0xE4, 0x85, 0x76, 0x2E, 0x0E, 0x00,
    0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
    0x42, 0x60, 0x82
};

QColor LedgerDelegate::m_erroneousColor = QColor(Qt::red);
QColor LedgerDelegate::m_importedColor = QColor(Qt::yellow);
QColor LedgerDelegate::m_separatorColor = QColor(0xff, 0xf2, 0x9b);


class LedgerSeparatorDate : public LedgerSeparator
{
public:
    LedgerSeparatorDate(eLedgerModel::Role role);
    virtual ~LedgerSeparatorDate() {}

    virtual bool rowHasSeparator(const QModelIndex& index) const override;
    virtual QString separatorText(const QModelIndex& index) const override;
    virtual void adjustBackgroundScheme(QPalette& palette, const QModelIndex& index) const override;

protected:
    QString getEntry(const QModelIndex& index, const QModelIndex& nextIndex) const;
    QMap<QDate, QString>      m_entries;
};

class LedgerSeparatorOnlineBalance : public LedgerSeparatorDate
{
public:
    LedgerSeparatorOnlineBalance(eLedgerModel::Role role);
    virtual ~LedgerSeparatorOnlineBalance() {}

    virtual bool rowHasSeparator(const QModelIndex& index) const override;
    virtual QString separatorText(const QModelIndex& index) const override;
    virtual void adjustBackgroundScheme(QPalette& palette, const QModelIndex& index) const override;

    void setSeparatorData(const QDate& date, const MyMoneyMoney& amount, int fraction);

private:
    QString m_balanceAmount;
};



QDate LedgerSeparator::firstFiscalDate;
bool  LedgerSeparator::showFiscalDate = true;
bool  LedgerSeparator::showFancyDate = true;


void LedgerSeparator::setFirstFiscalDate(int firstMonth, int firstDay)
{
    firstFiscalDate = QDate(QDate::currentDate().year(), firstMonth, firstDay);
    if (QDate::currentDate() < firstFiscalDate)
        firstFiscalDate = firstFiscalDate.addYears(-1);
}

QModelIndex LedgerSeparator::nextIndex(const QModelIndex& index) const
{
    const int nextRow = index.row() + 1;
    if (index.isValid() && (nextRow < index.model()->rowCount(QModelIndex()))) {
        const QAbstractItemModel* model = index.model();
        return model->index(nextRow, 0, QModelIndex());
    }
    return QModelIndex();
}

LedgerSeparatorDate::LedgerSeparatorDate(eLedgerModel::Role role)
    : LedgerSeparator(role)
{
    const QDate today = QDate::currentDate();
    const QDate thisMonth(today.year(), today.month(), 1);
    const QDate lastMonth = thisMonth.addMonths(-1);
    const QDate yesterday = today.addDays(-1);
    // a = QDate::dayOfWeek()         todays weekday (1 = Monday, 7 = Sunday)
    // b = QLocale().firstDayOfWeek() first day of week (1 = Monday, 7 = Sunday)
    int weekStartOfs = today.dayOfWeek() - QLocale().firstDayOfWeek();
    if (weekStartOfs < 0) {
        weekStartOfs = 7 + weekStartOfs;
    }
    const QDate thisWeek = today.addDays(-weekStartOfs);
    const QDate lastWeek = thisWeek.addDays(-7);
    const QDate thisYear(today.year(), 1, 1);

    m_entries[thisYear] = i18n("This year");
    m_entries[lastMonth] = i18n("Last month");
    m_entries[thisMonth] = i18n("This month");
    m_entries[lastWeek] = i18n("Last week");
    m_entries[thisWeek] = i18n("This week");
    m_entries[yesterday] = i18n("Yesterday");
    m_entries[today] = i18n("Today");
    m_entries[today.addDays(1)] = i18n("Future transactions");
    m_entries[thisWeek.addDays(7)] = i18n("Next week");
    m_entries[thisMonth.addMonths(1)] = i18n("Next month");
    if (showFiscalDate && firstFiscalDate.isValid()) {
        m_entries[firstFiscalDate] = i18n("Current fiscal year");
        m_entries[firstFiscalDate.addYears(-1)] = i18n("Previous fiscal year");
        m_entries[firstFiscalDate.addYears(1)] = i18n("Next fiscal year");
    }
}


QString LedgerSeparatorDate::getEntry(const QModelIndex& index, const QModelIndex& nextIndex) const
{
    Q_ASSERT(index.isValid());
    Q_ASSERT(nextIndex.isValid());
    Q_ASSERT(index.model() == nextIndex.model());

    const QAbstractItemModel* model = index.model();
    QString rc;
    if(!m_entries.isEmpty()) {
        if (model->data(index, (int)m_role).toDate() != model->data(nextIndex, (int)m_role).toDate()) {
            const QDate key = model->data(index, (int)m_role).toDate();
            const QDate endKey = model->data(nextIndex, (int)m_role).toDate();
            QMap<QDate, QString>::const_iterator it = m_entries.upperBound(key);
            while((it != m_entries.cend()) && (it.key() <= endKey)) {
                rc = *it;
                ++it;
            }
        }
    }
    return rc;
}

bool LedgerSeparatorDate::rowHasSeparator(const QModelIndex& index) const
{
    bool rc = false;
    if(!m_entries.isEmpty()) {
        QModelIndex nextIdx = nextIndex(index);
        if(nextIdx.isValid() ) {
            const QString id = nextIdx.model()->data(nextIdx, (int)eLedgerModel::Role::TransactionSplitId).toString();
            // For a new transaction the id is completely empty, for a split view the transaction
            // part is filled but the split id is empty and the string ends with a dash
            // and we never draw a separator in front of that row
            if(!id.isEmpty() && !id.endsWith('-')) {
                rc = !getEntry(index, nextIdx).isEmpty();
            }
        }
    }
    return rc;
}

QString LedgerSeparatorDate::separatorText(const QModelIndex& index) const
{
    QModelIndex nextIdx = nextIndex(index);
    if(nextIdx.isValid()) {
        return getEntry(index, nextIdx);
    }
    return QString();
}

void LedgerSeparatorDate::adjustBackgroundScheme(QPalette& palette, const QModelIndex& index) const
{
    Q_UNUSED(index);
    KColorScheme::adjustBackground(palette, KColorScheme::ActiveBackground, QPalette::Base, KColorScheme::Button, KSharedConfigPtr());
}



LedgerSeparatorOnlineBalance::LedgerSeparatorOnlineBalance(eLedgerModel::Role role)
    : LedgerSeparatorDate(role)
{
    // we don't need the standard values
    m_entries.clear();
}

void LedgerSeparatorOnlineBalance::setSeparatorData(const QDate& date, const MyMoneyMoney& amount, int fraction)
{
    m_entries.clear();
    if (date.isValid()) {
        m_balanceAmount = amount.formatMoney(fraction);
        m_entries[date] = i18n("Online statement balance: %1", m_balanceAmount);
    }
}

bool LedgerSeparatorOnlineBalance::rowHasSeparator(const QModelIndex& index) const
{
    bool rc = false;
    if(!m_entries.isEmpty()) {
        QModelIndex nextIdx = nextIndex(index);
        const QAbstractItemModel* model = index.model();
        const QDate date = model->data(index, (int)m_role).toDate();
        // only a real transaction can have an online balance separator
        if(model->data(index, (int) eLedgerModel::Role::ScheduleId).toString().isEmpty()) {
            // if this is not the last entry and not a schedule?
            if(nextIdx.isValid()) {
                // index points to the last entry of a date
                rc = (date != model->data(nextIdx, (int)m_role).toDate());
                if (!rc) {
                    // in case it's the same date, we need to check if this is the last real transaction
                    // and the next one is a scheduled transaction
                    if(!model->data(nextIdx, (int) eLedgerModel::Role::ScheduleId).toString().isEmpty() ) {
                        rc = (date <= m_entries.firstKey());
                    }
                } else {
                    // check if this the spot for the online balance data
                    rc &= ((date <= m_entries.firstKey())
                           && (model->data(nextIdx, (int)m_role).toDate() > m_entries.firstKey()));
                }
            } else {
                rc = (date <= m_entries.firstKey());
            }
        }
    }
    return rc;
}

QString LedgerSeparatorOnlineBalance::separatorText(const QModelIndex& index) const
{
    if(rowHasSeparator(index)) {
        return m_entries.first();
    }
    return QString();
}

void LedgerSeparatorOnlineBalance::adjustBackgroundScheme(QPalette& palette, const QModelIndex& index) const
{
    const QAbstractItemModel* model = index.model();
    QModelIndex amountIndex = model->index(index.row(), (int)eLedgerModel::Column::Balance);
    QString amount = model->data(amountIndex).toString();
    KColorScheme::BackgroundRole role = KColorScheme::PositiveBackground;

    if (!m_entries.isEmpty()) {
        if(amount != m_balanceAmount) {
            role = KColorScheme::NegativeBackground;
        }
    }
    KColorScheme::adjustBackground(palette, role, QPalette::Base, KColorScheme::Button, KSharedConfigPtr());
}




class LedgerDelegate::Private
{
public:
    Private()
        : m_editor(0)
        , m_view(0)
        , m_editorRow(-1)
        , m_separator(0)
        , m_onlineBalanceSeparator(0)
    {}

    ~Private()
    {
        delete m_separator;
    }

    inline bool displaySeparator(const QModelIndex& index) const
    {
        return m_separator && m_separator->rowHasSeparator(index);
    }

    inline bool displayOnlineBalanceSeparator(const QModelIndex& index) const
    {
        return m_onlineBalanceSeparator && m_onlineBalanceSeparator->rowHasSeparator(index);
    }

    NewTransactionEditor*         m_editor;
    LedgerView*                   m_view;
    int                           m_editorRow;
    LedgerSeparator*              m_separator;
    LedgerSeparatorOnlineBalance* m_onlineBalanceSeparator;
};


LedgerDelegate::LedgerDelegate(LedgerView* parent)
    : QStyledItemDelegate(parent)
    , d(new Private)
{
    d->m_view = parent;
}

LedgerDelegate::~LedgerDelegate()
{
    delete d;
}

void LedgerDelegate::setSortRole(eLedgerModel::Role role)
{
    delete d->m_separator;
    delete d->m_onlineBalanceSeparator;
    d->m_separator = 0;
    d->m_onlineBalanceSeparator = 0;

    switch(role) {
    case eLedgerModel::Role::PostDate:
        d->m_separator = new LedgerSeparatorDate(role);
        d->m_onlineBalanceSeparator = new LedgerSeparatorOnlineBalance(role);
        break;
    default:
        qDebug() << "LedgerDelegate::setSortRole role" << (int)role << "not implemented";
        break;
    }
}

void LedgerDelegate::setErroneousColor(const QColor& color)
{
    m_erroneousColor = color;
}

void LedgerDelegate::setOnlineBalance(const QDate& date, const MyMoneyMoney& amount, int fraction)
{
    if(d->m_onlineBalanceSeparator) {
        d->m_onlineBalanceSeparator->setSeparatorData(date, amount, fraction);
    }
}

QWidget* LedgerDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    Q_UNUSED(option);

    if(index.isValid()) {
        if(d->m_view->selectionModel()->selectedRows().count() > 1) {
            qDebug() << "Editing multiple transactions at once is not yet supported";

            /**
             * @todo replace the following three lines with the creation of a special
             * editor that can handle multiple transactions at once
             */
            d->m_editor = 0;
            LedgerDelegate* that = const_cast<LedgerDelegate*>(this);
            emit that->closeEditor(d->m_editor, NoHint);

        } else {
            d->m_editor = new NewTransactionEditor(parent, d->m_view->accountId());
        }

        if(d->m_editor) {
            d->m_editorRow = index.row();
            connect(d->m_editor, SIGNAL(done()), this, SLOT(endEdit()));
            emit sizeHintChanged(index);
        }

    } else {
        qFatal("LedgerDelegate::createEditor(): we should never end up here");
    }
    return d->m_editor;
}

int LedgerDelegate::editorRow() const
{
    return d->m_editorRow;
}

void LedgerDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    QStyleOptionViewItem opt = option;
    initStyleOption(&opt, index);

    // never change the background of the cell the mouse is hovering over
    opt.state &= ~QStyle::State_MouseOver;

    // show the focus only on the detail column
    opt.state &= ~QStyle::State_HasFocus;

    // if selected, always show as active, so that the
    // background does not change when the editor is shown
    if (opt.state & QStyle::State_Selected) {
        opt.state |= QStyle::State_Active;
    }

    painter->save();

    QAbstractItemView* view = qobject_cast< QAbstractItemView* >(parent());
    const bool editWidgetIsVisible = d->m_view && d->m_view->indexWidget(index);
    const bool rowHasSeparator = d->displaySeparator(index);
    const bool rowHasOnlineBalance = d->displayOnlineBalanceSeparator(index);

    // Background
    QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
    const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin);
    const int lineHeight = opt.fontMetrics.lineSpacing() + 2;

    if (rowHasSeparator) {
        // don't draw over the separator space
        opt.rect.setHeight(opt.rect.height() - lineHeight );
    }
    if (rowHasOnlineBalance) {
        // don't draw over the online balance space
        opt.rect.setHeight(opt.rect.height() - lineHeight );
    }

    style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);

    QPalette::ColorGroup cg;

    // Do not paint text if the edit widget is shown
    if (!editWidgetIsVisible) {
        if(view && (index.column() == (int)eLedgerModel::Column::Detail)) {
            if(view->currentIndex().row() == index.row()) {
                opt.state |= QStyle::State_HasFocus;
            }
        }
        const QRect textArea = QRect(opt.rect.x() + margin, opt.rect.y() + margin, opt.rect.width() - 2 * margin, opt.rect.height() - 2 * margin);
        const bool selected = opt.state & QStyle::State_Selected;

        QStringList lines;
        if(index.column() == (int)eLedgerModel::Column::Detail) {
            lines << index.model()->data(index, (int)eLedgerModel::Role::PayeeName).toString();
            if(selected) {
                lines << index.model()->data(index, (int)eLedgerModel::Role::CounterAccount).toString();
                lines << index.model()->data(index, (int)eLedgerModel::Role::SingleLineMemo).toString();

            } else {
                if(lines.at(0).isEmpty()) {
                    lines.clear();
                    lines << index.model()->data(index, (int)eLedgerModel::Role::SingleLineMemo).toString();
                }
                if(lines.at(0).isEmpty()) {
                    lines << index.model()->data(index, (int)eLedgerModel::Role::CounterAccount).toString();
                }
            }
            lines.removeAll(QString());
        }

        const bool erroneous = index.model()->data(index, (int)eLedgerModel::Role::Erroneous).toBool();

        // draw the text items
        if(!opt.text.isEmpty() || !lines.isEmpty()) {

            // check if it is a scheduled transaction and display it as inactive
            if(!index.model()->data(index, (int)eLedgerModel::Role::ScheduleId).toString().isEmpty()) {
                opt.state &= ~QStyle::State_Enabled;
            }

            cg = (opt.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled;

            if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) {
                cg = QPalette::Inactive;
            }
            if (selected) {
                // always use the normal palette since the background is also in normal
                painter->setPen(opt.palette.color(QPalette::ColorGroup(QPalette::Normal), QPalette::HighlightedText));

            } else if (erroneous) {
                painter->setPen(m_erroneousColor);

            } else {
                painter->setPen(opt.palette.color(cg, QPalette::Text));
            }

            if (opt.state & QStyle::State_Editing) {
                painter->setPen(opt.palette.color(cg, QPalette::Text));
                painter->drawRect(textArea.adjusted(0, 0, -1, -1));
            }

            // collect data for the various columns
            if(index.column() == (int)eLedgerModel::Column::Detail) {
                for(int i = 0; i < lines.count(); ++i) {
                    painter->drawText(textArea.adjusted(0, lineHeight * i, 0, 0), opt.displayAlignment, lines[i]);
                }

            } else {
                painter->drawText(textArea, opt.displayAlignment, opt.text);
            }
        }

        // draw the focus rect
        if(opt.state & QStyle::State_HasFocus) {
            QStyleOptionFocusRect o;
            o.QStyleOption::operator=(opt);
            o.rect = style->proxy()->subElementRect(QStyle::SE_ItemViewItemFocusRect, &opt, opt.widget);
            o.state |= QStyle::State_KeyboardFocusChange;
            o.state |= QStyle::State_Item;

            cg = (opt.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled;
            o.backgroundColor = opt.palette.color(cg, (opt.state & QStyle::State_Selected)
                                                  ? QPalette::Highlight : QPalette::Window);
            style->proxy()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter, opt.widget);
        }

        // draw the attention mark
        if((index.column() == (int)eLedgerModel::Column::Detail)
                && erroneous) {
            QPixmap attention;
            attention.loadFromData(attentionSign, sizeof(attentionSign), 0, 0);
            style->proxy()->drawItemPixmap(painter, option.rect, Qt::AlignRight | Qt::AlignTop, attention);
        }
    }

    // draw a separator if any
    if (rowHasOnlineBalance) {
        opt.rect.setY(opt.rect.y() + opt.rect.height());
        opt.rect.setHeight(lineHeight);
        d->m_onlineBalanceSeparator->adjustBackgroundScheme(opt.palette, index);
        opt.backgroundBrush = opt.palette.base();

        // never draw it as selected but always enabled
        opt.state &= ~QStyle::State_Selected;
        style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);

        // when the editor is shown, the row has only a single column
        // so we need to paint the separator if we get here in this casee
        bool needPaint = editWidgetIsVisible;

        if(!needPaint && (index.column() == (int)eLedgerModel::Column::Detail)) {
            needPaint = true;
            // adjust the rect to cover all columns
            if(view && view->viewport()) {
                opt.rect.setX(0);
                opt.rect.setWidth(view->viewport()->width());
            }
        }

        if(needPaint) {
            painter->setPen(opt.palette.color(QPalette::Normal, QPalette::Text));
            painter->drawText(opt.rect, Qt::AlignCenter, d->m_onlineBalanceSeparator->separatorText(index));
        }
    }

    if (rowHasSeparator) {
        opt.rect.setY(opt.rect.y() + opt.rect.height());
        opt.rect.setHeight(lineHeight);
        d->m_separator->adjustBackgroundScheme(opt.palette, index);
        opt.backgroundBrush = opt.palette.base();

        // never draw it as selected but always enabled
        opt.state &= ~QStyle::State_Selected;
        style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);

        // when the editor is shown, the row has only a single column
        // so we need to paint the separator if we get here in this casee
        bool needPaint = editWidgetIsVisible;

        if(!needPaint && (index.column() == (int)eLedgerModel::Column::Detail)) {
            needPaint = true;
            // adjust the rect to cover all columns
            if(view && view->viewport()) {
                opt.rect.setX(0);
                opt.rect.setWidth(view->viewport()->width());
            }
        }

        if(needPaint) {
            painter->setPen(opt.palette.foreground().color());
            painter->drawText(opt.rect, Qt::AlignCenter, d->m_separator->separatorText(index));
        }
    }
    painter->restore();
}

QSize LedgerDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    bool fullDisplay = false;
    if(d->m_view) {
        QModelIndex currentIndex = d->m_view->currentIndex();
        if(currentIndex.isValid()) {
            QString currentId = currentIndex.model()->data(currentIndex, (int)eLedgerModel::Role::TransactionSplitId).toString();
            QString myId = index.model()->data(index, (int)eLedgerModel::Role::TransactionSplitId).toString();
            fullDisplay = (currentId == myId);
        }
    }

    QSize size;
    QStyleOptionViewItem opt = option;
    int rows = 1;
    initStyleOption(&opt, index);

    QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
    const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin);
    const int lineHeight = opt.fontMetrics.lineSpacing();

    if(index.isValid()) {
        // check if we are showing the edit widget
        // const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(opt.widget);
        if (d->m_view) {
            QModelIndex editIndex = d->m_view->model()->index(index.row(), 0);
            if(editIndex.isValid()) {
                QWidget* editor = d->m_view->indexWidget(editIndex);
                if(editor) {
                    size = editor->minimumSizeHint();
                    if(d->displaySeparator(index)) {
                        // don't draw over the separator space
                        size += QSize(0, lineHeight + margin);
                    }
                    if(d->displayOnlineBalanceSeparator(index)) {
                        // don't draw over the separator space
                        size += QSize(0, lineHeight + margin);
                    }
                    return size;
                }
            }
        }
    }

    size = QSize(100, lineHeight + 2*margin);

    if(fullDisplay) {
        auto payee = index.data((int)eLedgerModel::Role::PayeeName).toString();
        auto counterAccount = index.data((int)eLedgerModel::Role::CounterAccount).toString();
        auto memo = index.data((int)eLedgerModel::Role::SingleLineMemo).toString();

        rows = (payee.length() > 0 ? 1 : 0) + (counterAccount.length() > 0 ? 1 : 0) + (memo.length() > 0 ? 1 : 0);
        // make sure we show at least one row
        if(!rows) {
            rows = 1;
        }
        // leave a few pixels as margin for each space between rows
        size.setHeight((size.height() * rows) - (margin * (rows - 1)));

    }

    if (d->m_separator && d->m_separator->rowHasSeparator(index)) {
        size.setHeight(size.height() + lineHeight + margin);
    }
    if (d->m_onlineBalanceSeparator && d->m_onlineBalanceSeparator->rowHasSeparator(index)) {
        size.setHeight(size.height() + lineHeight + margin);
    }
    return size;
}

void LedgerDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    Q_UNUSED(index);

    QStyle *style = option.widget ? option.widget->style() : QApplication::style();
    const int margin = style->pixelMetric(QStyle::PM_FocusFrameHMargin);
    const int lineHeight = option.fontMetrics.lineSpacing();

    int ofs = 8;
    if(d->m_view) {
        if(d->m_view->verticalScrollBar()->isVisible()) {
            ofs += d->m_view->verticalScrollBar()->width();
        }
    }

    QRect r(option.rect);
    if (option.widget)
        r.setWidth(option.widget->width() - ofs);

    if(d->displaySeparator(index)) {
        // consider the separator space
        r.setHeight(r.height() - lineHeight - margin);
    }
    if(d->displayOnlineBalanceSeparator(index)) {
        // consider the separator space
        r.setHeight(r.height() - lineHeight - margin);
    }
    editor->setGeometry(r);
    editor->update();
}

void LedgerDelegate::endEdit()
{
    if(d->m_editor) {
        if(d->m_editor->accepted()) {
            emit commitData(d->m_editor);
        }
        emit closeEditor(d->m_editor, NoHint);
        d->m_editorRow = -1;
    }
}

/**
 * This eventfilter seems to do nothing but it prevents that selecting a
 * different row with the mouse closes the editor
 */
bool LedgerDelegate::eventFilter(QObject* o, QEvent* event)
{
    return QAbstractItemDelegate::eventFilter(o, event);
}

void LedgerDelegate::setEditorData(QWidget* editWidget, const QModelIndex& index) const
{
    NewTransactionEditor* editor = qobject_cast<NewTransactionEditor*>(editWidget);
    if(editor) {
        editor->loadTransaction(index.model()->data(index, (int)eLedgerModel::Role::TransactionSplitId).toString());
    }
}

void LedgerDelegate::setModelData(QWidget* editWidget, QAbstractItemModel* model, const QModelIndex& index) const
{
    Q_UNUSED(model)
    Q_UNUSED(index)

    NewTransactionEditor* editor = qobject_cast<NewTransactionEditor*>(editWidget);
    if(editor) {
        editor->saveTransaction();
    }
}
