/////////////////////////////////////////////////////////////
// CINEMA 4D SDK                                           //
/////////////////////////////////////////////////////////////
// (c) MAXON Computer GmbH, all rights reserved            //
/////////////////////////////////////////////////////////////

#ifndef __C4D_SNAPDATA_H_
#define __C4D_SNAPDATA_H_

#include "ocamera.h"
#include "c4d_basedata.h"
#include "dmodeling.h"

class String;
class BaseList2D;
class BaseDocument;
class BaseDraw;
class ViewportSelect;
class AtomArray;
class BaseThread;
class BaseDrawHelp;
class BaseBitmap;
class BaseContainer;
class EditorWindow;
class BaseObject;

#define		ID_SNAPCORE_LIB	440000112

#define PLUGINFLAG_SNAP_INFERRED_POINT (1 << 27)	/// Inferred points can be generated by the results of this snap mode (used in registration)
#define PLUGINFLAG_SNAP_INFERRED_AXIS	 (1 << 26)	/// Inferred axis can be generated by the results of this snap mode (used in registration)
#define PLUGINFLAG_SNAP_MIDPOINT			 (1 << 25)	/// This snap mode is used by the midpoint snap, use the parent id to set which snap it belongs to


//----------------------------------------------------------------------------------------
/// Snap Priority
/// A higher priority snap will take precedence over a lower priority one when both are within snap range
//----------------------------------------------------------------------------------------
enum SNAPPRIORITY
{
	SNAPPRIORITY_0		 = 0,			/// The lowest priority
	SNAPPRIORITY_PLANE = 1000,	/// Planar snapping e.g. to the workplane or a polygon surface
	SNAPPRIORITY_GUIDE = 2000,	/// Guide snapping lower then edge to allow correct drawing for dynamic guides
	SNAPPRIORITY_EDGE	 = 3000,	/// Edge snapping is a slightly higher priority and will override planar snapping
	SNAPPRIORITY_POINT = 4000		/// Point snapping is the highest (default) snapping priority
};														// > and < needed so that needs to be left out ENUM_END_LIST(SNAPPRIORITY);

//----------------------------------------------------------------------------------------
/// Snap flags, sent during the snap process itself to limit snapping
//----------------------------------------------------------------------------------------
enum SNAPFLAGS
{
	SNAPFLAGS_0								= 0,				/// No flags
	SNAPFLAGS_IGNORE_SELECTED	=	(1 << 0),	/// Don't snap to the selected objects or components
	SNAPFLAGS_IGNORE_AXIS			=	(1 << 1),	/// Don't snap to the active axis
	SNAPFLAGS_POINTSONLY			= (1 << 2),	/// Ignore modes with lower priority than points
	SNAPFLAGS_SURFACEONLY			= (1 << 3)	/// Ignore modes with higher priority than planes
} ENUM_END_FLAGS(SNAPFLAGS);

//----------------------------------------------------------------------------------------
/// Inferred guide types.
//----------------------------------------------------------------------------------------
enum INFERREDGUIDETYPE
{
	INFERRED_GUIDE_POINT = 0,	/// An inferred point or "axis", this will create guides along all perpendicular lines to the point in x, y and z
	INFERRED_GUIDE_LINE	 = 1,	/// Inferred guide line, this will act just like a normal guide line, e.g. along an edge
	INFERRED_GUIDE_PLANE = 2	/// Inferred plane, this will act just like a normal guide plane
} ENUM_END_LIST(INFERREDGUIDETYPE);

//----------------------------------------------------------------------------------------
/// Structure for holding and returning information about the result of a snap
/// and the point that's been snapped to
//----------------------------------------------------------------------------------------
struct SnapPoint
{
public:
	SnapPoint() : target(nullptr), component(NOTOK) { }
	~SnapPoint() { }

	Matrix			mat;				///		The result position of the snap
	BaseList2D* target;			///		The target object being snapped to
	Int32				component;	///		The component ID being snapped to (e.g. polygon, edge, point, line point)
};

//----------------------------------------------------------------------------------------
/// The final result that the user gets when calling Snap::Snap or Snap::Intesect
/// user owns the struct
//----------------------------------------------------------------------------------------
struct SnapResult : public SnapPoint
{
public:
	SnapResult() : SnapPoint(), snapmode(NOTOK), initial_snap(Vector(0.0)) { }
	~SnapResult() { }

