#include "containers_view.h"

#include <QPainter>
#include <QSortFilterProxyModel>
#include <QStyle>
#include <QTreeView>


enum ColumnContainers {
	ccType,
	ccItemSize,
	ccCount,
	ccBytesAllocated,
	ccBytesUsed,
	ccColumnCount,
};


ContainersModel::ContainersModel() {
	mode_changes = false;
	ls_column    = 0;
	ls_order     = Qt::AscendingOrder;
	all.resize(columnCount(), 0L);
	prev_all.resize(all.size(), 0L);
}


void ContainersModel::update(const PIVector<PIIntrospectionContainers::TypeInfo> & t) {
	prev_data.clear();
	all.fill(0U);
	for (const auto & i: cur_data) {
		prev_data[i.id] = i;
	}
	for (const auto & i: t) {
		all[ccCount] += i.count;
#if PIP_VERSION >= PIP_MAKE_VERSION(2, 35, 0)
		all[ccBytesAllocated] += i.allocated_bytes;
		all[ccBytesUsed] += i.used_bytes;
#else
		all[ccBytesAllocated] += i.allocated;
		all[ccBytesUsed] += i.used;
#endif
	}
	int pts  = cur_data.size_s();
	cur_data = t;
	if (t.size_s() > pts) {
		beginInsertRows(QModelIndex(), pts, t.size_s() - 1);
		endInsertRows();
	}
	if (t.size_s() < pts) {
		beginRemoveRows(QModelIndex(), t.size_s(), pts - 1);
		endRemoveRows();
	}
	sort(ls_column, ls_order);
	emit headerDataChanged(Qt::Horizontal, ccCount, columnCount());
}


void ContainersModel::clear() {
	beginRemoveRows(QModelIndex(), 0, cur_data.size_s() - 1);
	cur_data.clear();
	prev_data.clear();
	all.fill(0L);
	endRemoveRows();
}


int ContainersModel::rowCount(const QModelIndex & parent) const {
	return cur_data.size_s();
}


int ContainersModel::columnCount(const QModelIndex & parent) const {
	return ccColumnCount;
}


QModelIndex ContainersModel::index(int row, int column, const QModelIndex & parent) const {
	if (row >= cur_data.size_s() || row >= cur_data.size_s()) return QModelIndex();
	return createIndex(row, column, cur_data[row].id);
}


bool ContainersModel::hasChildren(const QModelIndex & parent) const {
	if (!parent.isValid()) return true;
	return false;
}


QVariant ContainersModel::headerData(int section, Qt::Orientation orientation, int role) const {
	if (orientation != Qt::Horizontal || role != Qt::DisplayRole) return QVariant();
	PIVector<llong> ret = all;
	if (mode_changes) {
		for (int i = 0; i < all.size_s(); ++i)
			ret[i] -= prev_all[i];
	}
	switch (section) {
	case ccType: return tr("Type");
	case ccItemSize: return tr("Item size");
	case ccCount: return tr("Count (%1)").arg(ret[ccCount]);
	case ccBytesAllocated: return tr("Allocated (%1)").arg(PI2QString(PIString::readableSize(ret[ccBytesAllocated])));
	case ccBytesUsed: return tr("Used (%1)").arg(PI2QString(PIString::readableSize(ret[ccBytesUsed])));
	default: break;
	}
	return QVariant();
}


