using UnityEngine;

namespace pdinklag.CPW.Common
{
    /// <summary>
    /// Represents a wrestler.
    /// </summary>
    public interface IWrestler
    {
        /// <summary>
        /// The wrestler's reach.
        ///
        /// The reach is the sum of upper and lower arm lengths plus the wrist radius.
        /// </summary>
        float Reach { get; }

        /// <summary>
        /// The wrestler's weight in kilograms.
        /// </summary>
        float Weight { get; }

        /// <summary>
        /// The wrestler's height in meters.
        /// </summary>
        float Height { get; }

        /// <summary>
        /// The radius of the wrestler's main hit capsule.
        /// </summary>
        float CollisionRadius { get; }

        /// <summary>
        /// The maximum XZ distance the wrestler is allowed to jump.
        /// </summary>
        float MaxJumpDistance { get; }

        /// <summary>
        /// The wrestler's current base position.
        /// </summary>
        Vector3 Position { get; set; }

        /// <summary>
        /// The wrestler's current base rotation.
        /// </summary>
        Quaternion Rotation { get; set; }

        /// <summary>
        /// Predicts the wrestler's position based on their current position and velocity.
        /// </summary>
        /// <param name="lookAhead">the time, in seconds, to look ahead</param>
        /// <returns>the predicted position</returns>
        Vector3 PredictPosition(float lookAhead);

        /// <summary>
        /// The wrestler's normalized forward direction.
        /// </summary>
        Vector3 Forward { get; set; }

        /// <summary>
        /// The wrestler's normalized back direction.
        /// </summary>
        Vector3 Back { get; }

        /// <summary>
        /// The wrestler's normalized left hand direction.
        /// </summary>
        Vector3 Left { get; }

        /// <summary>
        /// The wrestler's normalized right hand direction.
        /// </summary>
        Vector3 Right { get; }

        /// <summary>
        /// The wrestler's normalized up direction.
        /// </summary>
        Vector3 Up { get; }

        /// <summary>
        /// The wrestler's normalized down direction.
        /// </summary>
        Vector3 Down { get; }

        /// <summary>
        /// The wrestler's current movement speed coefficient (or factor).
        /// </summary>
        float MovemenetSpeedCoefficient { get; }

        /// <summary>
        /// The wrestler's forward direction.
        /// </summary>
        /// <param name="ctx">the target context</param>
        IDirection ForwardDir(ITargetContext ctx);

        /// <summary>
        /// The wrestler's back direction.
        /// </summary>
        /// <param name="ctx">the target context</param>
        IDirection BackDir(ITargetContext ctx);

        /// <summary>
        /// The wrestler's right direction.
        /// </summary>
        /// <param name="ctx">the target context</param>
        IDirection RightDir(ITargetContext ctx);

        /// <summary>
        /// The wrestler's left direction.
        /// </summary>
        /// <param name="ctx">the target context</param>
        IDirection LeftDir(ITargetContext ctx);

        /// <summary>
        /// Retrieves the descriptor of a body part of the wrestler.
        /// </summary>
        /// <param name="part">the body part</param>
        IBodyPart BodyPart(RagdollPart part);

        /// <summary>
        /// Retrieves the position of a POI on the wrestler's body.
        /// </summary>
        /// <param name="poi">the POI</param>
        /// <returns>the position of the POI</returns>
        Vector3 POI(BodyPOI poi);

        /// <summary>
        /// Retrieves a target to a POI on the wrestler's body.
        /// </summary>
        /// <param name="poi">the POI</param>
        /// <param name="ctx">the target context</param>
        /// <returns>the target to the POI</returns>
        ITarget POITarget(BodyPOI poi, ITargetContext ctx);

