#include "logview.h"

#include "ecombobox.h"
#include "qad_types.h"
#include "ui_logview.h"

#include <QAbstractTextDocumentLayout>
#include <QEvent>
#include <QPixmap>
#include <QScrollBar>
#include <QTextBlock>
#include <QTextBlockUserData>
#include <QTextDocument>
#include <QTextEdit>


class TextBlockData: public QTextBlockUserData {
public:
	TextBlockData(const QString & kw): keyword(kw) {}
	QString keyword;
};


LogView::Category::Category() {
	bold = false;
}


void LogView::Category::makeIcon(QSize size, QSize size_icon, qreal ratio) {
	icon_image = QImage();
	if (!image.isNull()) {
		icon_image = image.scaled(size * ratio, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
		icon_image.setDevicePixelRatio(ratio);
#endif
	}
	QPixmap px = QPixmap::fromImage(image.scaled(size_icon * ratio, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
	px.setDevicePixelRatio(ratio);
#endif
	icon.addPixmap(px, QIcon::Active);
	icon.addPixmap(px, QIcon::Disabled);
	icon.addPixmap(px, QIcon::Normal);
	icon.addPixmap(px, QIcon::Selected);
}


LogView::LogView(QWidget * parent): QWidget(parent) {
	ui = new Ui::LogView();
	ui->setupUi(this);
	ui->textEdit->setContextMenuPolicy(Qt::ActionsContextMenu);
	actionLogSelectAll = new QAction(QIcon(":/icons/select-all.png"), tr("Select All"), this);
	actionLogCopy      = new QAction(QIcon(":/icons/edit-copy.png"), tr("Copy"), this);
	actionLogClear     = new QAction(QIcon(":/icons/edit-clear.png"), tr("Clear"), this);
	connect(actionLogSelectAll, SIGNAL(triggered(bool)), ui->textEdit, SLOT(selectAll()));
	connect(actionLogCopy, SIGNAL(triggered(bool)), ui->textEdit, SLOT(copy()));
	connect(actionLogClear, SIGNAL(triggered(bool)), ui->textEdit, SLOT(clear()));
	ui->textEdit->addAction(actionLogSelectAll);
	ui->textEdit->addAction(actionLogCopy);
	ui->textEdit->addAction(actionLogClear);
	ui->buttonClear->setDefaultAction(ui->actionClear);
	ui->labelIconSearch->setFixedSize(preferredIconSize(1.2, this));
	ui->comboCategory->addItem(tr("All"));
	ui->textEdit->document()->setUndoRedoEnabled(false);
	setLinesLimit(10000);
	QTextCursor tc(ui->textEdit->document());
	def_cf = tc.charFormat();
	def_bf = tc.blockFormat();
}


LogView::~LogView() {
	delete ui;
}


const QTextEdit * LogView::textEdit() const {
	return ui->textEdit;
}


void LogView::setLogFont(QFont f) {
	ui->textEdit->document()->setDefaultFont(f);
	QTextCursor tc(ui->textEdit->document());
	def_cf = tc.charFormat();
	def_bf = tc.blockFormat();
}


QFont LogView::logFont() const {
	return ui->textEdit->document()->defaultFont();
}


bool LogView::isFilterVisible() const {
	return ui->widgetToolbar->isVisible();
}


int LogView::linesLimit() const {
	int ret = ui->textEdit->document()->maximumBlockCount();
	if (ret > 0) --ret;
	return ret;
}


void LogView::registerCategory(const QString & label,
                               QString keyword,
                               const QImage & icon,
                               QBrush text_brush,
                               QBrush background,
                               bool bold) {
	QRegularExpression regexp(keyword,
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
	                          QRegularExpression::PatternOptions(QRegularExpression::CaseInsensitiveOption)
#else
	                          Qt::CaseInsensitive
#endif
	);
	registerCategory(label, regexp, icon, text_brush, background, bold);
}


void LogView::registerCategory(const QString & label,
                               QRegularExpression regexp,
                               const QImage & icon,
                               QBrush text_brush,
                               QBrush background,
                               bool bold) {
	if (!regexp.isValid() || regexp.pattern().isEmpty()) return;
	removeCategory(regexp);
	Category c;
	c.regexp     = regexp;
	c.label      = label;
	c.image      = icon;
	c.text_brush = text_brush;
	c.background = background;
	c.bold       = bold;
	c.makeIcon(iconImageSize(),
	           preferredIconSize(1., this)
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
	               ,
	           devicePixelRatioF()
#endif
	);
	categories.append(c);
	ui->comboCategory->addItem(c.icon, label, QVariant(regexp));
}


void LogView::removeCategory(QString keyword) {
	QRegularExpression regexp(keyword,
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
	                          QRegularExpression::PatternOptions(QRegularExpression::CaseInsensitiveOption)
#else
	                          Qt::CaseInsensitive
#endif
	);
	removeCategory(regexp);
}


void LogView::removeCategory(QRegularExpression regexp) {
	Category c;
	c.regexp = regexp;
	categories.removeAll(c);
	for (int i = 1; i < ui->comboCategory->count(); ++i) {
		if (ui->comboCategory->itemData(i)
		        .
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
		    toRegularExpression()
#else
		    toRegExp()
#endif
		        .pattern() == regexp.pattern()) {
			ui->comboCategory->removeItem(i);
			--i;
		}
	}
}


void LogView::clearCategories() {
	ui->comboCategory->blockSignals(true);
	while (ui->comboCategory->count() > 1)
		ui->comboCategory->removeItem(ui->comboCategory->count() - 1);
	categories.clear();
	ui->comboCategory->blockSignals(false);
	filter();
}


void LogView::addText(const QString & text, bool insert_newline) {
	addText(text, QString(), insert_newline);
}


void LogView::addText(const QString & text, const QString & keyword, bool insert_newline) {
	if (text.isEmpty()) return;
	QTextCursor tc(ui->textEdit->document());
	QStringList sl = text.split("\n");
	tc.movePosition(QTextCursor::End);
	QScrollBar * bar = ui->textEdit->verticalScrollBar();
	bool at_end      = (bar->value() == bar->maximum()) || bar->isHidden();
	for (int i = 0; i < sl.size(); ++i) {
		tc.insertText(sl[i]);
		if (!keyword.isEmpty()) tc.block().setUserData(new TextBlockData(keyword));
		if ((i < sl.size() - 1) || insert_newline) newLine(keyword);
	}
	if (at_end) scrollToBottom();
}


void LogView::changeEvent(QEvent * e) {
	QWidget::changeEvent(e);
	switch (e->type()) {
	case QEvent::LanguageChange:
		ui->retranslateUi(this);
		ui->comboCategory->setItemText(0, tr("All"));
		actionLogSelectAll->setText(tr("Select All"));
		actionLogCopy->setText(tr("Copy"));
		actionLogClear->setText(tr("Clear"));
		break;
	case QEvent::Polish: {
		ui->labelIconSearch->setFixedSize(preferredIconSize(1.2, this));
		QSize is = iconImageSize(), is_i = preferredIconSize(1., this);
		for (int i = 0; i < categories.size(); ++i)
			categories[i].makeIcon(is,
			                       is_i
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
			                       ,
			                       devicePixelRatioF()
#endif
			);
	} break;
	default: break;
	}
}


void LogView::newLine(const QString & keyword) {
	QTextCursor tc(ui->textEdit->document());
	tc.movePosition(QTextCursor::End);
	tc.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
	QString line = tc.selectedText();
	QImage icon;
	foreach(const Category & c, categories) {
		bool matched = false;
		if (keyword.isEmpty())
			matched = line.contains(c.regexp);
		else
			matched = (keyword == c.regexp.pattern());
		if (matched) {
			QTextCharFormat cf  = def_cf;
			QTextBlockFormat bf = def_bf;
			if (c.text_brush != Qt::NoBrush) cf.setForeground(c.text_brush);
			if (c.background != Qt::NoBrush) bf.setBackground(c.background);
			if (c.bold) cf.setFontWeight(QFont::Bold);
			tc.setCharFormat(cf);
			tc.setBlockFormat(bf);
			icon = c.icon_image;
			break;
		}
	}
	if (!icon.isNull()) {
		tc.movePosition(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor);
		tc.insertImage(icon);
	}
	QRegularExpression regexp =
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
		ui->comboCategory->currentData().toRegularExpression();
#else
		ui->comboCategory->itemData(ui->comboCategory->currentIndex()).toRegExp();
#endif
	QString fs = ui->lineEdit->text();
	if (isFilterVisible()) filterBlock(tc.block(), fs, regexp);
	tc.movePosition(QTextCursor::End);
	tc.setCharFormat(def_cf);
	tc.insertBlock();
	tc.setBlockFormat(def_bf);
}


QSize LogView::iconImageSize() {
	int hei = QFontMetrics(ui->textEdit->document()->defaultFont()).height() / 1.25;
	return QSize(hei, hei);
}


void LogView::filterBlock(QTextBlock block, const QString & fs, const QRegularExpression & regexp) {
	bool vis     = true; //, pvis = block.isVisible();
	QString line = block.text();
	if (!line.isEmpty()) {
		if (line[0] == QChar::ObjectReplacementCharacter) line.remove(0, 1);
	}
	if (regexp.isValid() && !regexp.pattern().isEmpty()) {
		QString kw;
		TextBlockData * bd = (TextBlockData *)block.userData();
		if (bd) kw = bd->keyword;
		if (!kw.isEmpty())
			vis = vis && (bd->keyword == regexp.pattern());
		else
			vis = vis && line.contains(regexp);
	}
	if (!fs.isEmpty()) vis = vis && line.contains(fs, Qt::CaseInsensitive);
	block.setVisible(vis);
	// qDebug() << "filterBlock" << line << vis;
	// if (vis != pvis)
	//	;//ui->textEdit->document()->mar
}


void LogView::setFilterVisible(bool yes) {
	ui->widgetToolbar->setHidden(!yes);
	filter();
}


void LogView::setLinesLimit(int l) {
	ui->textEdit->document()->setMaximumBlockCount(l <= 0 ? 0 : l + 1);
}


void LogView::clear() {
	ui->textEdit->clear();
}


void LogView::scrollToBottom() {
	QScrollBar * bar = ui->textEdit->verticalScrollBar();
	bar->setValue(bar->maximum());
}


void LogView::filter() {
	QTextDocument * doc = ui->textEdit->document();
	int bc              = doc->blockCount();
	QRegularExpression regexp;
	QString fs;
	if (isFilterVisible()) {
		regexp =
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
			ui->comboCategory->currentData().toRegularExpression();
#else
			ui->comboCategory->itemData(ui->comboCategory->currentIndex()).toRegExp();
#endif
		fs = ui->lineEdit->text();
	}
	QTextBlock bl;
	for (int i = 0; i < bc; ++i) {
		bl = doc->findBlockByNumber(i);
		filterBlock(bl, fs, regexp);
	}
	doc->markContentsDirty(0, bl.position() + bl.length());
}
