/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef InputData_h__
#define InputData_h__

#include "nsGUIEvent.h"
#include "nsDOMTouchEvent.h"
#include "nsDebug.h"

namespace mozilla {


enum InputType
{
  MULTITOUCH_INPUT,
  PINCHGESTURE_INPUT,
  TAPGESTURE_INPUT
};

class MultiTouchInput;
class PinchGestureInput;
class TapGestureInput;

// This looks unnecessary now, but as we add more and more classes that derive
// from InputType (eventually probably almost as many as nsGUIEvent.h has), it
// will be more and more clear what's going on with a macro that shortens the
// definition of the RTTI functions.
#define INPUTDATA_AS_CHILD_TYPE(type, enumID) \
  const type& As##type() const \
  { \
    NS_ABORT_IF_FALSE(mInputType == enumID, "Invalid cast of InputData."); \
    return (const type&) *this; \
  }

/** Base input data class. Should never be instantiated. */
class InputData
{
public:
  InputType mInputType;
  // Time in milliseconds that this data is relevant to. This only really
  // matters when this data is used as an event. We use uint32_t instead of
  // TimeStamp because it is easier to convert from nsInputEvent. The time is
  // platform-specific but it in the case of B2G and Fennec it is since startup.
  uint32_t mTime;

  INPUTDATA_AS_CHILD_TYPE(MultiTouchInput, MULTITOUCH_INPUT)
  INPUTDATA_AS_CHILD_TYPE(PinchGestureInput, PINCHGESTURE_INPUT)
  INPUTDATA_AS_CHILD_TYPE(TapGestureInput, TAPGESTURE_INPUT)

protected:
  InputData(InputType aInputType, uint32_t aTime)
    : mInputType(aInputType),
      mTime(aTime)
  {


  }
};

/**
 * Data container for a single touch input. Similar to nsDOMTouch, but used in
 * off-main-thread situations. This is more for just storing touch data, whereas
 * nsDOMTouch derives from nsIDOMTouch so it is more useful for dispatching
 * through the DOM (which can only happen on the main thread). nsDOMTouch also
 * bears the problem of storing pointers to nsIWidget instances which can only
 * be used on the main thread, so if instead we used nsDOMTouch and ever set
 * these pointers off-main-thread, Bad Things Can Happen(tm).
 *
 * Note that this doesn't inherit from InputData because this itself is not an
 * event. It is only a container/struct that should have any number of instances
 * within a MultiTouchInput.
 *
 * fixme/bug 775746: Make nsDOMTouch inherit from this class.
 */
class SingleTouchData
{
public:
  SingleTouchData(int32_t aIdentifier,
                  nsIntPoint aScreenPoint,
                  nsIntPoint aRadius,
                  float aRotationAngle,
                  float aForce)
    : mIdentifier(aIdentifier),
      mScreenPoint(aScreenPoint),
      mRadius(aRadius),
      mRotationAngle(aRotationAngle),
      mForce(aForce)
  {


  }

  // A unique number assigned to each SingleTouchData within a MultiTouchInput so
  // that they can be easily distinguished when handling a touch start/move/end.
  int32_t mIdentifier;

  // Point on the screen that the touch hit, in device pixels. They are
  // coordinates on the screen.
  nsIntPoint mScreenPoint;

  // Radius that the touch covers, i.e. if you're using your thumb it will
  // probably be larger than using your pinky, even with the same force.
  // Radius can be different along x and y. For example, if you press down with
  // your entire finger vertically, the y radius will be much larger than the x
  // radius.
  nsIntPoint mRadius;

  float mRotationAngle;

  // How hard the screen is being pressed.
  float mForce;
};

/**
 * Similar to nsTouchEvent, but for use off-main-thread. Also only stores a
 * screen touch point instead of the many different coordinate spaces nsTouchEvent
 * stores its touch point in. This includes a way to initialize itself from an
 * nsTouchEvent by copying all relevant data over. Note that this copying from
 * nsTouchEvent functionality can only be used on the main thread.
 *
 * Stores an array of SingleTouchData.
 */
class MultiTouchInput : public InputData
{
public:
  enum MultiTouchType
  {
    MULTITOUCH_START,
    MULTITOUCH_MOVE,
    MULTITOUCH_END,
    MULTITOUCH_ENTER,
    MULTITOUCH_LEAVE,
    MULTITOUCH_CANCEL
  };

  MultiTouchInput(MultiTouchType aType, uint32_t aTime)
    : InputData(MULTITOUCH_INPUT, aTime),
      mType(aType)
  {


  }