        /// <summary>
        /// Retrieves a target to the POI on the wrestler's body after the last frame's LateUpdate.
        /// This can be useful if the POI must be evaluated after IK, but comes at a delay of one frame.
        /// </summary>
        /// <param name="poi">the POI</param>
        /// <param name="ctx">the target context</param>
        /// <returns>the target to the POI</returns>
        ITarget POITargetPostIK(BodyPOI poi, ITargetContext ctx);

        /// <summary>
        /// Samples an animation. Must be ended via <see cref="IAnimSampling.EndSampling"/>.
        /// </summary>
        /// <param name="address">the animation's asset ID</param>
        /// <param name="relativeTime">the relative time (0 to 1) to sample at</param>
        /// <param name="offsetTime">the relative time (0 to 1) to sample at initially</param>
        /// <returns>the sampling handler to end the sampling</returns>
        IAnimSampling SampleAnim(AssetAddress address, float relativeTime, float offsetTime = 0);

        /// <summary>
        /// Samples an animation. Must be ended via <see cref="IAnimSampling.EndSampling"/>.
        /// </summary>
        /// <param name="address">the animation's asset ID</param>
        /// <param name="relativeTime">the relative time (0 to 1) to sample at</param>
        /// <param name="faceDir">the target face direction for which to sample</param>
        /// <param name="offsetTime">the relative time (0 to 1) to sample at initially</param>
        /// <returns>the sampling handler to end the sampling</returns>
        IAnimSampling SampleAnim(AssetAddress address, float relativeTime, Vector3 faceDir, float offsetTime = 0);

        /// <summary>
        /// Samples an animation. Must be ended via <see cref="IAnimSampling.EndSampling"/>.
        /// </summary>
        /// <param name="address">the animation's asset ID</param>
        /// <param name="relativeTime">the relative time (0 to 1) to sample at</param>
        /// <param name="pos">the target position at which to sample</param>
        /// <param name="faceDir">the target face direction for which to sample</param>
        /// <param name="offsetTime">the relative time (0 to 1) to sample at initially</param>
        /// <returns>the sampling handler to end the sampling</returns>
        IAnimSampling SampleAnim(AssetAddress address, float relativeTime, Vector3 pos, Vector3 faceDir, float offsetTime = 0);

        /// <summary>
        /// Depenetrates the wrestler's capsule from any collisions at the current position.
        /// </summary>
        /// <returns>the depenetration vector</returns>
        Vector3 Depenetrate();

        /// <summary>
        /// Depenetrates the wrestler's capsule from any collisions at the given position.
        /// </summary>
        /// <param name="pos">the wrestler's simulated position</param>
        /// <returns>the depenetration vector</returns>
        Vector3 Depenetrate(Vector3 pos);

        /// <summary>
        /// Gets the target position of the wrestler so that the given POI matches the given target.
        /// </summary>
        /// <param name="poi">the POI in question</param>
        /// <param name="target">the target position</param>
        /// <returns>the position the wrestler should move to</returns>
        Vector3 MatchPOI(Vector3 poi, Vector3 target);

        /// <summary>
        /// Gets the target position of the wrestler so that the given POI matches the given target in its X and Z coordinates.
        /// </summary>
        /// <param name="poi">the POI in question</param>
        /// <param name="target">the target position</param>
        /// <returns>the position the wrestler should move to</returns>
        Vector3 MatchPOIXZ(Vector3 poi, Vector3 target);

        /// <summary>
        /// Gets the target position of the wrestler so that the given POI lies on the given ray.
        /// </summary>
        /// <param name="poi">the POI in question</param>
        /// <param name="ray">the target ray</param>
        /// <returns>the position the wrestler should move to</returns>
        Vector3 MatchPOIRay(Vector3 poi, Ray ray);

        /// <summary>
        /// Gets the target position of the wrestler so that the given POI lies on the given ray section.
        /// </summary>
        /// <param name="poi">the POI in question</param>
        /// <param name="ray">the target ray</param>
        /// <param name="section">the target ray secion</param>
        /// <returns>the position the wrestler should move to</returns>
        Vector3 MatchPOIRay(Vector3 poi, Ray ray, MinMax section);

