/*
 * scribiu -- read notebooks and voice memos from Livescribe pens
 * Copyright (C) 2015 Javier S. Pedro <javier@javispedro.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <QtCore/QDebug>
#include <QtGui/QResizeEvent>
#include "notebookview.h"

#define VIEW_MARGIN 2
#define PAGE_SEPARATION 100

NotebookView::NotebookView(QWidget *parent) :
    QGraphicsView(parent), _nb(new AfdNotebook(this)), _replay(new PaperReplay(this)),
    _zoom(100), _curPage(0)
{
	setScene(new QGraphicsScene(this));
	setTransformationAnchor(AnchorUnderMouse);
	setDragMode(ScrollHandDrag);
	setRenderHints(QPainter::Antialiasing);
}

void NotebookView::setNotebook(const QString &path)
{
	removePages();

	_nbPath = path;

	if (!path.isEmpty()) {
		if (createPages()) {
			emit pageNumbersChanged();
			if (!_pages.isEmpty()) {
				_curPage = _pages.begin().key();
				centerOn(_pages[_curPage]);
				emit curPageChanged();
			}
		} else {
			qWarning() << "Could not open notebook:" << _nbPath;
		}
	}
}

QString NotebookView::notebook() const
{
	return _nbPath;
}

void NotebookView::setPaperReplay(const QString &path)
{
	_replayPath = path;
	// TODO reload; for now, please set this before the notebook
}

QString NotebookView::paperReplay() const
{
	return _replayPath;
}

QList<int> NotebookView::pageNumbers() const
{
	return _pages.keys();
}

int NotebookView::curPage() const
{
	return _curPage;
}

void NotebookView::setCurPage(int page)
{
	if (page != _curPage) {
		_curPage = page;
		if (_zoom > 100) {
			setZoom(100);
		}
		if (_pages.contains(_curPage)) {
			centerOn(_pages[_curPage]);
		}
		emit curPageChanged();
	}
}

int NotebookView::zoom() const
{
	return _zoom;
}

void NotebookView::setZoom(int zoom)
{
	if (zoom != _zoom) {
		_zoom = zoom;
		calculateScale();
	}
}

QSize NotebookView::getCurPageSize() const
{
	return _nb->getPageSize(_curPage);
}

QRect NotebookView::getCurPageTrim() const
{
	return _nb->getPageTrim(_curPage);
}

QImage NotebookView::exportPage(int pageNum) const
{
	const QRect pageTrim = getCurPageTrim();
	QImage image(pageTrim.width() / 4, pageTrim.height() / 4, QImage::Format_RGB32);
	QPainter painter(&image);
	painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
	renderPage(&painter, pageNum, QRectF(), pageTrim);
	return image;
}

void NotebookView::renderPage(QPainter *painter, int pageNum, const QRectF &target, const QRectF &source) const
{
	QGraphicsScene scene;
	PageItem *item = new PageItem(_nb, 0, pageNum);
	scene.addItem(item);
	scene.render(painter, target, source, Qt::KeepAspectRatio);
}

void NotebookView::requestPaperReplay(const QString &file, qint64 time)
{
	emit paperReplayRequested(file, time);
}

void NotebookView::clear()
{
	removePages();
	emit pageNumbersChanged();
}

void NotebookView::prevPage()
{
	QMap<int, PageItem*>::iterator it = _pages.lowerBound(_curPage);
	if (it != _pages.end() && it != _pages.begin()) {
		--it;
		setCurPage(it.key());
	}
}

void NotebookView::nextPage()
{
	QMap<int, PageItem*>::iterator it = _pages.upperBound(_curPage);
	if (it != _pages.end()) {
		setCurPage(it.key());
	}
}

void NotebookView::resizeEvent(QResizeEvent *event)
{
	QGraphicsView::resizeEvent(event);
	calculateScale();
}

void NotebookView::scrollContentsBy(int dx, int dy)
{
	QGraphicsView::scrollContentsBy(dx, dy);
	QGraphicsItem *item = itemAt(size().width() / 2, size().height() / 2);
	while (item && item->type() != PageItem::Type) {
		item = item->parentItem();
	}
	if (item && item->type() == PageItem::Type) {
		PageItem * page = static_cast<PageItem*>(item);
		int centerPage = page->pageNum();
		if (centerPage != _curPage) {
			_curPage = centerPage;
			emit curPageChanged();
		}
	}
}

void NotebookView::removePages()
{
	_pages.clear();
	scene()->clear();
	scene()->setSceneRect(QRectF());
	_nb->close();
	_replay->close();
	_maxPageSize.setWidth(0);
	_maxPageSize.setHeight(0);
	if (_zoom > 100) {
		_zoom = 100;
		emit zoomChanged();
	}
	resetTransform();
}

bool NotebookView::createPages()
{
	if (!_nb->open(_nbPath)) return false;

	QStringList pens = _nb->penSerials();
	if (pens.isEmpty()) return false;

	// Failure to open paperreplay data is not fatal
	bool haveReplay = _replay->open(_replayPath, _nb->guid());

	QList<int> pagesWithStrokes = _nb->pagesWithStrokes(pens.first());
	Q_ASSERT(_pages.isEmpty());

	_maxPageSize.setWidth(0);
	_maxPageSize.setHeight(0);
	foreach (int pageNum, pagesWithStrokes) {
		PageItem *page = new PageItem(_nb, haveReplay ? _replay : 0, pageNum);
		QRectF box = page->boundingRect();
		if (box.width() > _maxPageSize.width()) {
			_maxPageSize.setWidth(box.width());
		}
		if (box.height() > _maxPageSize.height()) {
			_maxPageSize.setHeight(box.height());
		}
		_pages.insert(pageNum, page);
	}

	calculateScale();

	qreal curY = 0;
	foreach (PageItem *page, _pages) {
		QRectF box = page->boundingRect();
		page->setPos((_maxPageSize.width() - box.width()) / 2.0, curY);
		curY += box.height();
		curY += PAGE_SEPARATION;

		scene()->addItem(page);
	}

	scene()->setSceneRect(0, 0, _maxPageSize.width(), curY);

	return true;
}

void NotebookView::calculateScale()
{
	if (_pages.isEmpty() || _maxPageSize.isEmpty()) return;
	const int margin = VIEW_MARGIN;
	QRectF viewRect = viewport()->rect().adjusted(margin, margin, -margin, margin);
	qreal baseScale = qMin(viewRect.width() / _maxPageSize.width(),
	                       viewRect.height() / _maxPageSize.height());
	resetTransform();
	scale(baseScale, baseScale);
	if (_zoom < 100) {
		qreal s = 0.25 + ((_zoom / 100.0) * 0.75);
		scale(s, s);
	} else if (_zoom > 100) {
		qreal s = 1.0 + (_zoom - 100) * 0.015;
		scale(s, s);
	}
}