// Copyright 2023, Algoryx Simulation AB.

#pragma once

// AGX Dynamics for Unreal includes.
#include "AGX_RealInterval.h"
#include "NativeBarrier.h"
#include "Utilities/DoubleInterval.h" /// @todo Use Unreal Engine double interval once they have it.

class FRigidBodyBarrier;
class FWireBarrier;
struct FWireWinchRef;

/*
 * The goal here is to tell the compiler that there is a template instantiation of
 * FNativeBarrier<FWireWinchRef> somewhere, without actually triggering an instantiation here and
 * now. The instantiation is instead done explicitly in the .cpp file. We do this because
 * FWireWinchRef is only declared, not defined, here in the header file since the type contains
 * AGX Dynamics types that can't be named outside the AGXUnrealBarrier module, and this header file
 * is included in other modules.
 *
 * We need to separate the Linux and Windows declarations because Linux must have the visibility
 * decorator here for the inherited member functions to be visible to users of FWireWinchBarrier,
 * while Visual Studio explicitly forbids it and instead require that the visibility decorator is on
 * the instantiation in the .cpp file instead.
 */
#if PLATFORM_LINUX
extern template class AGXUNREALBARRIER_API FNativeBarrier<FWireWinchRef>;
#elif PLATFORM_WINDOWS
extern template class FNativeBarrier<FWireWinchRef>;
#else
#pragma error("This platform is currently not supported.");
#endif


class AGXUNREALBARRIER_API FWireWinchBarrier : public FNativeBarrier<FWireWinchRef>
{
public:
	using Super = FNativeBarrier<FWireWinchRef>;

	FWireWinchBarrier();
	FWireWinchBarrier(std::unique_ptr<FWireWinchRef> Native);
	FWireWinchBarrier(FWireWinchBarrier&& Other);
	virtual ~FWireWinchBarrier();

	void AllocateNative(
		const FRigidBodyBarrier* Body, const FVector& LocalLocation, const FVector& LocalNormal,
		double PulledInLength);

	/// The body that the winch is attached to. Will be empty when attached to the world.
	FRigidBodyBarrier GetRigidBody() const;

/// @todo Don't know how to read this from the AGX Dynamics API.
#if 0
	/// The position of the winch on the body it's attached to, or in world space if there is no
	/// body.
	FVector GetLocalPosition() const;
#endif

	/**
	 * @return The direction of the winch on the body it's attached to, or in world space if there
	 * is no body.
	 */
	FVector GetNormal() const;

	/**
	 * @return The location of the winch in the body's frame, or world space if there is no body.
	 */
	FVector GetLocation() const;

	/*
	 * Set the length of wire that is held inside the winch. This will create new wire, not move
	 * free wire into the winch.
	 */
	void SetPulledInWireLength(double InPulledInLength);

	/**
	 * The length of wire that the winch contains currently.
	 * This will decrease during routing/initialization if Auto Feed is enabled.
	 */
	double GetPulledInWireLength() const;


	/**
	 * Decide if wire should be taken from the winch during routing, or if the routed wire is in
	 * addition to the the initial pulled in length. Only used during initialization.
	 * @param bAutoFeed True to take from the winch, false to add wire in addition to the winch.
	 */
	void SetAutoFeed(bool bAutoFeed);

	bool GetAutoFeed() const;

	/// Maximum force to push or pull the wire.
	FAGX_RealInterval GetForceRange() const;

	/**
	 * Set the maximum forces that the winch may use to haul in or pay out wire.
	 * The lower end of the range must be negative or zero and is the maximum force to haul in.
	 * The upper end of the range must be positive or zero and is the maximum force to pay out.
	 */
	void SetForceRange(const FAGX_RealInterval& InForceRange);

	void SetForceRange(double MinForce, double MaxForce);

	/// The ability of the winch to slow down the wire when the brake is enabled.
	FAGX_RealInterval GetBrakeForceRange() const;

	void SetBrakeForceRange(const FAGX_RealInterval& InBrakeForceRange);

	void SetBrakeForceRange(double MinForce, double MaxForce);

	/**
	 * The speed that the winch tries to haul in or pay out wire with.
	 * Positive values is paying out.
	 * Negative values is hauling in.
	 */
	double GetSpeed() const;

	/**
	 * Set the speed that the winch tries to haul in or pay out wire with.
	 * Positive values is paying out.
	 * Negative values is hauling in.
	 */
	void SetSpeed(double InTargetSpeed);

	/**
	 * The current speed of the winch motor.
	 * Positive values is paying out.
	 * Negative values is hauling in.
	 */
	double GetCurrentSpeed() const;

	/// \return The force that the motor is currently applying.
	double GetCurrentForce() const;

	/// \return The force that the brake is currently applying.
	double GetCurrentBrakeForce() const;

	bool HasWire() const;

// Not yet implemented.
#if 0
	FWireBarrier GetWire() const;
#endif

	FGuid GetGuid() const;

protected:
/// @todo Determine if we need to override these.
#if 0
	virtual void PreNativeChanged() override;
	virtual void PostNativeChanged() override;
#endif
};
