//
// Created by dtx on 2022/5/11.
//

#include "SelectPartitionTreeView.h"
#include "BaseHeaderView.h"
#include "utils/Utils.h"
#include <DLabel>
#include <DApplication>
#include <DGuiApplicationHelper>
#include <QAbstractTableModel>
#include <QJsonObject>
#include <QJsonArray>
#include <QToolTip>
#include <QMouseEvent>
#include <QTimer>
#include <QHeaderView>
#include <QPainterPath>
#include <QtMath>

#if DTK_VERSION >= DTK_VERSION_CHECK(5, 5, 10, 0)
    #include <DPaletteHelper>
#else
    #include <DApplicationHelper>
#endif

SelectPartitionHeaderView::SelectPartitionHeaderView(Qt::Orientation orientation, QWidget *parent)
        : DHeaderView(orientation, parent)
{
    installEventFilter(this);
    viewport()->setAutoFillBackground(false);
}

int SelectPartitionHeaderView::sectionSizeHint(int logicalIndex) const
{
    return DHeaderView::sectionSizeHint(logicalIndex);
}

QSize SelectPartitionHeaderView::sizeHint() const
{
    return QSize(width(), 36);
}

void SelectPartitionHeaderView::paintEvent(QPaintEvent *event)
{
    QPainter painter(viewport());
    painter.save();
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setOpacity(1);

    auto *style = dynamic_cast<DStyle *>(DApplication::style());
    QStyleOptionHeader option;
    initStyleOption(&option);
    auto radius = style->pixelMetric(DStyle::PM_FrameRadius, &option);

    QRect rect = viewport()->rect();
    QRectF clipRect(rect.x(), rect.y(), rect.width(), rect.height() * 2);
    QRectF subRect(rect.x(), rect.y() + rect.height(), rect.width(), rect.height());
    QPainterPath clipPath, subPath;
    clipPath.addRoundedRect(clipRect, radius, radius);
    subPath.addRect(subRect);
    clipPath = clipPath.subtracted(subPath);
    painter.fillPath(clipPath, QColor(Qt::transparent));

    painter.setPen(QColor(Qt::lightGray));
    painter.drawPath(clipPath);

    DHeaderView::paintEvent(event);
    painter.restore();
}

void SelectPartitionHeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const {
    painter->save();
    painter->setRenderHint(QPainter::Antialiasing);
    painter->setOpacity(1);

    DPalette::ColorGroup cg = DPalette::Active;;
    QStyleOption opt;
    opt.initFrom(this);

#if DTK_VERSION >= DTK_VERSION_CHECK(5, 5, 10, 0)
    DPaletteHelper *dAppHelper = DPaletteHelper::instance();
#else
    DApplicationHelper *dAppHelper = DApplicationHelper::instance();
#endif
    DPalette palette = dAppHelper->palette(this);

    QStyleOptionHeader option;
    initStyleOption(&option);

    // title
    QRect contentRect(rect.x(), rect.y(), rect.width(), rect.height() - m_spacing);

    QBrush contentBrush(palette.color(cg, DPalette::Base));
    QBrush vSpacingBrush(palette.color(cg, DPalette::FrameBorder));
    QRectF vSpacingRect(rect.x() - 10, rect.y() + 6, 1, rect.height() - 12);
    painter->fillRect(vSpacingRect, vSpacingBrush);

    QPen forground;
    forground.setColor(palette.color(cg, DPalette::Text));

    QRect textRect = {
            contentRect.x() - 4,
            contentRect.y() + 9,
            contentRect.width(),
            contentRect.height()};
    QString title = model()->headerData(logicalIndex, orientation(), Qt::DisplayRole).toString();
    int align = model()->headerData(logicalIndex, orientation(), Qt::TextAlignmentRole).toInt();

    // forground
    painter->setPen(forground);
    painter->drawText(textRect, align, title);

    painter->restore();
}

SelectPartitionItemModel::SelectPartitionItemModel(int columnCount, QObject *parent)
        : QAbstractTableModel(parent), m_columnCount(columnCount)
{

}

int SelectPartitionItemModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return m_dataInfo.count();
}

int SelectPartitionItemModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return m_columnCount;
}

QVariant SelectPartitionItemModel::data(const QModelIndex &index, int role) const
{
    int iRow = index.row();
    if (iRow >= m_dataInfo.count()) {
        return QVariant();
    }

    return m_dataInfo[iRow].marshal();
}

QVariant SelectPartitionItemModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
        if (section < m_headItems.size())
            return m_headItems.at(section);
    }
    return QVariant();
}

void SelectPartitionItemModel::setHeadData(const QStringList &heads)
{
    m_headItems = heads;
}

SelectPartitionModelItem SelectPartitionItemModel::getDataInfo(int row)
{
    if (row >= m_dataInfo.count()) {
        return {};
    }
    return m_dataInfo.at(row);
}