	Int32	 snapmode;			///		The snap mode used
	Vector initial_snap;	///		The snap porition before 2d processing
	Vector delta;					///		The delta between new and old point positions
};

//----------------------------------------------------------------------------------------
/// Structure containing relevant context information for the snap system
//----------------------------------------------------------------------------------------
struct SnapStruct
{
public:
	SnapStruct() : doc(nullptr), bd(nullptr), snap_radius(10.0), excluded_objects(nullptr), object_list(nullptr), bt(nullptr), flags(SNAPFLAGS_0), projection(Pperspective) { }
	~SnapStruct() { }

	BaseDocument* doc;							///	The current document
	BaseDraw*			bd;								/// The active BaseDraw that snapping will be taking place in
	Float					snap_radius;			/// The radius to snap within
	AtomArray*		excluded_objects;	/// A list of objects to exclude
	AtomArray*		object_list;			/// A list of objects in view
	BaseThread*		bt;								/// The current BaseThread
	SNAPFLAGS			flags;						/// Flags sent by user calling snap function
	Int32					projection;				/// cached bd->GetProjection() but faster
};

//----------------------------------------------------------------------------------------
/// The base virtual class for Snap mode plugins
/// For example Polygon Snap, Edge Snap, Spline Snap, Vertex Snap and so on
//----------------------------------------------------------------------------------------
class SnapData : public BaseData
{
public:
	//----------------------------------------------------------------------------------------
	/// Initialize the snap plugin here.
	/// @return				True for success of initializing the plugin.
	//----------------------------------------------------------------------------------------
	virtual Bool Init(void) { return true; }

	//----------------------------------------------------------------------------------------
	/// Free the snap plugin instance here
	virtual void Free(void) { }

	//----------------------------------------------------------------------------------------
	/// Prepare for snapping to begin.
	/// @param[in] ss		The passed Snap context structure.
	/// @return						true on Success of initializing all snap structures needed.
	//----------------------------------------------------------------------------------------
	virtual Bool InitSnap(const SnapStruct& ss) { return true; }

	//----------------------------------------------------------------------------------------
	/// 3D Snap detection routine.
	/// @param[in] p					The position being snapped to in world coordinates.
	/// @param[in] ss					The passed Snap context structure.
	/// @param[out] result		Contains data about the resulting snap point if one is generated.
	/// @return								True if a snap occurred, false if not.
	//----------------------------------------------------------------------------------------
	virtual Bool Snap(const Vector& p, const SnapStruct& ss, SnapPoint& result) { return false; }

	//----------------------------------------------------------------------------------------
	/// Guide intersection routine.
	/// @param[in] p						The guide normal in world coordinates.
	/// @param[in] n						The guide normal in world coordinates.
	/// @param[in] plane				True if the guide is a plane, false if it's a ray/line.
	/// @param[in] ss						The passed Snap context structure.
	/// @param[out] result			Contains data about the resulting intersection points if they're generated.
	/// @return									True if an intersection occurred, false if not.
	//----------------------------------------------------------------------------------------
	virtual Bool Intersect(const Vector& p, const Vector& n, Bool plane, const SnapStruct& ss, SnapPoint& result){ return false; }

	//----------------------------------------------------------------------------------------
	/// Free any stored data after snapping has occurred
	/// @param [in] ss				The passed Snap context structure.
	//----------------------------------------------------------------------------------------
	virtual void FreeSnap(const SnapStruct& ss) { }

	//----------------------------------------------------------------------------------------
	/// Snap draw routine.
	/// @param[in] ss					The passed Snap context structure.
	/// @param[in] doc				The current Document Passed by the tool.
	/// @param[in] bd					The current BaseDraw passed by the tool.
	/// @param[in] bh					The current BaseDrawHelp passed by the tool.
	/// @param[in] bt					The current BaseThread passed by the tool.
	/// @return								True success of drawing
	//----------------------------------------------------------------------------------------
	virtual Bool Draw(const SnapStruct& ss, BaseDocument* doc, BaseDraw* bd, BaseDrawHelp* bh, BaseThread* bt) { return true; }

