/*
 * Copyright 2009- ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 * In applying this licence, ECMWF does not waive the privileges and immunities
 * granted to it by virtue of its status as an intergovernmental organisation
 * nor does it submit to any jurisdiction.
 */

#include "CompactView.hpp"

#include <QApplication>
#include <QDebug>
#include <QMouseEvent>
#include <QPainter>
#include <QScrollBar>
#include <QStyledItemDelegate>
#include <QTimerEvent>
#include <QtAlgorithms>
#include <QtGlobal>

#include "ExpandState.hpp"
#include "TreeNodeModel.hpp"
#include "TreeNodeViewDelegate.hpp"
#include "UIDebug.hpp"
#include "UiLog.hpp"

// #define _UI_COMPACTVIEW_DEBUG

CompactView::CompactView(TreeNodeModel* model, QWidget* parent) : AbstractNodeView(model, parent) {
    // This is needed for making the context menu work
    setProperty("view", "tree");

    // we cannot call it from the constructor of the base class
    // because it calls a pure virtual method
    reset();
}

CompactView::~CompactView() = default;

// Creates and initialize the viewItem structure of the children of the element
//  parentId: the items whose children are to be expanded
//  recursiveExpanding: all the children will be expanded
//  afterIsUninitialized: when we recurse from layout(-1) it indicates
//  the items after 'i' are not yet initialized and need not to be moved

void CompactView::layout(int parentId, bool recursiveExpanding, bool afterIsUninitialized, bool preAllocated) {
    // This is the root item.
    if (parentId == -1) {
        rowCount_    = 0;
        maxRowWidth_ = 0;
    }

    QModelIndex parentIndex = (parentId < 0) ? root_ : modelIndex(parentId);

    if (parentId >= 0 && !parentIndex.isValid()) {
        // modelIndex() should never return something invalid for the real items.
        // This can happen if columncount has been set to 0.
        // To avoid infinite loop we stop here.
        return;
    }

    int count      = model_->rowCount(parentIndex);
    bool expanding = true;

    // This is the root item. viewItems must be empty at this point.
    if (parentId == -1) {
        Q_ASSERT(viewItems_.empty());
        Q_ASSERT(preAllocated == false);
        viewItems_.resize(count);
        afterIsUninitialized = true; // It can only be true when we expand from the root!
    }
    // The count of the stored children does not match the actual count
    else if (viewItems_[parentId].total != (uint)count) {
        // Expand
        if (!afterIsUninitialized) {
            // We called expandall for a non-root item. All the new items need must be
            // already instered at this point. This is the duty of the caller routine.
            // const int itemsCount = viewItems_.size();
            // if(recursiveExpanding)
            if (preAllocated) {
                // We called expandAll() for a non-root item. All the needed items need must already be
                // inserted at this point. This is the duty of the caller routine!
                // When layout() is finished we need to adjust the parent of all the items
                // after the insertion position. This is the duty of the caller routine. We
                // have chosen this solution for performance reasons!
            }
            else {
                insertViewItems(parentId + 1, count, TreeNodeViewItem());
            }
        }
        // ExpandAll from the root
        else if (count > 0)
            viewItems_.resize(viewItems_.size() + count);
    }
    else {
        expanding = false;
    }

    int first              = parentId + 1;
    int last               = 0;
    int children           = 0;
    int level              = (parentId >= 0 ? viewItems_[parentId].level + 1 : 0);
    TreeNodeViewItem* item = nullptr;

    std::vector<int> itemWidthVec;
    std::vector<int> itemHeightVec;
    int widest = 0;
    for (int i = first; i < first + count; i++) {
        int w = 0, h = 0;
        QModelIndex currentIndex = model_->index(i - first, 0, parentIndex);
        delegate_->sizeHintCompute(currentIndex, w, h);
        itemWidthVec.push_back(w);
        itemHeightVec.push_back(h);

        if (parentId >= 0 && !model_->isAttribute(currentIndex))
            if (w > widest)
                widest = w;
#ifdef _UI_COMPACTVIEW_DEBUG
        UiLog().dbg() << "  item=" << currentIndex.data().toString() << " w=" << w;
#endif
    }

#ifdef _UI_COMPACTVIEW_DEBUG
    if (parentId >= 0)
        UiLog().dbg() << "layout parent=" << viewItems_[parentId].index.data().toString() << " widest child=" << widest;
#endif

    // Iterate through the direct children of parent item. At this point all the items
    // needed in the loop below are pre-allocated but not yet initialised.
    for (int i = first; i < first + count; i++) {
        QModelIndex currentIndex = model_->index(i - first, 0, parentIndex);

        last                   = i + children;
        item                   = &viewItems_[last];
        item->parentItem       = parentId;
        item->index            = currentIndex;
        item->hasMoreSiblings  = (i < first + count - 1);
        item->level            = level;
        item->expanded         = false;
        item->total            = 0;
        item->widestInSiblings = widest;

        // We compute the size of the item. For attributes we delay the width computation until we
        // actually paint them and we set their width to 300.
        item->width  = itemWidthVec[i - first];
        item->height = itemHeightVec[i - first];

        int xp = leftMargin_;
        if (parentId >= 0) {
            item->widestInSiblings = widest;
            xp                     = viewItems_[parentId].alignedRight() + itemGap_;
        }
        else {
            item->widestInSiblings = item->width;
        }

        item->x = xp;

        if (item->alignedRight() > maxRowWidth_)
            maxRowWidth_ = item->alignedRight();

        // We need to expand the item
        if (recursiveExpanding || isIndexExpanded(currentIndex)) {
            if (recursiveExpanding)
                expandedIndexes.insert(currentIndex);

            item->expanded = true;

#ifdef _UI_COMPACTVIEW_DEBUG
            UiLog().dbg() << "  before " << item->index.data().toString() << " total=" << item->total;
#endif
            // Add the children to the layout
            layout(last, recursiveExpanding, afterIsUninitialized, preAllocated);

            item = &viewItems_[last];

#ifdef _UI_COMPACTVIEW_DEBUG
            UiLog().dbg() << "  after " << item->index.data().toString() << " total=" << item->total;
#endif
            children += item->total;
            item->hasChildren = item->total > 0;
        }
        else {
            item->hasChildren = model_->hasChildren(currentIndex);
        }
    }

    if (!expanding)
        return; // nothing changed

#ifdef _UI_COMPACTVIEW_DEBUG
    UiLog().dbg() << " update parent total";
#endif

    int pp = parentId;
    while (pp > -1) {
        viewItems_[pp].total += count;

#ifdef _UI_COMPACTVIEW_DEBUG
        UiLog().dbg() << "  parent=" << viewItems_[pp].index.data().toString() << "  total=" << viewItems_[pp].total;
#endif

        pp = viewItems_[pp].parentItem;
    }
}