        /// <summary>
        /// Gets the target position of the wrestler so that the given POI lies on the given ray on the X/Z plane.
        /// </summary>
        /// <param name="poi">the POI in question</param>
        /// <param name="ray">the target ray</param>
        /// <returns>the position the wrestler should move to</returns>
        Vector3 MatchPOIRayXZ(Vector3 poi, Ray ray);

        /// <summary>
        /// Gets the target position of the wrestler so that the given POI lies on the given ray section on the X/Z plane.
        /// </summary>
        /// <param name="poi">the POI in question</param>
        /// <param name="ray">the target ray</param>
        /// <param name="section">the target ray secion</param>
        /// <returns>the position the wrestler should move to</returns>
        Vector3 MatchPOIRayXZ(Vector3 poi, Ray ray, MinMax section);

        /// <summary>
        /// Computes a coefficient for lifting another wrestler.
        ///
        /// If both wrestlers are equal, the coefficient is 1.
        /// If the other wrestler weighs less than this one, the coefficient is less than one and greater than one otherwise.
        /// </summary>
        /// <param name="other">the other wrestler</param>
        /// <returns>the coefficient</returns>
        float LiftCoefficient(IWrestler other);

        /// <summary>
        /// Computes the animation speed for lifting another wrestler.
        ///
        /// If both wrestlers are equal, the speed is 1.
        /// If the other wrestler weighs less than this one, the speed will somewhat increase, and decrease otherwise
        /// </summary>
        /// <param name="other">the other wrestler</param>
        /// <returns>the lift animation speed</returns>
        float LiftAnimSpeed(IWrestler other);

        /// <summary>
        /// Make the wrestler go dizzy for their current rest time.
        /// </summary>
        /// <param name="restTimeMult">the rest time multiplier</param>
        void SetDizzy(float restTimeMult = 1.0f);

        /// <summary>
        /// Tells whether or not the wrestler is currently dizzy.
        /// </summary>
        bool IsDizzy { get; }

        /// <summary>
        /// Apply crash and burn damage on the wrestler; used for when a flying move is missed.
        /// </summary>
        void CrashAndBurn();

        /// <summary>
        /// Plays a random sound from an audio clip group.
        /// </summary>
        /// <param name="id">the audio clip group's asset ID</param>
        /// <param name="volume">the playback volume between 0 and 1</param>
        void PlaySoundGroup(AssetAddress id, float volume = 1.0f);

        /// <summary>
        /// Makes the wrestler anticipate a damaging impact.
        /// </summary>
        /// <param name="bodyParts">the body parts anticipating an impact</param>
        /// <param name="damage">the base damage caused by the impact</param>
        /// <param name="instigator">the instigator</param>
        /// <param name="timeout">the time, in seconds, after which the impact will expire</param>
        /// <return>an object representing the anticipated impact</return>
        IAnticipatedImpact AnticipateMoveImpact(InlineArray<RagdollPart> bodyParts, Damage damage, IWrestler instigator, float timeout = 3.0f);

        /// <summary>
        /// Cancel anticipiation of an impact.
        /// </summary>
        void CancelAnticipatedImpact();

        /// <summary>
        /// Causes a ground impact.
        /// </summary>
        /// <param name="target">the position of the ground impact</param>
        /// <param name="velocity">the velocity towards the ground</param>
        /// <param name="mass">the mass causing the impact</param>
        /// <param name="ground">the hit ground position</param>
        /// <returns>whether a ground has been detected</returns>
        bool GroundImpact(Vector3 target, float velocity, float mass, out Vector3 ground);

        /// <summary>
        /// Causes a ground impact.
        /// </summary>
        /// <param name="target">the position of the ground impact</param>
        /// <param name="velocity">the velocity towards the ground</param>
        /// <param name="mass">the mass causing the impact</param>
        /// <returns>whether a ground has been detected</returns>
        bool GroundImpact(Vector3 target, float velocity, float mass);

