/***************************************************************************
 *   Copyright (C) 2004-2005 by David Saxton                               *
 *   david@bluehaze.org                                                    *
 *                                                                         *
 *   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.                                   *
 ***************************************************************************/

#ifndef MECHANICSITEM_H
#define MECHANICSITEM_H

#include <item.h>
#include <QList>

class LibraryItem;
class MechanicsItem;
// class MechanicsItemOverlayItem;
class MechanicsDocument;
typedef QList<MechanicsItem*> MechanicsItemList;

/**
@short Stores mass, moment of inertia
@author David Saxton
*/
class MechanicsInfo
{
public:
	MechanicsInfo();

	double mass; // Mass
	double momentOfInertia; // Moment of inertia
};

class CombinedMechanicsInfo : public MechanicsInfo
{
public:
	CombinedMechanicsInfo();
	CombinedMechanicsInfo( const MechanicsInfo &info );

	double x; // X coordinate of center of mass
	double y; // Y coordinate of center of mass
};

/**
@short Stores a position and orientation
@author David Saxton
*/
class PositionInfo
{
public:
	PositionInfo();
	/**
	 * Adds together two positions: for this=PARENT +(CHILD), the new position
	 * is formed by translating this position by that of the CHILDs
	 * translation, and then rotating everything about the center of this item
	 */
	const PositionInfo operator+( const PositionInfo &info );
	/**
	 * Not quite the inverse of operator+. Subtracts the given position info
	 * as if it was applied before this current info.
	 */
	const PositionInfo operator-( const PositionInfo &info );
	/**
	 * x position (0 is left)
	 */
	double x() const { return m_x; }
	/**
	 * y position (0 is top)
	 */
	double y() const { return m_y; }
	/**
	 * Angle in radians, positive direction is anticlockwise
	 */
	double angle() const { return m_angle; }
	/**
	 * Sets the x-position
	 */
	void setX( double x ) { m_x = x; }
	/**
	 * Sets the y-position
	 */
	void setY( double y ) { m_y = y; }
	/**
	 * Sets the angle
	 */
	void setAngle( double angle ) { m_angle = angle; }
	/**
	 * Adds (x,y) to the current position
	 */
	void translate( double dx, const double dy ) { m_x += dx; m_y += dy; }
	/**
	 * Rotates anticlockwise by the given amount (in radians)
	 */
	void rotate( double angle ) { m_angle += angle; }
	/**
	 * Resets the position to (0,0), and the orientation to 0
	 */
	void reset();
	/**
	 * Rotates the current position about the given point through the given
	 * angle in radians anticlockwise. This will change the position and
	 * orientation.
	 */
	void rotateAboutPoint( double x, double y, double angle );

protected:
	double m_x;
	double m_y;
	double m_angle;
};


/**
@author David Saxton
*/
class MechanicsItem : public Item
{
Q_OBJECT
public:
	MechanicsItem( MechanicsDocument *mechanicsDocument, bool newItem, const QString &id );
	~MechanicsItem() override;

	enum SelectionMode
	{
		sm_move,
		sm_resize,
		sm_rotate
	};
	/**
	 * Sets the selection mode (sm_resize or sm_rotate). Note that setSelected
	 * also needs to be called to select the item.
	 */
	void setSelectionMode( SelectionMode sm );
	void setSelected( bool yes ) override;
	/**
	 * @returns the selection mode
	 */
	SelectionMode selectionMode() const { return m_selectionMode; }
	/**
	 * Move the MechanicsItem by the given amount
	 */
	void moveBy( double dx, double dy ) override;
	/**
	 * Returns the absolute position on the canvas
	 */
	PositionInfo absolutePosition() const;
	/**
	 * Returns the position relative to the parent item (or the absolute
	 * position if there is no parent item)
	 */
	PositionInfo relativePosition() const { return m_relativePosition; }
	/**
	 * Returns the mechanics info for this item (so not taking into account that
	 * of attached children)
	 */
	MechanicsInfo *mechanicsInfo() { return &m_mechanicsInfo; }
	/**
	 * Returns the combined mechanics info for this item (which takes into
	 * account that of attached children).
	 */
	CombinedMechanicsInfo *mechanicsInfoCombined() { return &m_mechanicsInfoCombined; }
	/**
	 * Returns the rectangle that can legitimately fit inside the given bounding
	 * rectangle, given this items current rotation. Legitimately means that
	 * whether this item is allowed to be distorted, inverted, resized, etc.
	 */
	QRect maxInnerRectangle( const QRect &outerRect ) const;

	ItemData itemData() const override;

	bool mousePressEvent( const EventInfo &eventInfo ) override;
	bool mouseReleaseEvent( const EventInfo &eventInfo ) override;
	bool mouseDoubleClickEvent ( const EventInfo &eventInfo ) override;
	bool mouseMoveEvent( const EventInfo &eventInfo ) override;
	bool wheelEvent( const EventInfo &eventInfo ) override;
	void enterEvent(QEvent *) override;
	void leaveEvent(QEvent *) override;

public slots:
	/**
	 * Rotate the item by the given amount (in radians)
	 */
	void rotateBy( double dtheta );
	void parentMoved();

signals:
	/**
	 * Emitted when this item moves (translates or rotates)
	 */
	void moved();

protected slots:
	/**
	 * Recalculate the combined mechanics info (e.g. when mass is changed, or child added)
	 */
	void updateMechanicsInfoCombined();

protected:
	void reparented( Item *oldItem, Item *newItem ) override;
	void childAdded( Item *child ) override;
	void childRemoved( Item *child ) override;
	/**
	 * Called when this item is resized, so that sub classes can do whatever
	 */
	virtual void itemResized() {};
	/**
	 * Sets the correct orientation on the painter
	 */
	void initPainter( QPainter &p );
	/**
	 * *Must* be called after calling initPainter, if initPainter was called
	 */
	void deinitPainter( QPainter &p );
	void dataChanged() override;
	void itemPointsChanged() override { updateCanvasPoints(); }
	/**
	 * Calculates the setPoints required from the current m_itemPoints and the
	 * current position / angle
	 */
	void updateCanvasPoints();

	MechanicsDocument *p_mechanicsDocument;
	PositionInfo m_relativePosition; // Absolution position if not attached to a parent item, or otherwise relative to parent item
	MechanicsInfo m_mechanicsInfo;
	CombinedMechanicsInfo m_mechanicsInfoCombined;

private:
	SelectionMode m_selectionMode;
};

#endif