	//----------------------------------------------------------------------------------------
	/// Intercepting the GetCursorInfo purely for the snap.
	/// @param[in] ss						The passed Snap context structure.
	/// @param[in] doc					The current Document Passed by the tool.
	/// @param[in] bd						The current BaseDraw passed by the tool.
	/// @param[in] x						Mouse x position passed by the tool.
	/// @param[in] y						Mouse x position passed by the tool.
	/// @param[out] result			The message container passed by the tool.
	/// @return									True if you need to override the active tool otherwise false.
	//----------------------------------------------------------------------------------------
	virtual Bool GetCursorInfo(const SnapStruct& ss, BaseDocument* doc, BaseDraw* bd, Float x, Float y, BaseContainer& result) { return false; }

	//----------------------------------------------------------------------------------------
	/// Intercepting the MouseInput purely for the snap.
	/// @param[in] ss						The passed Snap context structure.
	/// @param[in] doc					The current Document Passed by the tool.
	/// @param[in] bd						The current BaseDraw passed by the tool.
	/// @param[in] win					The current EditorWindow passed by the tool.
	/// @param[in] msg					The message container passed by the tool.
	/// @return									True if you need to override the active tool otherwise false.
	//----------------------------------------------------------------------------------------
	virtual Bool MouseInput(const SnapStruct& ss, BaseDocument* doc, BaseDraw* bd, EditorWindow* win, const BaseContainer& msg){ return false; }

	//----------------------------------------------------------------------------------------
	/// Intercepting the KeyboardInput purely for the snap.
	/// @param[in] ss						The passed Snap context structure.
	/// @param[in] doc					The current Document Passed by the tool.
	/// @param[in] bd						The current BaseDraw passed by the tool.
	/// @param[in] win					The current EditorWindow passed by the tool.
	/// @param[in] msg					The message container passed by the tool.
	/// @return									True if you need to override the active tool otherwise false.
	//----------------------------------------------------------------------------------------
	virtual Bool KeyboardInput(const SnapStruct& ss, BaseDocument* doc, BaseDraw* bd, EditorWindow* win, const BaseContainer& msg) { return false; }
};

//----------------------------------------------------------------------------------------
/// The Snap Interface, must be allocated either with AutoAlloc or Alloc and freed after use with Free
//----------------------------------------------------------------------------------------
class SnapCore
{
private:
	SnapCore(void) { }

public:
	//----------------------------------------------------------------------------------------
	/// Snap Initialization routine.
	/// @param[in] doc						The current document.
	/// @param[in] bd							The BaseDraw to initalize.
	/// @param[in] exclude				An array of objects to exclude from being snapped to.
	/// @return										True on success of initializing the snap.
	//----------------------------------------------------------------------------------------
	Bool Init(BaseDocument* doc, BaseDraw* bd, AtomArray* exclude = nullptr);

	//----------------------------------------------------------------------------------------
	/// Updates the snaps to use the current settings.
	/// @return										True on success of updating.
	//----------------------------------------------------------------------------------------
	Bool Update(void);

	//----------------------------------------------------------------------------------------
	/// Snap Initialization routine.
	/// @param[in] p							The position to snap to in global space.
	/// @param[out] result				The position to snap to in global space.
	/// @param[in] flags					The flags to limit snapping on selection or to a spcific type.
	/// @return										True on success of finding a snap point, false if no point was found in range to snap to.
	//----------------------------------------------------------------------------------------
	Bool Snap(const Vector& p, SnapResult& result, SNAPFLAGS flags = SNAPFLAGS_0);

	//----------------------------------------------------------------------------------------
	/// Snap Initialization routine.
	/// @param[in] p							The position to snap to in global space.
	/// @param[in] n							The normal of the guide being intersected with in global space.
	/// @param[in] plane					True if the intersection should be with a plane, false if with a ray/line.
	/// @param[out] result				The result of the successful intersection, user owns this.
	/// @param[in] flags					The flags to limit snapping on selection or to a spcific type.
	/// @return										True on success of finding an intersection, false if no intersection was found in range.
	//----------------------------------------------------------------------------------------
	Bool Intersect(const Vector& p, const Vector& n, Bool plane, SnapResult& result, SNAPFLAGS flags = SNAPFLAGS_0);

