/*
 * 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 "EventProducer.hpp"
#include "TouchEvent.hpp"
#include "TouchThread.hpp"

#include <qapplication.h>
#include <qdesktopwidget.h>
#include <qevent.h>
#include <qmetatype.h>
#include <qobject.h>

#define USE_MOUSEHANDLER 1

namespace IrTouch
{

class EventHandler
{
public:
  EventHandler()
  {
  }

  virtual ~EventHandler()
  {
  }

  virtual void handleData(const IrTouch::TouchData &data) = 0;
};

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

#ifdef USE_MOUSEHANDLER
class MouseHandler: public EventHandler
{
  bool bFirstEvent;
  QPoint mPrevScreenPos;
  QWidget *mEnteredWidget;

public:
  MouseHandler() : EventHandler(), bFirstEvent(true), mEnteredWidget(0)
  {
    qDebug() << "MouseHandler::MouseHandler()";
  }

  void handleData(const IrTouch::TouchData &data)
  {
    if (data.rects().count() == 1 && data.guessedObjectType() == "one-finger")
    {
      // mouse press on first finger event, mouse move on every event following
      QEvent::Type type = bFirstEvent ? QEvent::MouseButtonPress : QEvent::MouseMove;
      bFirstEvent = false;

      //qDebug() << "mouse rect" << obj.associatedRects.first();

      // convert touch position to screen position
      QPoint screenPos = data.rects().values().first().center();

      if ((type == QEvent::MouseButtonPress))
      {
        // Remember widget at current screen position for all following mouse operations
        mEnteredWidget = QApplication::widgetAt(screenPos);
        if (!mEnteredWidget) // no widget, nothing to do
          return;

        QPoint widgetPos = mEnteredWidget->mapFromGlobal(screenPos);
        QMouseEvent pressEvent(type, widgetPos, screenPos,
            Qt::LeftButton, Qt::LeftButton, QApplication::keyboardModifiers());
        QApplication::sendEvent(mEnteredWidget, &pressEvent);

        mPrevScreenPos = screenPos;
      }
      else if ((type == QEvent::MouseMove) && mEnteredWidget)
      {
        QPoint posDiff = screenPos - mPrevScreenPos;
        //qDebug() << "posdiff" << posDiff << "; manhattan length" << posDiff.manhattanLength();
        // limit small movements, touch events are fluctuating too much
        if (posDiff.manhattanLength() > 1)
        {
          QPoint widgetPos = mEnteredWidget->mapFromGlobal(screenPos);
          // movement inside the widget that was touched
          QMouseEvent moveEvent(type, widgetPos, screenPos,
              Qt::NoButton, Qt::LeftButton, QApplication::keyboardModifiers());
          QApplication::sendEvent(mEnteredWidget, &moveEvent);

          mPrevScreenPos = screenPos;
        }
      }
    }
    else if (mEnteredWidget)
    { // mouse release on previously touched widget
      QPoint widgetPos = mEnteredWidget->mapFromGlobal(mPrevScreenPos);
      QMouseEvent me(QEvent::MouseButtonRelease, widgetPos, mPrevScreenPos,
          Qt::LeftButton, Qt::NoButton, QApplication::keyboardModifiers());
      QApplication::sendEvent(mEnteredWidget, &me);
      mEnteredWidget = 0L;
      mPrevScreenPos = QPoint();
      bFirstEvent = false;
    }
  }

}; // class MouseHandler
#endif //USE_MOUSEHANDLER

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


class TouchHandler: public EventHandler
{
  bool bFirstEvent;
  //QPoint mPrevScreenPos;
  QWidget *mEnteredWidget;

public:
  TouchHandler() :
    EventHandler(), bFirstEvent(true), mEnteredWidget(0)
  {
    qDebug() << "TouchHandler::TouchHandler()";
  }

  void handleData(const IrTouch::TouchData &data)
  {
    //qDebug() << "TouchHandler::handleData()";

    if (!mEnteredWidget)
    {
      if (data.isEmpty())
        return;
      mEnteredWidget = QApplication::widgetAt(data.boundingRect().center());
      if (!mEnteredWidget)
        return;

      qDebug() << "TouchEvent for entered widget" << mEnteredWidget;
      TouchEvent tev(data);
      QApplication::sendEvent(mEnteredWidget, &tev);
    }
    else
    {
      TouchEvent tev(data);
      QApplication::sendEvent(mEnteredWidget, &tev);

      if (data.rects().isEmpty())
      {
        qDebug() << "LAST TouchEvent for entered widget" << mEnteredWidget;
        mEnteredWidget = 0L;
      }
    }
  }
}; // class TouchHandler


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


EventProducer::EventProducer(QObject *parent) :
  QObject(parent)
{
  qDebug() << "EventProducer::EventProducer()";
  mCurrentHandler.reset(new TouchHandler());
  qDebug() << "EventProducer ready to roll";
}

EventProducer::~EventProducer()
{
  qDebug() << "EventProducer::~EventProducer()";
}

void EventProducer::buildEvents(const IrTouch::TouchData data)
{
  static QSize sDisplaySize = QApplication::desktop()->screenGeometry().size();
#ifdef USE_MOUSEHANDLER
  static bool sIsSingleTouch = false;
#endif

  //qDebug() << "EventProducer::buildEvents()" << "data:" << data;

  TouchData scaledData = data; // copy
  scaledData.scaleBy(sDisplaySize.width() / (qreal) (4096.0),
      sDisplaySize.height() / (qreal) (4096.0));

  if (mCurrentHandler.get())
    mCurrentHandler->handleData(scaledData);

#ifdef USE_MOUSEHANDLER
  bool bCurrIsSingleTouch = data.guessedObjectType() == "one-finger";
  if (sIsSingleTouch != bCurrIsSingleTouch)
  {
    mCurrentHandler.release();
    sIsSingleTouch = bCurrIsSingleTouch;

    qDebug() << "Switching event handler for touch events";

    if (sIsSingleTouch)
      mCurrentHandler.reset(new MouseHandler());
    else
      mCurrentHandler.reset(new TouchHandler());

    if (mCurrentHandler.get())
      mCurrentHandler->handleData(scaledData);
  }
#endif

}

} // namespace IrTouch

#include "moc_EventProducer.cpp"