        /// <summary>
        /// Tells whether the given body part is currently on a ground.
        /// </summary>
        /// <param name="bodyPart">the body part in question</param>
        /// <param name="ground">the ground position if detected</param>
        /// <param name="scanDistanceMult">the scan distance multiplier</param>
        /// <returns>true if on ground, false otherwise</returns>
        bool ScanForGround(RagdollPart bodyPart, out Vector3 ground, float scanDistanceMult = 1);

        /// <summary>
        /// Tells whether the given body part is currently on a ground.
        /// </summary>
        /// <param name="bodyPart">the body part in question</param>
        /// <param name="scanDistanceMult">the scan distance multiplier</param>
        /// <returns>true if on ground, false otherwise</returns>
        bool ScanForGround(RagdollPart bodyPart, float scanDistanceMult = 1);

        /// <summary>
        /// Makes the wrestler go into a state.
        /// </summary>
        /// <param name="state">the state to go to</param>
        /// <param name="param">a parameter, the meaning of which (if any) is defined by the target state</param>
        void GotoState(TargetState state, float param = float.NaN);

        /// <summary>
        /// Makes the wrestler sit up from a supine position.
        /// </summary>
        void SitUpFromSupine();

        /// <summary>
        /// Makes the wrestler stand up from a supine position.
        /// </summary>
        void StandUpFromSupine();

        /// <summary>
        /// Makes the wrestler stand up from a prone position.
        /// </summary>
        void StandUpFromProne();

        /// <summary>
        /// Makes the wrestler stand up from an sitting position.
        /// </summary>
        void StandUpFromSit();

        /// <summary>
        /// Makes the wrestler stand up from an all-fours position.
        /// </summary>
        void StandUpFromAllFours();

        /// <summary>
        /// Increases the wrestler's pain.
        /// </summary>
        /// <param name="damage">the damage</param>
        /// <param name="instigator">the damage instigator, may be null</param>
        void Damage(Damage damage, IWrestler instigator);

        /// <summary>
        /// Drains the wrestler's stamina.
        /// </summary>
        /// <param name="amount">the (positive) amount</param>
        void Fatigue(float amount);

        /// <summary>
        /// Changes the wrestler's momentum.
        /// </summary>
        /// <param name="change">the momentum change, positive or negative</param>
        void ChangeMomentum(float change);

        /// <summary>
        /// The wrestler that this wrestler currently targets. May be null.
        /// </summary>
        IWrestler Target { get; }

        /// <summary>
        /// The weapon currently held by the wrestler. May be null.
        /// </summary>
        IWeapon Weapon { get; }

        /// <summary>
        /// Makes the wrestler drop their current weapon.
        /// </summary>
        void DropWeapon();

        /// <summary>
        /// Sets the Y coordinate to base the wrestler's capsule at.
        /// </summary>
        /// <param name="y">the y to use for the base coordinate</target>
        /// <param name="duration">the number of seconds it takes for the base to transition, 0 meaning instantly</param>
        void SetYBase(float y, float duration);

        /// <summary>
        /// Waits for the wrestler to move to the target.
        /// </summary>
        /// <param name="ctx">the execution context</param>
        /// <param name="target">the move target</param>
        /// <param name="speedMult">the movement speed multiplier</param>
        /// <returns>the corresponding yield instruction</returns>
        YieldInstructionCode MoveTo(IContext ctx, Vector3 target, float speedMult = 1.0f);

        /// <summary>
        /// Waits for the wrestler to turn towards the given direction.
        /// </summary>
        /// <param name="ctx">the execution context</param>
        /// <param name="target">the target direction</param>
        /// <param name="speedMult">the turning speed multiplier</param>
        /// <returns>the corresponding yield instruction</returns>
        YieldInstructionCode TurnTo(IContext ctx, Vector3 target, float speedMult = 1.0f);