// Paint the rows intersecting with the given region
void CompactView::paint(QPainter* painter, const QRegion& region) {
    // Even though the viewport palette is set correctly at the
    // beginning something sets it to another value. Here we try
    // to detect it and correct the palette with the right colour.
    if (expectedBg_.isValid()) {
        QPalette p = viewport()->palette();
        if (p.color(QPalette::Window) != expectedBg_) {
            p.setColor(QPalette::Window, expectedBg_);
            viewport()->setPalette(p);
            viewport()->update();
            expectedBg_ = QColor();
            return;
        }
    }

#ifdef _UI_COMPACTVIEW_DEBUG
    UiLog().dbg() << "CompactView::paint -->";
    // UiLog().dbg() << "sizeof(TreeNodeViewItem)=" << sizeof(TreeNodeViewItem);
    // UiLog().dbg() << "region=" << region;
#endif

    int firstVisibleOffset = 0;

    // The first visible item at the top of the viewport
    int firstVisible = firstVisibleItem(firstVisibleOffset);
#ifdef _UI_COMPACTVIEW_DEBUG
    UiLog().dbg() << "firstVisible " << firstVisible;
#endif

    if (firstVisible < 0)
        return;

#ifdef _UI_COMPACTVIEW_DEBUG
    UiLog().dbg() << "scrollX" << horizontalScrollBar()->value() << " " << viewport()->width();
#endif

    int xOffset = 0;
    if (horizontalScrollBar()->value() > 0) {
        xOffset = horizontalScrollBar()->value();
        painter->translate(-xOffset, 0);
    }

    const int itemsCount    = viewItems_.size();
    const int viewportWidth = viewport()->width();

#if QT_VERSION < QT_VERSION_CHECK(5, 8, 0)
    QVector<QRect> rects = region.rects();
#else
    QVector<QRect> rects;
    for (auto rect : region) {
        rects << rect;
    }
#endif
    QVector<int> drawn;
    bool multipleRects = (rects.size() > 1);

    // Iterate through the rectangles in the region
    for (auto rect : rects) {
        const QRect area = (multipleRects ? QRect(0, rect.y(), viewportWidth, rect.height()) : rect);
#ifdef _UI_COMPACTVIEW_DEBUG
        UiLog().dbg() << " area=" << area;
#endif

        // Initialise indentVec. For each indentation level it tells us if
        // a connector line is to be drawn. Here we scan up to the
        // toplevel item in the firstVisible item's branch.
        std::vector<int> indentVec(1000, 0);
        if (firstVisible > 0) {
            TreeNodeViewItem* item = &viewItems_[firstVisible];
            int level              = item->level;
            while (item->parentItem >= 0 && level > 0) {
                TreeNodeViewItem* pt = &viewItems_[item->parentItem];
                if (item->hasMoreSiblings) {
                    indentVec[item->level] = connectorPos(item, pt);
                }
                UI_ASSERT(pt->level == level - 1,
                          "item->parentItem=" << item->parentItem << " pt->level=" << pt->level << " level=" << level);
                item = pt;
                level--;
            }
        }

        int i = firstVisible;       // the first item at the top of the viewport
        int y = firstVisibleOffset; // we may only see part of the first item

        // start at the top of the viewport  and iterate down through the update area
        int itemsInRow = 1;
        for (; i < itemsCount; i += itemsInRow) {
            int itemHeight = 0;
            rowProperties(i, itemHeight, itemsInRow, indentVec);

#ifdef _UI_COMPACTVIEW_DEBUG
            UiLog().dbg() << "row: " << i << " " << itemHeight << " " << itemsInRow;
#endif
            // Try to find the first item int the current rect
            if (y + itemHeight > area.top())
                break;
            y += itemHeight;
        }

#ifdef _UI_COMPACTVIEW_DEBUG
        UiLog().dbg() << "y: " << y << " " << area.bottom();
#endif

        // Paint the visible rows in the current rect
        for (; i < itemsCount && y <= area.bottom(); i += itemsInRow) {
            if (!multipleRects || !drawn.contains(i)) {
                // Draw a whole row. It will update y,itemsInRow and indentVec!!
                drawRow(painter, i, xOffset, y, itemsInRow, indentVec);

#ifdef _UI_COMPACTVIEW_DEBUG
                UiLog().dbg() << " row rendered - item=" << i << " y=" << y << " itemsInRow=" << itemsInRow;
#endif
            }
            else {
                int rh = rowHeight(i, 1, itemsInRow);
                y += rh;
#ifdef _UI_COMPACTVIEW_DEBUG
                UiLog().dbg() << " row skipped  - item=" << i << " y=" << y << " itemsInRow=" << itemsInRow;
#endif
            }

            if (multipleRects)
                drawn.append(i);
        }
    }
}