	//----------------------------------------------------------------------------------------
	/// Adds an inferred guide point, line or plane to the scene for snapping to.
	/// @param[in] doc						The current document.
	/// @param[in] mat						The matrix for the point (axis), line, or plane, z points along the line or as the normal for the plane.
	/// @param[in] type						The type of inferred guide to add.
	/// @return										True on success of adding the guide, false for a memory error attempting to do so.
	//----------------------------------------------------------------------------------------
	BaseObject* AddInferred(BaseDocument* doc, const Matrix& mat, INFERREDGUIDETYPE type);

	//----------------------------------------------------------------------------------------
	/// Flush Inferred System all guides and inferred elements will be deleted, should be called at mouse release.
	/// @return										True on success of removing all inferred guides.
	//----------------------------------------------------------------------------------------
	Bool FlushInferred(void);

	//----------------------------------------------------------------------------------------
	/// Recive the active object and pass it to SnapHook if is created from current tool important for tool guide.
	/// @param[in] op							The new Object created in the tool.
	//----------------------------------------------------------------------------------------
	void SetToolObject(BaseObject* op);

	//----------------------------------------------------------------------------------------
	/// Standard allocation routine.
	/// @return										Instance of SnapCore.
	//----------------------------------------------------------------------------------------
	static SnapCore* Alloc(void);

	//----------------------------------------------------------------------------------------
	/// Standard freeing routine.
	/// @param[in] &p							The Snap core to Free.
	//----------------------------------------------------------------------------------------
	static void Free(SnapCore*& p);
};

//----------------------------------------------------------------------------------------
/// Test if a snap mode is enabled or not.
/// @param[in] doc							The document you wish to test within.
/// @param[in] mode							The snapmode's ID to test for.
/// @return											True if the snap is enabled, false if not.
//----------------------------------------------------------------------------------------
Bool IsSnapEnabled(BaseDocument* doc, Int32 mode = NOTOK);

//----------------------------------------------------------------------------------------
/// Set the snap enabled status for a particular mode.
/// @param[in] state						The state to use true for enabled false for disabled.
/// @param[in] doc							The document to set the snapmode state for.
/// @param[in] mode							The snapmode to set the enabled state for.
//----------------------------------------------------------------------------------------
void EnableSnap(Bool state, BaseDocument* doc, Int32 mode = NOTOK);

//----------------------------------------------------------------------------------------
/// Routine to get the snap settings for the current document.
/// @param[in] doc							The document who's snap settings you wish to retrieve.
/// @param[in] snapmode					Optionally pick a specific snap mode to get or modify it's specific settings.
/// @return											A copy of the settings in a BaseContainer form.
//----------------------------------------------------------------------------------------
BaseContainer SnapSettings(BaseDocument* doc, Int32 snapmode = NOTOK);

//----------------------------------------------------------------------------------------
/// Routine to set the snap settings for the current mode/document.
/// @param[in] doc							The document you wish to set the snap settings for.
/// @param[in] bc								The BaseContainer with the settings for this document and/or snapmode.
/// @param[in] snapmode					Optionally specify which snapmode you wish to set the settings for instead of the global settings.
//----------------------------------------------------------------------------------------
void SnapSettings(BaseDocument* doc, const BaseContainer& bc, Int32 snapmode = NOTOK);

//----------------------------------------------------------------------------------------
/// Test if quantizing is enabled or not.
/// @param[in] doc							The document you wish to test within.
/// @return											True if quantizing is enabled, false if not.
//----------------------------------------------------------------------------------------
Bool IsQuantizeEnabled(BaseDocument* doc);

//----------------------------------------------------------------------------------------
/// Retrieve the quantize step values from QUANTIZE_MOVE, QUANTIZE_SCALE etc.
/// @param[in] doc							The document you wish to retrieve parameters for.
/// @param[in] bd								The viewport that you want to retrieve the correct QUANTIZE_MOVE value for, not relevant for other quantize modes.
/// @param[in] quantize_mode		The mode you want to retrieve the value for, e.g. QUANTIZE_ROTATE, QUANTIZE_MOVE or QUANTIZE_SCALE.
/// @return											The real step value.
//----------------------------------------------------------------------------------------
Float QuantizeStep(BaseDocument* doc, BaseDraw* bd, Int32 quantize_mode);

