/*
 * Copyright 2009-2010  Stefan Gehn <stefan@srcbox.net>
 * 
 * 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 2 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 "PhotoScene.hpp"
#include "PhotoListView.hpp"
#include "PhotoDraggable.hpp"
#include "PhotoPage.hpp"
#include "TouchEvent.hpp"
#include "TrashDropWidget.hpp"

#include <qapplication.h>
#include <qdebug.h>
#include <qdir.h>

#include <qgraphicslinearlayout.h>
#include <qgraphicsgridlayout.h>
#include <qgraphicsproxywidget.h>
#include <qgraphicssceneevent.h>

#include <qpushbutton.h>

#include <qtpropertyanimation.h>

using namespace IrTouch;

// DIN-A4: 210mm × 297mm
#define PAGE_BASE_WIDTH 210
#define PAGE_BASE_HEIGHT 297

#define OB_MARGIN 8

#define SB_ICON_SIZE 128
#define SB_WIDTH (OB_MARGIN + SB_ICON_SIZE + OB_MARGIN)

#define TB_ICON_SIZE 48
#define TB_HEIGHT (OB_MARGIN + TB_ICON_SIZE + OB_MARGIN)

static QString locateBase;

static QString locateIcon(const QString &iconName)
{
  if (locateBase.isNull())
  {
    QDir locDir(QApplication::applicationDirPath());
    if (locDir.dirName() == "debug" || locDir.dirName() == "release")
    {
      locDir.cdUp();
      if (locDir.dirName() == "build")
        locDir.cdUp();
    }
    locateBase = locDir.absolutePath();
  }
  return locateBase + QDir::separator() + "icons" + QDir::separator() + iconName;
}

static void applyStyle(QWidget *w)
{
  if (w->inherits("QListView"))
  {
    w->setStyleSheet("border:1px solid #332; background-color:#554; color:white;");
  }
  else if (w->inherits("QPushButton"))
  {
    w->setStyleSheet(
        "QPushButton{border:1px solid #332; background-color:#554; color:white;}" \
        "QPushButton:pressed{border:1px solid #666; " \
        "background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 #dadbde, stop: 1 #f6f7fa); }");
  }
}

static QGraphicsRectItem *createTouchDebugItem(const QRectF &rect, quint32 id)
{
  QGraphicsRectItem *item = new QGraphicsRectItem();
  item->setPen(Qt::NoPen);
  item->setBrush(QColor(255, 0, 0, 85)); // red rectangle for multi touch
  item->setData(0, id);
  item->setRect(rect);
  return item;
}



PhotoScene::PhotoScene(QObject *parent, const QRectF &sceneRect) :
	QGraphicsScene(parent), mTouchDebugGroup(0),
	mCurrentLeftPage(0), mCurrentRightPage(0),
	mTwoFingerId1(0), mTwoFingerId2(0)
{
  setSceneRect(sceneRect);
  setItemIndexMethod(QGraphicsScene::NoIndex);
	setDisplayTouchDebug(false);

  //--- top-level widget in scene including grid-layout
	QGraphicsGridLayout *tlLayout = new QGraphicsGridLayout();
	tlLayout->setContentsMargins(0, 0, 0, 0);
	tlLayout->setSpacing(OB_MARGIN / 2.0);

	QGraphicsWidget *tlWidget = new QGraphicsWidget();
	tlWidget->setGeometry(sceneRect);
	tlWidget->setLayout(tlLayout);
  addItem(tlWidget);


  //----------------------------------------------------------------------
  //--- Left sidebar
	PhotoListView *photoLv = new PhotoListView();
	mPhotoLvProxy = addWidget(photoLv);
  tlLayout->addItem(mPhotoLvProxy, 1, 0);

	applyStyle(photoLv);
	photoLv->setViewMode(QListView::IconMode);
	photoLv->setMovement(QListView::Static);
	photoLv->setIconSize(QSize(SB_ICON_SIZE, SB_ICON_SIZE));
	photoLv->setSpacing(OB_MARGIN/2.0);
	photoLv->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
	photoLv->setMaximumWidth(SB_WIDTH);
	QObject::connect(photoLv, SIGNAL(photoDragStarted(QPoint, QString)),
      this, SLOT(createDraggable(QPoint, QString)));

  //----------------------------------------------------------------------
  //--- Central page-display
  mPageParent = new QGraphicsWidget(tlWidget);
  mPageParent->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
  // Layout for page-display
  mPageLayout = new QGraphicsGridLayout();
  // spacing between individual pages
  mPageLayout->setSpacing(0);
  // spacing around all pages
  mPageLayout->setContentsMargins(0, 0, 0, 0);
  mPageParent->setLayout(mPageLayout);
  tlLayout->addItem(mPageParent, 1, 1);

  //----------------------------------------------------------------------
  //--- Right sidebar
  mRightBar = new QGraphicsWidget(tlWidget);
  tlLayout->addItem(mRightBar, 1, 2);
  // Layout for right sidebar
  QGraphicsLinearLayout *rightBarLayout = new QGraphicsLinearLayout(Qt::Vertical, mRightBar);
  rightBarLayout->setContentsMargins(0, 0, 0, 0);

  QPushButton *btnAddPage = new QPushButton();
  applyStyle(btnAddPage);
	btnAddPage->setIcon(QIcon(locateIcon("page_add.png")));
  btnAddPage->setIconSize(QSize(SB_ICON_SIZE, SB_ICON_SIZE));
  connect(btnAddPage, SIGNAL(clicked()), this, SLOT(slotAddPage()));
  rightBarLayout->addItem(addWidget(btnAddPage));

  // stretch to align items at the bottom of the sidebar
  rightBarLayout->addStretch(1);

	mTrashWidget = new TrashDropWidget(mRightBar);
	mTrashWidget->setMinimumSize(SB_ICON_SIZE, SB_ICON_SIZE);
	mTrashWidget->setMaximumSize(SB_ICON_SIZE, SB_ICON_SIZE);
	mTrashWidget->setEnabled(false);
	connect(this, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged()));
	rightBarLayout->addItem(mTrashWidget);

	//----------------------------------------------------------------------


	//if (!loadScene("pages.tbp"))
  {
    // Add at least two empty pages at start
    while (mPages.count() < 2)
      addPage();
  }
  // Display page 0 and 1
  showPages(0);
  resetTouchDebugItems();
}

PhotoScene::~PhotoScene()
{
	//saveScene("pages.tbp");
}

bool PhotoScene::loadScene(const QString &fileName)
{
  QFile sceneFile(fileName);
  if (!sceneFile.open(QIODevice::ReadOnly))
    return false;
  qDebug() << "Loading scene";
  QDataStream sceneDataStream(&sceneFile);
  int pageCount;
  sceneDataStream >> pageCount;
  while (pageCount-- > 0)
  {
    PhotoPage *page = addPage();
    sceneDataStream >> *page;
  }
  qDebug() << "Loaded" << mPages.count() << "pages from" << fileName;
  return mPages.count() > 1;
}

bool PhotoScene::saveScene(const QString &fileName)
{
  QFile sceneFile(fileName);
  if (!sceneFile.open(QIODevice::WriteOnly))
    return false;
  qDebug() << "Saving scene";
  QDataStream sceneDataStream(&sceneFile);

  // store number of pages
  sceneDataStream << mPages.count();

  // store each page
  Q_FOREACH(PhotoPage *page, mPages)
  {
    sceneDataStream << *page;
  }

  qDebug() << "Saved" << mPages.count() << "pages to" << fileName;
  return true;
}

void PhotoScene::showPages(int leftPageIndex)
{
  qDebug() << "PhotoScene::showPages(" << leftPageIndex << ")";

  if (mCurrentLeftPage)
    mCurrentLeftPage->hide();

  if (mCurrentRightPage)
    mCurrentRightPage->hide();

  // ---
  if (leftPageIndex >= mPages.count())
  {
    qWarning() << "leftPageIndex too big:" << leftPageIndex << ">=" << mPages.count();
    return;
  }
  qDebug() << "displaying page " << leftPageIndex + 1 << "on left side";
  mCurrentLeftPage = mPages.at(leftPageIndex);
  mCurrentLeftPage->show();
  mPageLayout->addItem(mCurrentLeftPage, 0, 0);

  // ---
  int rightPageIndex = leftPageIndex + 1;
  if (rightPageIndex >= mPages.count())
  {
    qWarning() << "rightPageIndex too big:" << rightPageIndex << ">=" << mPages.count();
    return;
  }
  qDebug() << "displaying page " << rightPageIndex + 1 << "on right side";
  mCurrentRightPage = mPages.at(rightPageIndex);
  mCurrentRightPage->show();
  mPageLayout->addItem(mCurrentRightPage, 0, 1);
}

PhotoPage *PhotoScene::addPage()
{
	int pageIndex = mPages.count();
	qDebug() << "PhotoScene::addPage(), adding page" << pageIndex + 1;
	PhotoPage *page = new PhotoPage(mPageParent);
	// hide new pages by default, showPages() "fixes" the display accordingly
	page->hide();
	connect(page, SIGNAL(turnPage()), this, SLOT(slotTurnPage()));
	connect(page, SIGNAL(destroyed(QObject*)), this, SLOT(slotPageDestroyed(QObject*)));
	mPages.append(page);

	page->setPageIndex(pageIndex, mPages.count());
	if (pageIndex > 0) // korrigieren der vorigen seite
	{
		qDebug() << "Fixing page" << pageIndex << "after adding page" << pageIndex + 1;
		mPages.at(pageIndex - 1)->setPageIndex(pageIndex - 1, mPages.count());
	}

	qDebug() << "PhotoScene::addPage(); END";
	return page;
}

void PhotoScene::slotTurnPage()
{
	PhotoPage *page = qobject_cast<PhotoPage *>(sender());
	Q_ASSERT(page);
  if (page == mCurrentLeftPage)
    slotPrevPage();
  else if (page == mCurrentRightPage)
    slotNextPage();
}

void PhotoScene::slotPageDestroyed(QObject *obj)
{
	PhotoPage *page = qobject_cast<PhotoPage *>(obj);
	Q_ASSERT(page);
	qDebug() << "page destroyed: " << obj;

	int destroyedPageIndex = mPages.indexOf(page);
	int currLeftPageIndex = mPages.indexOf(mCurrentLeftPage);
	if (page == mCurrentLeftPage)
		mCurrentLeftPage = 0L;
	else if (page == mCurrentRightPage)
		mCurrentRightPage = 0L;

	qDebug() << "Removing destroyed page" << (destroyedPageIndex+1) << "from list of pages";
	mPages.removeAt(destroyedPageIndex);

	int pageCount = mPages.count();
	// Seitenzahlen aktualisieren, inklusive der Seite vor der gerade gelöschten.
	for (int i = qMin(destroyedPageIndex - 1, 0); i < pageCount; i++)
	{
		mPages.at(i)->setPageIndex(i, pageCount);
	}

	// Fix page display if one of the displayed pages was removed
	showPages(currLeftPageIndex);
}

void PhotoScene::slotSelectionChanged()
{
	mTrashWidget->setEnabled(!selectedItems().isEmpty());
}

void PhotoScene::createDraggable(const QPoint &lvPos, const QString &filePath)
{
  // Make sure we're not selecting or moving anything but the new draggable
  clearSelection();

  const QPointF scenePos = mPhotoLvProxy->mapToScene(lvPos);

  // create the draggable
  PhotoDraggable *dr = PhotoDraggable::createDraggable(filePath, scenePos);
  if (!dr) // invalid filePath
    return;
  // add draggable to scene
  addItem(dr);

	// FIXME: Ugly workaround to transfer mouse-grab from listview to draggabler
#if 1
  // "Fake" mouse release on listview
  QGraphicsSceneMouseEvent emulatedReleaseEvent(QEvent::GraphicsSceneMouseRelease);
  emulatedReleaseEvent.setScenePos(scenePos);
  emulatedReleaseEvent.setPos(scenePos);
  emulatedReleaseEvent.setButton(Qt::LeftButton);
  mouseReleaseEvent(&emulatedReleaseEvent);

  // Hack to forward all following mouse events to this newly created draggable
  //mPhotoLvProxy->installSceneEventFilter(dr);

  // Fake mouse press on PhotoDraggable
  QGraphicsSceneMouseEvent emulatedPressEvent(QEvent::GraphicsSceneMousePress);
  emulatedPressEvent.setScenePos(scenePos);
  emulatedPressEvent.setPos(scenePos);
  emulatedPressEvent.setButton(Qt::LeftButton);
  emulatedPressEvent.setButtons(Qt::LeftButton);
  mousePressEvent(&emulatedPressEvent);
#endif
}

void PhotoScene::slotPrevPage()
{
  qDebug() << "PhotoScene::slotPrevPage()";
	int idx = mPages.indexOf(mCurrentLeftPage);
	if (idx > 1)
		showPages(idx - 2);
  else
    qWarning() << "not enough pages to go to previous page";
}

void PhotoScene::slotAddPage()
{
  qDebug() << "PhotoScene::slotAddPage()";
	PhotoPage *page = addPage();
	int idx = page->pageIndex();
	if ((idx % 2) == 0) // left page
		showPages(idx);
	else if (idx - 1 >= 0) // right page added
		showPages(idx - 1);

}

void PhotoScene::slotNextPage()
{
  qDebug() << "PhotoScene::slotNextPage()";
	int idx = mPages.indexOf(mCurrentRightPage);
	if (idx + 1 < mPages.count())
		showPages(idx + 1);
  else
    qWarning() << "not enough pages to go to next page";
}

void PhotoScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
  updateMouseDebugItem(event->scenePos());
  QGraphicsScene::mouseMoveEvent(event);
}

void PhotoScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
  resetTouchDebugItems();
  updateMouseDebugItem(event->scenePos());
  QGraphicsScene::mousePressEvent(event);
}

void PhotoScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
  resetTouchDebugItems();
  QGraphicsScene::mouseReleaseEvent(event);
}

void PhotoScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event)
{
	if (event->button() == Qt::LeftButton)
	{
		PhotoDraggable *pd = photoDraggableAt(event->scenePos());
		if (pd)
		{
			togglePageFixation(pd, event->scenePos());
			event->accept();
			return;
		}
	}
  QGraphicsScene::mouseDoubleClickEvent(event);
}

bool PhotoScene::eventFilter(QObject *watched, QEvent *event)
{
  static QGraphicsRectItem *sideDebugItem = 0;

  Q_UNUSED(watched);
  if (event->type() != IrTouch::TouchEvent::eventType())
    return false;

	const TouchData &data = static_cast<IrTouch::TouchEvent *>(event)->data();
  //qDebug() << "Got TouchEvent with data:" << data;

  updateTouchDebugItems(data);

  //--- Select items based on touch events
	if (data.rects().isEmpty())
  {
    // No touch rectangles left, clear selection
    clearSelection();
  }
  else if (selectedItems().isEmpty())
  {
    // Set selection from current touch rectangles
    setTouchDebugVisible(false);
    Q_FOREACH(const TouchRect &trect, data.rects().values())
    { // For now this only selects PhotoDraggables
			PhotoDraggable *pd = photoDraggableAt(trect.center());
      if (!pd)
        continue;
      //qDebug() << "Selecting PhotoDraggable at " << trect.center();
      pd->setSelected(true);
    }
    setTouchDebugVisible(true);
  }
  //---

  if (data.guessedObjectType() == "vertical-hand-side")
  {
    if (mFirstSideRect.isNull())
    {
      mFirstSideRect = data.boundingRect();
    }
    else
    {
      QRectF unitedRect = mFirstSideRect.united(data.boundingRect());

      delete sideDebugItem;
      sideDebugItem = createTouchDebugItem(unitedRect, 0);
			sideDebugItem->setBrush(QColor(0, 200, 0, 20));
      addItem(sideDebugItem);

      if (mCurrentLeftPage)
      {
        QRectF leftRect = mCurrentLeftPage->sceneBoundingRect();
				if (unitedRect.intersected(leftRect).width() > (leftRect.width() * 0.60))
        {
          mCurrentLeftPage->clearPage();
          mFirstSideRect = QRect();
          delete sideDebugItem;
          sideDebugItem = 0;
        }
      }

      if (mCurrentRightPage)
      {
        QRectF rightRect = mCurrentRightPage->sceneBoundingRect();
				if (unitedRect.intersected(rightRect).width() > (rightRect.width() * 0.60))
        {
          mCurrentRightPage->clearPage();
          mFirstSideRect = QRect();
          delete sideDebugItem;
          sideDebugItem = 0;
        }
      }
    }
  }
  else
  {
    mFirstSideRect = QRect();
    delete sideDebugItem;
    sideDebugItem = 0;
  }

  //--- dual-touch scaling and rotation
  if (data.rects().size() == 2)
  {
    if (mTwoFingerId1 == 0 && mTwoFingerId2 == 0)
    { // no touch-rects remembered so far, save for following events
      mTwoFingerId1 = data.rects().values().at(0).id();
      mTwoFingerId2 = data.rects().values().at(1).id();
      mTwoFingerStamp.start(); // remember time of initial dual-touch
    }
    else if (data.rects().contains(mTwoFingerId1) && data.rects().contains(mTwoFingerId2))
    { // same touch-rects were found again
      handleDualTouch(data.rects().value(mTwoFingerId1), data.rects().value(mTwoFingerId2));
    }
    else
    { // previous touch rects not found, do nothing
      //mTwoFingerId1 = 0;
      //mTwoFingerId2 = 0;
    }
  }
  else
  { // more or less than two touches, forget everything
    mTwoFingerId1 = 0;
    mTwoFingerId2 = 0;
  }
  //---

  mLastData = data; // remember touchdata for comparing current and last data
  event->accept();
  return true;
}


void PhotoScene::handleDualTouch(const TouchRect &r1, const TouchRect &r2)
{
  if (!mLastData.rects().contains(r1.id()) || !mLastData.rects().contains(r2.id()))
    return;

  const TouchRect &prev_r1 = mLastData.rects().value(r1.id());
  const TouchRect &prev_r2 = mLastData.rects().value(r2.id());
  // line between center of previous touchrects
  QLineF oldL(prev_r1.center(), prev_r2.center());
  // line between center of current touchrects
  QLineF newL(r1.center(), r2.center());

  qreal lengthDiff = qAbs(newL.length() - oldL.length());
  qreal angleDiff = newL.angleTo(oldL);
  qreal scaleFactor = newL.length() / oldL.length();

	//FIXME: photo fixation should not be mixed with rotation/scaling!

  if (lengthDiff < 2 && (angleDiff < 0.4 || angleDiff > 359.6))
  { // almost no movement of fingers detected
    if (mTwoFingerStamp.isValid() && mTwoFingerStamp.elapsed() > 300)
    {
      setTouchDebugVisible(false);

			// find PhotoDraggable that is under both TouchRects
			PhotoDraggable *d1 = photoDraggableAt(r1.center());
			PhotoDraggable *d2 = photoDraggableAt(r2.center());
			if (d1 && (d1 == d2))
			{
				qDebug() << "No huge touch-movement detected, toggling fixation of PhotoDraggable";
				togglePageFixation(d1, r1.center());
				mTwoFingerStamp = QTime(); // invalidate
			}

			setTouchDebugVisible(true);
    }
  }
  else
  {
    if (mTwoFingerStamp.isValid())
      mTwoFingerStamp.start(); // reset "no-movement" timestamp
  }

	// Scale and rotate all currently selected items
  Q_FOREACH(QGraphicsItem *item, selectedItems())
  {
    PhotoDraggable *pd = dynamic_cast<PhotoDraggable *> (item);
    if (!pd)
      continue;
    pd->scaleAndRotate(scaleFactor, angleDiff);
  }
}

PhotoPage *PhotoScene::pageAt(const QPointF &scenePos) const
{
	// Find the page at scenePos
	QList<QGraphicsItem *> possiblePages = items(scenePos);
	Q_FOREACH(QGraphicsItem *item, possiblePages)
	{
		PhotoPage *page = dynamic_cast<PhotoPage *>(item);
		if (page)
			return page;
	}
	return 0L;
}

PhotoDraggable *PhotoScene::photoDraggableAt(const QPointF &scenePos)
{
	QGraphicsItem *item = itemAt(scenePos);
	if (!item)
		return 0;
	PhotoDraggable *dr;
	// check if topmost item is a photodraggable
	dr = dynamic_cast<PhotoDraggable *>(item);
	if (dr)
		return dr;
	// do the same for the parent (happens if point is on a childitem of our photodraggable)
	dr = dynamic_cast<PhotoDraggable *>(item->parentItem());
	if (dr)
		return dr;
	return 0;
}


void PhotoScene::togglePageFixation(PhotoDraggable *pd, const QPointF &scenePos)
{
  if (!pd)
    return;
	PhotoPage *page = pageAt(scenePos);
	if (!page)
		return;

	clearSelection();
	if (pd->parentItem())
	{
		page->removeDraggable(pd);
		pd->setEnabled(true);
		pd->setSelected(true);
	}
	else
	{
		page->addDraggable(pd);
		pd->setEnabled(false);
	}
}

static QGraphicsItem *findChildItem(const QGraphicsItem *parent, int key, const QVariant &val)
{
  Q_FOREACH(QGraphicsItem *item, parent->childItems())
  {
    if (item->data(key) == val)
      return item;
  }
  return 0L;
}

void PhotoScene::resetTouchDebugItems()
{
	delete mTouchDebugGroup;
	mTouchDebugGroup = new QGraphicsItemGroup(0L, this);
	mTouchDebugGroup->setZValue(5);
}

void PhotoScene::updateMouseDebugItem(const QPointF &pos)
{
	if (!mDisplayTouchDebug)
			return;

	QRectF rect(pos.x() - 8, pos.y() - 8, 16, 16);
  QGraphicsRectItem *debugItem = static_cast<QGraphicsRectItem *> (findChildItem(mTouchDebugGroup, 0, 0));
  if (!debugItem)
  {
    debugItem = createTouchDebugItem(rect, 0);
    debugItem->setBrush(QColor(0, 0, 255, 96)); // blue rectangle for mouse touch
    mTouchDebugGroup->addToGroup(debugItem);
  }
  else
  {
    debugItem->setRect(rect);
  }
}

void PhotoScene::updateTouchDebugItems(const IrTouch::TouchData &data)
{
	if (!mDisplayTouchDebug)
			return;

  QList<QGraphicsItem *> debugItemList = mTouchDebugGroup->childItems();
  QGraphicsRectItem *debugItem = 0L;

  // delete debug-items whose TouchRect has disappeared
  Q_FOREACH(QGraphicsItem *item, debugItemList)
  {
    if (!data.rects().contains(item->data(0).toUInt()))
      delete item;
  }

  // Add or update debug-items for all TouchRectangles
  Q_FOREACH(const TouchRect &trect, data.rects().values())
  {
    debugItem = static_cast<QGraphicsRectItem *> (findChildItem(mTouchDebugGroup, 0, trect.id()));
    if (debugItem)
      debugItem->setRect(trect);
    else
      mTouchDebugGroup->addToGroup(createTouchDebugItem(trect, trect.id()));
  }
}

void PhotoScene::setTouchDebugVisible(bool b)
{
  if (mTouchDebugGroup)
    mTouchDebugGroup->setVisible(b);
}

void PhotoScene::setDisplayTouchDebug(bool display)
{
	mDisplayTouchDebug = display;
	if (!display)
		resetTouchDebugItems();
}




#include "moc_PhotoScene.cpp"