// Draw a whole row starting at item "start".
void CompactView::drawRow(QPainter* painter,
                          int start,
                          int /*xOffset*/,
                          int& yp,
                          int& itemsInRow,
                          std::vector<int>& indentVec) {
    itemsInRow            = 0;
    bool leaf             = false;
    const auto itemsCount = static_cast<int>(viewItems_.size());

    // Get the rowheight
    int iir = 0;
    int rh  = rowHeight(start, 1, iir);

    // See if there are no multiline items in this row
    bool singleRow = delegate_->isSingleHeight(rh);

    int firstLevel          = 0;
    const int viewportWidth = viewport()->width();

    // We iterate through the items in the row
    for (int i = start; i < itemsCount && !leaf; ++i) {
        TreeNodeViewItem* item = &(viewItems_[i]);
#ifdef _UI_COMPACTVIEW_DEBUG
        UiLog().dbg() << "  item=" << i << " " << item->index.data().toString();
#endif
        leaf = (item->total == 0);

        // Find out the first indentation level in the row
        if (firstLevel == 0)
            firstLevel = item->level;

        // Init style option
        QStyleOptionViewItem opt;
        if (selectionModel_->isSelected(item->index))
            opt.state |= QStyle::State_Selected;

        int optWidth = 2000;
        if (item->width > optWidth)
            optWidth = item->width;
        opt.rect = QRect(item->x, yp, optWidth, item->height);

        // We do not render the item if it is outisde the viewport and
        // its parent's right is also outside the viewport. Here we considered that
        // the connector line is always drawn from the child to the parent.
        bool needToDraw = true;
        if (item->parentItem >= 0) {
            if (viewItems_[item->parentItem].right() >= translation() + viewportWidth)
                needToDraw = false;
        }

        if (needToDraw) {
            // For single rows we center items halfway through the rowHeight
            if (singleRow) {
                if (item->height < rh) {
                    opt.rect.moveTop(yp + (rh - item->height) / 2);
                }
            }

            // QRect vr=visualRect(item->index);
            // painter->fillRect(vr,QColor(120,120,120,120));

            // #ifdef _UI_COMPACTVIEW_DEBUG
            //           UiLog().dbg() << "  optRect=" << opt.rect << " visRect=" << vr;
            // #endif

            // Draw the item with the delegate
            QSize paintedSize;
            delegate_->paintIt(painter, opt, item->index, paintedSize);

            // we have to know if the item width/height is the same that we expected.
            // This can happen when:
            //  -we set a fixed initial width for the item (e.g. for an attribute)
            //   and now we got the real width
            //  -the number of icons or additional extra information
            //   changed for a node (so the width changed)
            //  -the number of lines changed in a multiline label (so the height changed)
            bool wChanged   = paintedSize.width() != item->width;
            bool hChanged   = paintedSize.height() != item->height;
            bool wIncreased = paintedSize.width() > item->width;

            if (wChanged || hChanged) {
                // set new size
                item->width  = paintedSize.width();
                item->height = paintedSize.height();

                if (item->right() > maxRowWidth_) {
                    maxRowWidth_ = item->right();
                    doDelayedWidthAdjustment();
                }
                else if (hChanged) {
                    doDelayedWidthAdjustment();
                }
            }

            // The width changed
            if (wChanged) {
                bool sameAsWidest = (item->width == item->widestInSiblings);
                item->width       = paintedSize.width();

                // servers
                if (item->parentItem == -1) {
                    adjustWidthInParent(i);
                    doDelayedWidthAdjustment();
                }
                // Nodes
                else if (model_->isNode(item->index)) {
                    // widestInSiblings has to be adjusted
                    if (sameAsWidest || paintedSize.width() > static_cast<int>(item->widestInSiblings)) {
                        adjustWidthInParent(i);
                        doDelayedWidthAdjustment();
                    }
                    // we just need to update the item
                    else if (paintedSize.width() < static_cast<int>(item->widestInSiblings)) {
                        doDelayedWidthAdjustment();
                    }
                }
                // Attributes
                else {
                    if (item->right() > maxRowWidth_) {
                        maxRowWidth_ = item->right();
                        doDelayedWidthAdjustment();
                    }
                    else if (wIncreased && !hChanged) {
                        // we need to repaint the attribute
                        doDelayedWidthAdjustment();
                    }
                }
            }
            // the height changed (can only be a multiline label)
            if (hChanged) {
                // set new size
                item->height = paintedSize.height();
                doDelayedWidthAdjustment();
            }

            // QRect rr=opt.rect;
            // rr.setWidth(item->width);
            // painter->drawRect(rr);

            // UiLog().dbg() << i << " " << viewItems_[i]->index << " " << viewItems_[i]->index.data().toString() << " "
            //               << viewItems_[i]->x << " " << viewItems_[i]->height << " " << leaf;

            painter->setPen(connectorColour_);

            // If not a top level item (i.e. not a server)
            if (item->parentItem >= 0) {
                // The parent item. It is always a node.
                TreeNodeViewItem* pt = &(viewItems_[item->parentItem]);

                // The horizontal line connecting the item to its parent
                int lineX1 = pt->right() + connectorGap_;
                int lineX2 = item->x - connectorGap_;
                int lineX  = (pt->right() + item->x) / 2;

#ifdef _UI_COMPACTVIEW_DEBUG
                UiLog().dbg() << "  lineX=" << lineX << " " << item->x << " " << connectorPos(item, pt);
#endif
                UI_ASSERT(lineX == connectorPos(item, pt),
                          "lineX=" << lineX << " i=" << i << " item->x=" << item->x
                                   << " connectorPos=" << connectorPos(item, pt));

                // First child - in the same row as its parent
                if (item->index.row() == 0) {
                    int lineY = yp + pt->height / 2;

                    // horizontal line to the parent
                    painter->drawLine(lineX1, lineY, lineX2, lineY);

                    // line towards the siblings  - downwards
                    if (item->hasMoreSiblings) {
                        // painter->drawLine(lineX,lineY,lineX,lineY+rh/2);
                        painter->drawLine(lineX, lineY, lineX, yp + rh);
                        indentVec[item->level] = lineX;
                    }
                    else
                        indentVec[item->level] = 0;
                }
                // Child in the middle - has sibling both upwards and downwards
                else if (item->hasMoreSiblings) {
                    int lineY = yp + item->height / 2;

                    painter->drawLine(lineX, lineY, lineX2, lineY);
                    // painter->drawLine(lineX,lineY+rh/2,lineX,lineY-rh/2);
                    painter->drawLine(lineX, yp, lineX, yp + rh);
                    indentVec[item->level] = lineX;
                }

                // The last child - has sibling only upwards
                else {
                    int lineY = yp + item->height / 2;
                    painter->drawLine(lineX, lineY, lineX2, lineY);
                    // painter->drawLine(lineX,lineY,lineX,lineY-rh/2);
                    painter->drawLine(lineX, lineY, lineX, yp);
                    indentVec[item->level] = 0;
                }
            }

            // indicate if a node is exandable
            if (item->hasChildren && !item->expanded) {
                int lineY   = yp + item->height / 2;
                int lineX   = item->right() + connectorGap_;
                QPen oriPen = painter->pen();
                painter->setPen(QPen(connectorColour_, 1, Qt::DashLine));
                painter->drawLine(lineX, lineY, lineX + expandConnectorLength_, lineY);
                painter->setPen(oriPen);
            }
        }

        // When we reach a leaf item we move one row down.
        if (leaf) {
            // Draw the vertical connector lines for all the levels
            // preceding the first level in the row!
            painter->setPen(connectorColour_);
            for (int j = 0; j < firstLevel; j++) {
                int xp = indentVec[j];
                if (xp != 0)
                    painter->drawLine(xp, yp, xp, yp + rh);
            }

            yp += rh;
            rh         = 0;
            firstLevel = 0;
        }
        itemsInRow++;
    }

    if (itemsInRow == 0)
        itemsInRow = 1;
}