void SelectPartitionItemModel::setDatas(QMap<QString, QList<QJsonObject>> modeData)
{
    // 初始化mode中的业务数据
    beginResetModel();
    m_dataInfo.clear();
    for (QString infoItem : modeData.keys()) {
        QList<QJsonObject> parttionData = modeData.value(infoItem);
        SelectPartitionModelItem rootItem;
        rootItem.name = infoItem;
        rootItem.itemType = "root";
        if (!parttionData.isEmpty()) {
            rootItem.deviceType = parttionData.at(0).value("deviceType").toString();
        }
        m_dataInfo.append(rootItem);
        for (int i = 0; i < parttionData.size(); i++) {
            SelectPartitionModelItem partItem;
            partItem.uuid = parttionData.at(i).value("uuid").toString();
            partItem.itemType = "item";
            partItem.name = parttionData.at(i).value("name").toString();
            partItem.deviceType = parttionData.at(i).value("deviceType").toString();
            partItem.mountPoint = parttionData.at(i).value("mountPoint").toString();
            partItem.deviceName = infoItem;
            partItem.partIndexInMap = i;
            partItem.partListSize = parttionData.size();
            partItem.size = parttionData.at(i).value("size").toVariant().toULongLong();
            partItem.used = parttionData.at(i).value("used").toVariant().toULongLong();
            partItem.free = parttionData.at(i).value("free").toVariant().toULongLong();
            m_dataInfo.append(partItem);
            //Utils::byte2DisplaySize(parttionData.at(i).value("size").toString().toULongLong());
        }
    }
    endResetModel();
}

SelectPartitionItemDelegate::SelectPartitionItemDelegate(QObject *parent)
    : QItemDelegate(parent)
{

}

void SelectPartitionItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    painter->save();
    //painter->setRenderHint(QPainter::Antialiasing);

    QMap<int, QRect> actions;
    QList<int> flags;
    QStyleOptionViewItem newOption = option;
    QFontMetrics fm(option.widget->font());
    QJsonObject infoObj = index.data().toJsonObject();
    SelectPartitionModelItem infoItem;
    infoItem.unmarshal(infoObj);

    if (!infoItem.itemType.compare("root")) {
        if (index.column() == 0) {
            QString strDeviceSize;
            if (infoItem.deviceType == "lvm") {
                strDeviceSize = fm.elidedText(tr("VG:") + " " + infoItem.name, Qt::ElideRight, 500);
            } else {
                strDeviceSize = fm.elidedText(tr("Disk:") + " " + infoItem.name, Qt::ElideRight, 500);
            }
            painter->drawText(newOption.rect.x() - 10, newOption.rect.y() + 24, strDeviceSize);
        }
    } else if (!infoItem.itemType.compare("item")) {
        // 第一列绘制的是选中图标
        if (index.column() == 0) {
            QPixmap pixmap;
            if (m_selectRow == index.row()) {
                pixmap = Utils::hidpiPixmap(":/resources/icons/radio_button_fill.svg", QSize(18, 19));
            } else {
                pixmap = Utils::hidpiPixmap(":/resources/icons/radio_button_off.svg", QSize(18, 18));
            }
            painter->drawPixmap(newOption.rect.x(),
                                newOption.rect.y() + (m_rowHeiht*qApp->devicePixelRatio() - pixmap.height()) / 2,
                                pixmap);
        }

        // 第二列绘制的是分区图标和分区名文本
        if (index.column() == 1) {
            QPixmap pixmap = Utils::hidpiPixmap(":/resources/icons/partition.svg", QSize(28, 28));;
            painter->drawPixmap(newOption.rect.x(),
                                newOption.rect.y() + (m_rowHeiht*qApp->devicePixelRatio() - pixmap.height()) / 2,
                                pixmap);

            painter->setPen(QColor(0,0,0));
            QString strDeviceSize = fm.elidedText(infoItem.name, Qt::ElideRight, 200);
            painter->drawText(newOption.rect.x() + pixmap.width() + 10, newOption.rect.y() + 23, strDeviceSize);
        }

        painter->setPen(QColor(0, 0, 0, 155));
        // 第三列绘制的是分区空间文本
        if (index.column() == 2) {
            QString strFileSystem = fm.elidedText(Utils::byte2DisplaySize(infoItem.size), Qt::ElideRight, 110);
            painter->drawText(newOption.rect.x(), newOption.rect.y() + 23, strFileSystem);
        }

        // 第四列绘制的是分区可用空间文本
        if (index.column() == 3) {
            QString strMountPoint = fm.elidedText(Utils::byte2DisplaySize(infoItem.free), Qt::ElideRight, 110);
            painter->drawText(newOption.rect.x(), newOption.rect.y() + 23, strMountPoint);
        }
    }

    painter->restore();
}