        /// <summary>
        /// Waits for the wrestler to move and turn towards the given target position and direction.
        /// </summary>
        /// <param name="ctx">the execution context</param>
        /// <param name="targetPos">the target position</param>
        /// <param name="targetDir">the target direction</param>
        /// <param name="moveSpeedMult">the movement speed multiplier</param>
        /// <param name="turnSpeedMult">the turning speed multiplier</param>
        /// <returns>the corresponding yield instruction</returns>
        YieldInstructionCode MoveAndTurn(IContext ctx, Vector3 targetPos, Vector3 targetDir, float moveSpeedMult = 1.0f, float turnSpeedMult = 1.0f);

        /// <summary>
        /// Makes the wrestler walk to the specified target using an AI.
        /// </summary>
        /// <param name="ctx">the execution context</param>
        /// <param name="target">the target to walk up to</param>
        /// <param name="desiredDistance">the desired distance from the target</param>
        /// <returns>the corresponding yield instruction</returns>
        YieldInstructionCode WalkTo(IContext ctx, Vector3 target, float desiredDistance = 0.25f);

        /// <summary>
        /// Potentially botches the current move, cancelling it and putting all participants in ragdoll mode.
        /// 
        /// The wrestler's strength, stamina and momentum will be tested against lifting the opponent:
        /// the heavier the victim and the weaker the wrestler, the higher will be the chance to botch.
        /// <param name="victim">the wrestler the move is performed on</param>
        /// <param name="bias">the bias, multiplying the final botch chance (>1 = more likely, <1 = less likely)</param>
        /// <returns>whether the move has been botched</returns>
        /// </summary>
        bool BotchMoveDueToLiftingStrength(IWrestler victim, float bias = 1.0f);

        /// <summary>
        /// Potentially botches the current move, cancelling it and putting all participants in ragdoll mode.
        /// 
        /// The wrestler's skill is tested against the given value, also taking momentum into account:
        /// the lower the skill, the higher will be the chance to botch
        /// <param name="safeSkill">the required skill to perform the move safely</param>
        /// <param name="bias">the bias, multiplying the final botch chance (>1 = more likely, <1 = less likely)</param>
        /// <returns>whether the move has been botched</returns>
        /// </summary>
        bool BotchMoveDueToSkill(float safeSkill, float bias = 1.0f);

        /// <summary>
        /// Should the wrestler drop to the floor after receiving a weak standing move?
        /// <param name="bias">the bias, multiplying the final drop chance (>1 = more likely, <1 = less likely)</param>
        /// </summary>
        bool ShouldDropAfterWeakStandingMove(float bias = 1.0f);

        /// <summary>
        /// Scales the velocity of all parts of the ragdoll.
        /// </summary>
        /// <param name="s">the scalar</param>
        void ScaleRagdollVelocity(float s);

        /// <summary>
        /// Briefly prevents the wrestler from causing impacts.
        /// This may be useful to protect a table from breaking right after being placed on it.
        /// Should likely be used in combination with ScaleRagdollVelocity(0).
        /// </summary>
        void PreventImpact();

        /// <summary>
        /// Retrieves a strike hit observer.
        /// You MUST call End once you no longer need it!
        /// </summary>
        IStrikeHitObserver ObserveStrikeHits();

        /// <summary>
        /// Is the wrestler currently ragdolling?
        /// </summary>
        bool IsRagdoll { get; }

        /// <summary>
        /// Is the wrestler currently on a ring apron?
        /// </summary>
        /// <value></value>
        bool IsOnRingApron { get; }

        /// <summary>
        /// Can the wrestler currently fall over?
        /// </summary>
        bool CanFallOver { get; }

        /// <summary>
        /// Does the wrestler react to strikes as if standing?
        /// </summary>
        bool CanReactAsStanding { get; }
    }
}