void CompactView::adjustWidthInParent(int start) {
    // The parent index of the start item
    int parentItem = viewItems_[start].parentItem;

    // The current max width in the start item's siblings
    int prevWidest = viewItems_[start].widestInSiblings;

    // If the parent is not the root ie the start item is not a server
    if (parentItem >= 0) {
        int w = 0, h = 0, widest = 0;
        QModelIndex parentIndex = viewItems_[parentItem].index;

        // Determine the max width in the siblings of the start
        // item, ie in the children of the parent item
        int rowCount = model_->rowCount(parentIndex);
        for (int i = 0; i < rowCount; i++) {
            QModelIndex idx = model_->index(i, 0, parentIndex);
            if (model_->isNode(idx)) {
                delegate_->sizeHintCompute(idx, w, h);
                if (w > widest)
                    widest = w;
            }
        }

        // If there is a new max width we need to adjust all the children of
        // the parent item
        int delta = widest - prevWidest;
        if (delta != 0) {
            int n = parentItem + viewItems_[parentItem].total;
            for (int i = parentItem + 1; i <= n; i++) {
                // For a direct child of the parent item we just
                // set the max width to its new value
                if (viewItems_[i].parentItem == parentItem) {
                    viewItems_[i].widestInSiblings = widest;
                }
                // The other items are shifted
                else {
                    viewItems_[i].x += delta;
                }

                // Check if the total width changed
                if (viewItems_[i].right() > maxRowWidth_)
                    maxRowWidth_ = viewItems_[i].right();
            }
        }
    }

    // If the parent is the root ie the start item is a server
    else {
        // Determine the diff between the current and the previous width
        int delta = viewItems_[start].width - prevWidest;

        // for server widestInSiblings is set to the width
        viewItems_[start].widestInSiblings = viewItems_[start].width;

        // Shift all the children with the diff
        if (delta != 0) {
            int n = start + viewItems_[start].total;
            for (int i = start + 1; i <= n; i++) {
                // shifted
                viewItems_[i].x += delta;

                // Check if the total width changed
                if (viewItems_[i].right() > maxRowWidth_)
                    maxRowWidth_ = viewItems_[i].right();
            }
        }
    }
}