QVariant ContainersModel::data(const QModelIndex & index, int role) const {
	if (role != Qt::DisplayRole && role != Qt::UserRole && role != (Qt::UserRole + 1)) return QVariant();
	uint id = uint(index.internalId());
	const PIIntrospectionContainers::TypeInfo & t(cur_data[index.row()]);
	llong v = 0L;
	if (role == Qt::DisplayRole || role == Qt::UserRole) {
		switch (index.column()) {
		case ccType: return PI2QString(t.name);
		case ccItemSize:
			if (role == Qt::UserRole) return t.item_size;
#if PIP_VERSION >= PIP_MAKE_VERSION(2, 35, 0)
			return PI2QString(t.item_size_str);
#else
			return PI2QString(PIString::readableSize(t.item_size));
#endif
		default: break;
		}
		if (mode_changes) {
			switch (index.column()) {
			case ccCount: return int(t.count) - int(prev_data.value(id).count);
			case ccBytesAllocated:
#if PIP_VERSION >= PIP_MAKE_VERSION(2, 35, 0)
				v = t.allocated_bytes;
				v -= prev_data.value(id).allocated_bytes;
#else
				v = t.allocated;
				v -= prev_data.value(id).allocated;
#endif
				if (role == Qt::UserRole) return piAbs(v);
				return PI2QString(PIString::readableSize(v));
			case ccBytesUsed:
#if PIP_VERSION >= PIP_MAKE_VERSION(2, 35, 0)
				v = t.used_bytes;
				v -= prev_data.value(id).used_bytes;
#else
				v = t.used;
				v -= prev_data.value(id).used;
#endif
				if (role == Qt::UserRole) return piAbs(v);
				return PI2QString(PIString::readableSize(v));
			}
		} else {
			switch (index.column()) {
			case ccCount: return t.count;
			case ccBytesAllocated:
#if PIP_VERSION >= PIP_MAKE_VERSION(2, 35, 0)
				if (role == Qt::UserRole) return t.allocated_bytes;
				return PI2QString(t.allocated_str);
			case ccBytesUsed:
				if (role == Qt::UserRole) return t.used_bytes;
				return PI2QString(t.used_str);
#else
				if (role == Qt::UserRole) return t.allocated;
				return PI2QString(PIString::readableSize(t.allocated));
			case ccBytesUsed:
				if (role == Qt::UserRole) return t.used;
				return PI2QString(PIString::readableSize(t.used));
#endif
			}
		}
	}
	if (role == (Qt::UserRole + 1) && (index.column() == ccCount)) {
		return t.count;
	}
	return QVariant();
}


Qt::ItemFlags ContainersModel::flags(const QModelIndex & index) const {
	return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}


// bool cmp(const PIIntrospectionContainers::TypeInfo & a, const PIIntrospectionContainers::TypeInfo & b);
#if PIP_VERSION >= PIP_MAKE_VERSION(2, 35, 0)
bool cmp_func_name_a(const PIIntrospectionContainers::TypeInfo & t0, const PIIntrospectionContainers::TypeInfo & t1) {
	return QString::localeAwareCompare(PI2QString(t0.name), PI2QString(t1.name)) < 0;
}
bool cmp_func_name_d(const PIIntrospectionContainers::TypeInfo & t0, const PIIntrospectionContainers::TypeInfo & t1) {
	return !cmp_func_name_a(t0, t1);
}
#  define CMP_FUNC(field)                                                                                                         \
	  bool cmp_func_##field##_a(const PIIntrospectionContainers::TypeInfo & t0, const PIIntrospectionContainers::TypeInfo & t1) { \
		  return t0.field < t1.field;                                                                                             \
	  }                                                                                                                           \
	  bool cmp_func_##field##_d(const PIIntrospectionContainers::TypeInfo & t0, const PIIntrospectionContainers::TypeInfo & t1) { \
		  return t0.field > t1.field;                                                                                             \
	  }
#else
int cmp_func_name_a(const PIIntrospectionContainers::TypeInfo * t0, const PIIntrospectionContainers::TypeInfo * t1) {
	return QString::localeAwareCompare(PI2QString(t0->name), PI2QString(t1->name));
}
int cmp_func_name_d(const PIIntrospectionContainers::TypeInfo * t0, const PIIntrospectionContainers::TypeInfo * t1) {
	return -cmp_func_name_a(t0, t1);
}
#  define CMP_FUNC(field)                                                                                                        \
	  int cmp_func_##field##_a(const PIIntrospectionContainers::TypeInfo * t0, const PIIntrospectionContainers::TypeInfo * t1) { \
		  return (t0->field) <= (t1->field) ? -1 : ((t0->field) == (t1->field) ? 0 : 1);                                         \
	  }                                                                                                                          \
	  int cmp_func_##field##_d(const PIIntrospectionContainers::TypeInfo * t0, const PIIntrospectionContainers::TypeInfo * t1) { \
		  return -cmp_func_##field##_a(t0, t1);                                                                                  \
	  }