QSize SelectPartitionItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const {
    int iColumn = index.column();
    QJsonObject jsonObj = index.data().toJsonObject();

    if (iColumn < m_sizes.size()) {
        return QSize(m_sizes.at(iColumn), m_rowHeiht);
    }

    return option.rect.size();
}

SelectPartitionTreeView::SelectPartitionTreeView(QWidget *parent)
        : DTreeView(parent), m_itemDelegate(Q_NULLPTR) {
    setAttribute(Qt::WA_Hover);
    setMouseTracking(true);
    setFocusPolicy(Qt::NoFocus);
    setContextMenuPolicy(Qt::NoContextMenu);
    setSelectionBehavior(QAbstractItemView::SelectRows);
    setSelectionMode(QAbstractItemView::NoSelection);
    setFrameShape(QFrame::NoFrame);
    setEditTriggers(QAbstractItemView::NoEditTriggers);

    m_headerView = new SelectPartitionHeaderView(Qt::Horizontal, this);
    m_headerView->setStretchLastSection(true);
    setTabOrder(m_headerView, this);
    setHeader(m_headerView);

    m_itemDelegate = new SelectPartitionItemDelegate();
    setItemDelegate(m_itemDelegate);
}

void SelectPartitionTreeView::setColumnsRowSize(const QList<int> &widths, const int &height)
{
    for (int i = 0; i < widths.size(); i++) {
        setColumnWidth(i, widths.at(i));
    }
    m_itemDelegate->setColumnsRowSize(widths, height);
}

void SelectPartitionTreeView::setSelectIndex(int index) {
    if (m_itemDelegate != nullptr) {
        selectionModel()->model()->layoutChanged();
        m_itemDelegate->setSelect(index);
    }
}

void SelectPartitionTreeView::drawRow(QPainter *painter, const QStyleOptionViewItem &options,
                                      const QModelIndex &index) const {
    painter->save();
    painter->setRenderHint(QPainter::Antialiasing);

    DPalette::ColorGroup cg = DPalette::Normal;

    auto *style = dynamic_cast<DStyle *>(DApplication::style());

    auto radius = style->pixelMetric(DStyle::PM_FrameRadius, &options);
    auto margin = style->pixelMetric(DStyle::PM_ContentsMargins, &options);

    auto palette = options.palette;
    SelectPartitionItemModel *devModel = static_cast<SelectPartitionItemModel *>(model());
    SelectPartitionModelItem infoItem = devModel->getDataInfo(index.row());

    QBrush background;

    QStyleOptionViewItem opt(options);
    opt.rect.adjust(0, 0, 10, -10);

    if (options.state & DStyle::State_Enabled) {
        if (selectionModel()->isSelected(index)) {
            background = palette.color(cg, DPalette::Highlight);
            // #ref: DStyle::generatedBrush
            if (m_pressed.isValid() && m_pressed.row() == index.row()) {
                // pressed
                background = style->adjustColor(background.color(), 0, 0, -10);
                opt.state = options.state | QStyle::State_Sunken;
            } else if (m_hover.isValid() && m_hover.row() == index.row()) {
                // hovered
                background = style->adjustColor(background.color(), 0, 0, 20);
            }
        } else {
            auto baseColor = palette.color(DPalette::Active, DPalette::Base);

            if (m_pressed.isValid() && m_pressed.row() == index.row()) {
                // pressed
                background = style->adjustColor(baseColor, 0, 0, 60, 0, 0, 20, 5);
            } else if (m_hover.isValid() && m_hover.row() == index.row()) {
                // hovered
                DGuiApplicationHelper::ColorType themeType = DGuiApplicationHelper::instance()->themeType();

                if (themeType == DGuiApplicationHelper::LightType) {
                    background = style->adjustColor(baseColor, 0, 0, -10);
                } else if (themeType == DGuiApplicationHelper::DarkType) {
                    background = style->adjustColor(baseColor, 0, 0, 20);
                }
            } else {
                background = palette.color(DPalette::Normal, DPalette::AlternateBase);
            }
        }
    }

    QFontMetrics fm(options.widget->font());
    if (!infoItem.itemType.compare("item")) {
        // draw row background
        QPainterPath path;
        QRect rowRect{options.rect.x(),
                      options.rect.y(),
                      header()->length() - header()->sectionPosition(0),
                      options.rect.height() - 2};
        rowRect.setX(rowRect.x() + margin);
        rowRect.setWidth(rowRect.width() - margin);

        if (infoItem.partListSize > 1) {
            if (infoItem.partIndexInMap == 0) {
                path.addRoundedRect(rowRect, radius, radius);
                path.setFillRule(Qt::WindingFill);
                QRect temp_rect(rowRect.left(), rowRect.top() + rowRect.height() / 2, rowRect.width(),rowRect.height() / 2);
                path.addRect(temp_rect);
            } else if ((infoItem.partIndexInMap + 1) == infoItem.partListSize) {
                path.addRoundedRect(rowRect, radius, radius);
                path.setFillRule(Qt::WindingFill);
                QRect temp_rect(rowRect.left(), rowRect.top(), rowRect.width(), rowRect.height() / 2);
                path.addRect(temp_rect);
            } else {
                path.addRoundedRect(rowRect, 0, 0);
            }
        } else {
            path.addRoundedRect(rowRect, radius, radius);
        }

        painter->fillPath(path, background);

        // draw focus
        if (hasFocus() && currentIndex().row() == index.row()) {
            QStyleOptionFocusRect o;
            o.QStyleOption::operator=(options);
            o.state |= QStyle::State_KeyboardFocusChange | QStyle::State_HasFocus;
            o.rect = style->visualRect(layoutDirection(), viewport()->rect(), rowRect);
            style->drawPrimitive(DStyle::PE_FrameFocusRect, &o, painter);
        }
    }

    DTreeView::drawRow(painter, opt, index);
    painter->restore();
}