int CompactView::connectorPos(TreeNodeViewItem* item, TreeNodeViewItem* parent) const {
    return (parent->right() + item->x) / 2;
}

// Get the rowheight. There are three kinds of row heights.
//  1. nodes (fixed height)
//  2. attributes (fixed height)
//  3. multiline label attributes (variable height!!!)
void CompactView::rowProperties(int start, int& rowHeight, int& itemsInRow, std::vector<int>& indentVec) const {
    rowHeight             = 0;
    itemsInRow            = 0;
    const auto itemsCount = static_cast<int>(viewItems_.size());

    for (int i = start; i < itemsCount; i++) {
        TreeNodeViewItem* item = &(viewItems_[i]);
        rowHeight              = qMax(rowHeight, static_cast<int>(item->height));
        itemsInRow++;
        if (item->total == 0) {
            indentVec[item->level] = 0;
            break;
        }

        if (item->parentItem >= 0) {
            // The parent item. It is always a node.
            TreeNodeViewItem* pt = &(viewItems_[item->parentItem]);

            if (item->hasMoreSiblings) {
                int lineX1             = pt->right() + 2;
                int lineX2             = item->x - 2;
                int lineX              = (lineX1 + lineX2) / 2;
                indentVec[item->level] = lineX;
            }
            else {
                indentVec[item->level] = 0;
            }
        }
    }

    UI_ASSERT(itemsInRow > 0, "itemsInRow=" << itemsInRow);
}