#endif
CMP_FUNC(item_size)
CMP_FUNC(count)
#if PIP_VERSION >= PIP_MAKE_VERSION(2, 35, 0)
CMP_FUNC(allocated_bytes)
CMP_FUNC(used_bytes)
#else
CMP_FUNC(allocated)
CMP_FUNC(used)
#endif
#undef CMP_FUNC
void ContainersModel::sort(int column, Qt::SortOrder order) {
	ls_column = column;
	ls_order  = order;
	if (cur_data.isEmpty()) return;
	PIVector<PIIntrospectionContainers::TypeInfo>::CompareFunc cf = nullptr;
#if PIP_VERSION >= PIP_MAKE_VERSION(2, 35, 0)
	switch (column) {
	case ccType: cf = order == Qt::AscendingOrder ? cmp_func_name_a : cmp_func_name_d; break;
	case ccItemSize: cf = order == Qt::AscendingOrder ? cmp_func_item_size_a : cmp_func_item_size_d; break;
	case ccCount: cf = order == Qt::AscendingOrder ? cmp_func_count_a : cmp_func_count_d; break;
	case ccBytesAllocated: cf = order == Qt::AscendingOrder ? cmp_func_allocated_bytes_a : cmp_func_allocated_bytes_d; break;
	case ccBytesUsed: cf = order == Qt::AscendingOrder ? cmp_func_used_bytes_a : cmp_func_used_bytes_d; break;
	default: break;
	}
#else
	switch (column) {
	case ccType: cf = order == Qt::AscendingOrder ? cmp_func_name_a : cmp_func_name_d; break;
	case ccItemSize: cf = order == Qt::AscendingOrder ? cmp_func_item_size_a : cmp_func_item_size_d; break;
	case ccCount: cf = order == Qt::AscendingOrder ? cmp_func_count_a : cmp_func_count_d; break;
	case ccBytesAllocated: cf = order == Qt::AscendingOrder ? cmp_func_allocated_a : cmp_func_allocated_d; break;
	case ccBytesUsed: cf = order == Qt::AscendingOrder ? cmp_func_used_a : cmp_func_used_d; break;
	default: break;
	}
#endif
	if (cf) cur_data.sort(cf);
	// qDebug() << "sort" << column << order;
	dataChanged(index(0, 0), index(cur_data.size_s() - 1, columnCount()));
}


void ContainersModel::setChangesMode(bool yes) {
	mode_changes = yes;
	if (cur_data.isEmpty()) return;
	sort(ls_column, ls_order);
	emit headerDataChanged(Qt::Horizontal, ccCount, columnCount());
}


void ContainersDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const {
	QStyledItemDelegate::paint(painter, option, index);
	if (model->index(index.row(), ccCount).data(Qt::UserRole + 1).toInt() == 0) {
		QColor col;
		if (option.state == QStyle::State_Enabled || option.state == QStyle::State_HasFocus)
			col = option.palette.color(QPalette::Active, QPalette::WindowText);
		else
			col = option.palette.color(QPalette::Disabled, QPalette::WindowText);
		col.setAlphaF(0.5);
		int y = option.rect.center().y() + (option.fontMetrics.height() / 2.5) - option.fontMetrics.strikeOutPos();
		painter->setPen(QPen(col, lineThickness()));
		painter->drawLine(QPoint(option.rect.left(), y), QPoint(option.rect.right(), y));
	}
}


ContainersView::ContainersView(QWidget * parent): QWidget(parent) {
	setupUi(this);
	model = new ContainersModel();
	connect(radioChanges, SIGNAL(toggled(bool)), model, SLOT(setChangesMode(bool)));
	QSortFilterProxyModel * proxy = new QSortFilterProxyModel();
	proxy->setSourceModel(model);
	proxy->setSortRole(Qt::UserRole);
	proxy->setDynamicSortFilter(false);
	treeContainers->setModel(model);
	treeContainers->setItemDelegate(new ContainersDelegate(model));
}


ContainersView::~ContainersView() {}


void ContainersView::changeEvent(QEvent * e) {
	QWidget::changeEvent(e);
	switch (e->type()) {
	case QEvent::LanguageChange: retranslateUi(this); break;
	default: break;
	}
}


void ContainersView::showContainers(const PIVector<PIIntrospectionContainers::TypeInfo> & t) {
	model->update(t);
}


void ContainersView::clear() {
	model->clear();
}