//----------------------------------------------------------------------------------------
/// Sets the quantize step values for QUANTIZE_MOVE, QUANTIZE_SCALE etc.
/// @param[in] doc							The document you wish to set parameters for.
/// @param[in] bd								The viewport that you want to set the correct QUANTIZE_MOVE value for, not relevant for other quantize modes.
/// @param[in] quantize_mode		The mode you want to set the value for, e.g. QUANTIZE_ROTATE, QUANTIZE_MOVE or QUANTIZE_SCALE.
/// @param[in] val							The value that you want to set the quantizing step size to.
//----------------------------------------------------------------------------------------
void QuantizeStep(BaseDocument* doc, BaseDraw* bd, Int32 quantize_mode, Float val);

//----------------------------------------------------------------------------------------
/// Retrieve the viewport workplane and orientation.
/// @param[in] bd								The viewport you want to get the workplane's matrix from.
/// @param[out] mg							A passed Matrix used to retrieve the correct workplane matrix, caller owns, pass nullptr to skip.
/// @param[out] scale						A passed Vector used to retrieve the correct workplane scale, called owns, pass nullptr to skip.
/// @param[out] op							Will be filled with the workplane object itself, called owns, pass nullptr to skip.
/// @return											True on success of retrieving the workplane and it's data.
//----------------------------------------------------------------------------------------
Bool GetConstructionPlane(BaseDraw* bd, Matrix* mg, Vector* scale, BaseObject** op);

//----------------------------------------------------------------------------------------
/// Retrieve the workplane object for the document.
/// @param[in] doc							The document to retrieve the workplane from.
/// @return											The workplane object.
//----------------------------------------------------------------------------------------
BaseObject* GetWorkplaneObject(BaseDocument* doc);

//----------------------------------------------------------------------------------------
/// Get the current workplane locked status.
/// @param[in] doc							The BaseDocument that the workplane belongs to.
/// @return											True if the workplane is locked
//----------------------------------------------------------------------------------------
Bool WorkplaneLock(BaseDocument* doc);

//----------------------------------------------------------------------------------------
/// Set the current workplane locked status.
/// @param[in] bd								The Viewport that to lock the workplane within.
/// @param[in] locked						Whether to lock or unlock the workplane.
//----------------------------------------------------------------------------------------
void WorkplaneLock(BaseDraw* bd, Bool locked);

//----------------------------------------------------------------------------------------
/// Get the current workplane Matrix.
/// @param[in] doc							The BaseDocument that the workplane belongs to.
/// @param[in] bd								The viewport you want to get the workplane's matrix from, if nullptr the locked matrix is returned indipendently form view.
/// @return The workplane matrix.
//----------------------------------------------------------------------------------------
Matrix GetWorkplaneMatrix(BaseDocument* doc, BaseDraw* bd);

//----------------------------------------------------------------------------------------
/// Snap registration routines.
//----------------------------------------------------------------------------------------

//----------------------------------------------------------------------------------------
/// Snap allocator type, used purely in RegisterSnapPlugin.
//----------------------------------------------------------------------------------------
typedef SnapData* SnapDataAllocator (void);

//----------------------------------------------------------------------------------------
/// Snap registration.
/// @param[in] id								The unique identifier for this plugin, should be registered on plugincafe.com.
/// @param[in] str							The name for the snap mode, normally for localization you should use GeLoadString(ID) using c4d_symbols and the strings files in your resource folder.
/// @param[in] help							A small block of information that will appear in the interface to explain how the snap mode operates.
/// @param[in] snapinfo					Standard registration flags, this includes PLUGINFLAG_SNAP_INFERRED_POINT and PLUGINFLAG_SNAP_INFERRED_AXIS.
/// @param[in] npalloc					The allocator, create a static SnapPlugin::Alloc() { return NewObjClear(MySnapMode); } method in your class, then set this to MySnapMode::Alloc.
/// @param[in] icon							A bitmap icon for the snap mode.
/// @param[in] priority					The priority of the snap mode, a higher priority will take precedent over other snaps within the radius.
/// @param[in] parent_mode			An ID for a parent snap mode, this mode then inherits that modes enabled/disabled state.
/// @return true on success of registering the new snap mode plugin.
//----------------------------------------------------------------------------------------
Bool RegisterSnapPlugin(Int32 id, const String& str, const String& help, Int32 snapinfo, SnapDataAllocator* npalloc, BaseBitmap* icon, SNAPPRIORITY priority = SNAPPRIORITY_EDGE, Int32 parent_mode = NOTOK);

#endif