int CompactView::rowHeight(int start, int forward, int& itemsInRow) const {
    uint rh               = 0;
    itemsInRow            = 0;
    const auto itemsCount = static_cast<int>(viewItems_.size());

    if (forward == 1) {
        for (int i = start; i < itemsCount; i++) {
            rh = qMax(rh, viewItems_[i].height);
            itemsInRow++;
            if (viewItems_[i].total == 0)
                break;
        }
    }
    else {
        UI_ASSERT(start >= 0, "start=" << start << " total=" << viewItems_.size());
        UI_ASSERT(start < static_cast<int>(viewItems_.size()), "start=" << start << " total=" << viewItems_.size());
        rh = qMax(rh, viewItems_[start].height);
        itemsInRow++;
        for (int i = start - 1; i >= 0; i--) {
            if (viewItems_[i].total == 0)
                break;
            rh = qMax(rh, viewItems_[i].height);
            itemsInRow++;
        }
    }

    UI_ASSERT(itemsInRow > 0, "itemsInRow=" << itemsInRow);
    return rh;
}

int CompactView::itemCountInRow(int start) const {
    const std::size_t itemsCount = viewItems_.size();
    int itemsInRow               = 0;
    for (std::size_t i = start; i < itemsCount; i++) {
        itemsInRow++;
        if (viewItems_[i].total == 0)
            return itemsInRow;
    }

    UI_ASSERT(itemsInRow > 0, "itemsInRow=" << itemsInRow);
    return itemsInRow;
}