  MultiTouchInput(const nsTouchEvent& aTouchEvent)
    : InputData(MULTITOUCH_INPUT, aTouchEvent.time)
  {
    NS_ABORT_IF_FALSE(NS_IsMainThread(),
                      "Can only copy from nsTouchEvent on main thread");

    switch (aTouchEvent.message) {
      case NS_TOUCH_START:
        mType = MULTITOUCH_START;
        break;
      case NS_TOUCH_MOVE:
        mType = MULTITOUCH_MOVE;
        break;
      case NS_TOUCH_END:
        mType = MULTITOUCH_END;
        break;
      case NS_TOUCH_ENTER:
        mType = MULTITOUCH_ENTER;
        break;
      case NS_TOUCH_LEAVE:
        mType = MULTITOUCH_LEAVE;
        break;
      case NS_TOUCH_CANCEL:
        mType = MULTITOUCH_CANCEL;
        break;
      default:
        NS_WARNING("Did not assign a type to a MultiTouchInput");
        break;
    }

    for (size_t i = 0; i < aTouchEvent.touches.Length(); i++) {
      nsDOMTouch* domTouch = (nsDOMTouch*)(aTouchEvent.touches[i].get());

      // Extract data from weird interfaces.
      int32_t identifier, radiusX, radiusY;
      float rotationAngle, force;
      domTouch->GetIdentifier(&identifier);
      domTouch->GetRadiusX(&radiusX);
      domTouch->GetRadiusY(&radiusY);
      domTouch->GetRotationAngle(&rotationAngle);
      domTouch->GetForce(&force);

      SingleTouchData data(identifier,
                           domTouch->mRefPoint,
                           nsIntPoint(radiusX, radiusY),
                           rotationAngle,
                           force);

      mTouches.AppendElement(data);
    }
  }

  // This conversion from nsMouseEvent to MultiTouchInput is needed because on
  // the B2G emulator we can only receive mouse events, but we need to be able
  // to pan correctly. To do this, we convert the events into a format that the
  // panning code can handle. This code is very limited and only supports
  // SingleTouchData. It also sends garbage for the identifier, radius, force
  // and rotation angle.
  MultiTouchInput(const nsMouseEvent& aMouseEvent)
    : InputData(MULTITOUCH_INPUT, aMouseEvent.time)
  {
    NS_ABORT_IF_FALSE(NS_IsMainThread(),
                      "Can only copy from nsMouseEvent on main thread");
    switch (aMouseEvent.message) {
    case NS_MOUSE_BUTTON_DOWN:
      mType = MULTITOUCH_START;
      break;
    case NS_MOUSE_MOVE:
      mType = MULTITOUCH_MOVE;
      break;
    case NS_MOUSE_BUTTON_UP:
      mType = MULTITOUCH_END;
      break;
    // The mouse pointer has been interrupted in an implementation-specific
    // manner, such as a synchronous event or action cancelling the touch, or a
    // touch point leaving the document window and going into a non-document
    // area capable of handling user interactions.
    case NS_MOUSE_EXIT:
      mType = MULTITOUCH_CANCEL;
      break;
    default:
      NS_WARNING("Did not assign a type to a MultiTouchInput");
      break;
    }

    mTouches.AppendElement(SingleTouchData(0,
                                           aMouseEvent.refPoint,
                                           nsIntPoint(1, 1),
                                           180.0f,
                                           1.0f));
  }

  MultiTouchType mType;
  nsTArray<SingleTouchData> mTouches;
};

/**
 * Encapsulation class for pinch events. In general, these will be generated by
 * a gesture listener by looking at SingleTouchData/MultiTouchInput instances and
 * determining whether or not the user was trying to do a gesture.
 */
class PinchGestureInput : public InputData
{
public:
  enum PinchGestureType
  {
    PINCHGESTURE_START,
    PINCHGESTURE_SCALE,
    PINCHGESTURE_END
  };

  PinchGestureInput(PinchGestureType aType,
                    uint32_t aTime,
                    const nsIntPoint& aFocusPoint,
                    float aCurrentSpan,
                    float aPreviousSpan)
    : InputData(PINCHGESTURE_INPUT, aTime),
      mType(aType),
      mFocusPoint(aFocusPoint),
      mCurrentSpan(aCurrentSpan),
      mPreviousSpan(aPreviousSpan)
  {


  }

  PinchGestureType mType;

  // Center point of the pinch gesture. That is, if there are two fingers on the
  // screen, it is their midpoint. In the case of more than two fingers, the
  // point is implementation-specific, but can for example be the midpoint
  // between the very first and very last touch. This is in device pixels and
  // are the coordinates on the screen of this midpoint.
  nsIntPoint mFocusPoint;

  // The distance in device pixels (though as a float for increased precision
  // and because it is the distance along both the x and y axis) between the
  // touches responsible for the pinch gesture.
  float mCurrentSpan;

  // The previous |mCurrentSpan| in the PinchGestureInput preceding this one.
  // This is only really relevant during a PINCHGESTURE_SCALE because when it is
  // of this type then there must have been a history of spans.
  float mPreviousSpan;
};

/**
 * Encapsulation class for tap events. In general, these will be generated by
 * a gesture listener by looking at SingleTouchData/MultiTouchInput instances and
 * determining whether or not the user was trying to do a gesture.
 */
class TapGestureInput : public InputData
{
public:
  enum TapGestureType
  {
    TAPGESTURE_LONG,
    TAPGESTURE_UP,
    TAPGESTURE_CONFIRMED,
    TAPGESTURE_DOUBLE,
    TAPGESTURE_CANCEL
  };

  TapGestureInput(TapGestureType aType, uint32_t aTime, const nsIntPoint& aPoint)
    : InputData(TAPGESTURE_INPUT, aTime),
      mType(aType),
      mPoint(aPoint)
  {


  }

  TapGestureType mType;
  nsIntPoint mPoint;
};

}

#endif // InputData_h__