bool SelectPartitionTreeView::viewportEvent(QEvent *event)
{
    switch (event->type()) {
        case QEvent::HoverLeave: {
            auto rect = visualRect(m_hover);
            rect.setX(0);
            rect.setWidth(viewport()->width());
            m_hover = QModelIndex();
            viewport()->update(rect);
            break;
        }
        case QEvent::HoverEnter:
        case QEvent::HoverMove: {
            auto *hev = dynamic_cast<QHoverEvent *>(event);
            m_hover = indexAt(hev->position().toPoint());
            auto rect = visualRect(m_hover);
            rect.setX(0);
            rect.setWidth(viewport()->width());
            viewport()->update(rect);
            break;
        }
        case QEvent::MouseMove:
        case QEvent::MouseButtonRelease:
        case QEvent::MouseButtonDblClick:
        case QEvent::MouseButtonPress: {
            auto *mev = dynamic_cast<QMouseEvent *>(event);
            auto newIndex = indexAt(mev->pos());
            QRegion region;
            QRect rect;
            if (m_pressed.isValid()) {
                rect = visualRect(m_pressed);
                rect.setX(0);
                rect.setWidth(viewport()->width());
                region += rect;
            }
            if (newIndex.isValid() && newIndex.row() != m_pressed.row()) {
                rect = visualRect(newIndex);
                rect.setX(0);
                rect.setWidth(viewport()->width());
                region += rect;
            }
            m_pressed = (mev->button() == Qt::LeftButton &&
                         (mev->type() == QEvent::MouseButtonPress || mev->type() == QEvent::MouseButtonDblClick))
                        ? newIndex : QModelIndex();
            viewport()->update(region);
            break;
        }
        default:
            break;
    }
    return DTreeView::viewportEvent(event);
}

void SelectPartitionTreeView::paintEvent(QPaintEvent *event) {
    QPainter painter(viewport());
    painter.save();
    painter.setRenderHints(QPainter::Antialiasing);
    painter.setOpacity(1);
    painter.setClipping(true);

    QWidget *wnd = DApplication::activeWindow();
    DPalette::ColorGroup cg;
    if (!wnd) {
        cg = DPalette::Inactive;
    } else {
        cg = DPalette::Active;
    }

    auto style = dynamic_cast<DStyle *>(DApplication::style());
    auto palette = DPalette();

    QBrush bgBrush(palette.color(cg, QPalette::Window));

    QStyleOptionFrame option;
    initStyleOption(&option);
    int radius = style->pixelMetric(DStyle::PM_FrameRadius, &option);

    QRect rect = viewport()->rect();
    QRectF clipRect(rect.x(), rect.y() - 6, rect.width(), rect.height() + 6);
    QPainterPath clipPath, subPath;
    clipPath.addRoundedRect(clipRect, radius, radius);

    painter.setPen(QColor(Qt::lightGray));
    painter.drawPath(clipPath);

    painter.restore();
    DTreeView::paintEvent(event);
}

int SelectPartitionTreeView::getTreeViewClickedDeviceName(const QModelIndex &index, int &partIndex, QString &deviceName)
{
    int retCode = -1;
    SelectPartitionItemModel *testMode = dynamic_cast<SelectPartitionItemModel *>(model());
    if (nullptr == testMode) {
        return retCode;
    }

    SelectPartitionModelItem infoItem = testMode->getDataInfo(index.row());
    if (!infoItem.itemType.compare("item")) {
        partIndex = infoItem.partIndexInMap;
        deviceName = infoItem.deviceName;
        retCode = 0;
    }

    return retCode;
}