int CompactView::itemRow(int item) const {
    if (item < 0 || item >= static_cast<int>(viewItems_.size()))
        return -1;

    int row        = -1;
    int itemsInRow = 0;
    for (int i = 0; i <= item; i += itemsInRow) {
        row++;
        itemsInRow = itemCountInRow(i);
    }

    return row;
}

int CompactView::firstVisibleItem(int& offset) const {
    const int value = verticalScrollBar()->value();
#ifdef _UI_COMPACTVIEW_DEBUG
    UiLog().dbg() << "CompactNodeView::firstVisibleItem --> value=" << value;
#endif

    if (verticalScrollMode_ == ScrollPerItem) {
        offset = 0;
        // value is the row number

        if (value < 0 || value >= rowCount_)
            return -1;

        int cnt                      = 0;
        int itemsInRow               = 0;
        const std::size_t itemsCount = viewItems_.size();
        for (std::size_t i = 0; i < itemsCount; i += itemsInRow) {
            if (cnt == value) {
#ifdef _UI_COMPACTVIEW_DEBUG
                UiLog().dbg() << " i=" << i << " itemsInRow=" << itemsInRow;
#endif
                return i;
            }
            itemsInRow = itemCountInRow(i);
            cnt++;
        }
        // return (value < 0 || value >= viewItems_.count()) ? -1 : value;
    }

    return -1;
}

// This has to be very quick. Called after each collapse/expand.
void CompactView::updateRowCount() {
    rowCount_             = 0;
    const auto itemsCount = static_cast<int>(viewItems_.size());
    for (int i = 0; i < itemsCount; i++) {
        if (viewItems_[i].total == 0)
            rowCount_++;
    }

#ifdef _UI_COMPACTVIEW_DEBUG
    UiLog().dbg() << "CompactNodeView::updateRowCount --> " << rowCount_;
#endif
}

void CompactView::updateScrollBars() {
#ifdef _UI_COMPACTVIEW_DEBUG
    UiLog().dbg() << "CompactNodeView::updateScrollBars -->";
#endif

    QSize viewportSize = viewport()->size();
    if (!viewportSize.isValid())
        viewportSize = QSize(0, 0);

    if (viewItems_.empty()) {
        // doItemsLayout();
    }

    int itemsInViewport = 0;

    const int itemsCount = viewItems_.size();
    if (itemsCount == 0)
        return;

    const int viewportHeight = viewportSize.height();
    int itemsInRow           = 1;
    for (int height = 0, item = itemsCount - 1; item >= 0; item -= itemsInRow) {
        // UiLog().dbg() << "item=" << item;
        height += rowHeight(item, -1, itemsInRow);
        if (height > viewportHeight)
            break;
        itemsInViewport++;
    }
#ifdef _UI_COMPACTVIEW_DEBUG
    UiLog().dbg() << "  itemsCount=" << itemsCount << " rowCount=" << rowCount_;
    UiLog().dbg() << "  itemsInViewport " << itemsInViewport;
#endif

    if (verticalScrollMode_ == ScrollPerItem) {
        if (!viewItems_.empty())
            itemsInViewport = qMax(1, itemsInViewport);

        // verticalScrollBar()->setRange(0, itemsCount - itemsInViewport);
        verticalScrollBar()->setRange(0, rowCount_ - itemsInViewport);
        verticalScrollBar()->setPageStep(itemsInViewport);
        verticalScrollBar()->setSingleStep(1);
    }
    else {
        // scroll per pixel
    }

    // Horizontal scrollbar
    if (viewportSize.width() < maxRowWidth_) {
        horizontalScrollBar()->setRange(0, maxRowWidth_ + 10 - viewportSize.width());
        horizontalScrollBar()->setPageStep(viewportSize.width());
        horizontalScrollBar()->setSingleStep(1);
    }
    else {
        horizontalScrollBar()->setRange(0, 0);
    }
}

/*
  Returns the rectangle on the viewport occupied by the item at \a index.
  If the index is not visible or explicitly hidden, the returned rectangle is invalid.
*/
QRect CompactView::visualRect(const QModelIndex& index) const {
    // if (!d->isIndexValid(index) || isIndexHidden(index))
    //     return QRect();

    // d->executePostedLayout();

    int vi = viewIndex(index);
    if (vi < 0)
        return {};

    int y  = -1;
    int rh = 0;
    coordinateForItem(vi, y, rh);
    if (y >= 0) {
        // return QRect(viewItems_[vi].x, y, viewItems_[vi].width,rh); //TODO: optimise it
        return {viewItems_[vi].x - 1 - translation(), y, viewItems_[vi].width + 2, rh};
    }
    return {};
}

// Returns the viewport y coordinate for  item.
void CompactView::coordinateForItem(int item, int& itemY, int& itemRowHeight) const {
    itemY = -1;
    if (verticalScrollMode_ == ScrollPerItem) {
        int offset = 0;
        // firstVisibleItem must always start a row!!!!
        int topViewItemIndex = firstVisibleItem(offset);
        if (item >= topViewItemIndex) {
            // search in the visible area first and continue down
            // ### slow if the item is not visible

            const int itemsCount     = viewItems_.size();
            const int viewportHeight = viewport()->size().height();
            int itemsInRow           = 1;

            for (int height = 0, viewItemIndex = topViewItemIndex;
                 height <= viewportHeight && viewItemIndex < itemsCount;
                 viewItemIndex += itemsInRow) {
                int h = rowHeight(viewItemIndex, 1, itemsInRow);
                if (viewItemIndex <= item && item < viewItemIndex + itemsInRow) {
                    itemY         = height;
                    itemRowHeight = h;
                    return;
                }
                height += h;
            }
        }
    }
}

// coordinate is in viewport coordinates
int CompactView::itemAtCoordinate(const QPoint& coordinate) const {
    const std::size_t itemCount = viewItems_.size();
    if (itemCount == 0)
        return -1;

    if (verticalScrollMode_ == ScrollPerItem) {
        // int topRow = verticalScrollBar()->value();

        int offset           = 0;
        int topViewItemIndex = firstVisibleItem(offset);

        if (coordinate.y() >= 0) {
            // the coordinate is in or below the viewport
            int viewItemCoordinate = 0;
            int itemsInRow         = 0;
            for (std::size_t viewItemIndex = topViewItemIndex; viewItemIndex < itemCount; viewItemIndex += itemsInRow) {
                viewItemCoordinate += rowHeight(viewItemIndex, 1, itemsInRow);
                if (viewItemCoordinate > coordinate.y()) {
                    viewItemIndex = itemAtRowCoordinate(viewItemIndex, itemsInRow, coordinate.x() + translation());
                    return (viewItemIndex >= itemCount ? -1 : viewItemIndex);
                }
            }
        }
    }

    return -1;
}

// return the item index at the absolute x coordinate (i.e. not viewport x coordinate)
int CompactView::itemAtRowCoordinate(int start, int count, int logicalXPos) const {
    for (int i = start; i < start + count; i++) {
        int left  = viewItems_[i].x - 1;
        int right = viewItems_[i].right() + 2;
        if (!viewItems_[i].expanded && viewItems_[i].hasChildren)
            right = viewItems_[i].right() + connectorGap_ + expandConnectorLength_ + 3;

        if (left <= logicalXPos && right >= logicalXPos) {
            return i;
        }
    }
    return -1;
}

void CompactView::updateViewport(const QRect rect) {
    viewport()->update(rect);
}

void CompactView::navigateLeft(const QModelIndex& idx) {
    int item = viewIndex(idx);
    if (item > -1) {
        if (viewItems_[item].isFirstChild()) {
            navigatePrev(idx);
        }
        else {
            collapse(idx);
        }
    }
}

void CompactView::navigateRight(const QModelIndex& idx) {
    int item = viewIndex(idx);
    if (item > -1) {
        if (viewItems_[item].expanded && viewItems_[item].hasChildren) {
            navigateNext(idx);
        }
        else {
            expand(idx);
        }
    }
}

void CompactView::navigateUp(const QModelIndex& idx) {
    navigatePrev(idx);
}

void CompactView::navigateDown(const QModelIndex& idx) {
    navigateNext(idx);
}
