using System;
using UnityEngine;

namespace pdinklag.CPW.Common
{
    /// <summary>
    /// Represents the execution of a wrestling move.
    /// </summary>
    public interface IMoveInstance : IContext
    {
        /// <summary>
        /// Gets the move that is being executed.
        /// </summary>
        /// <typeparam name="T">the expected move type</typeparam>
        T GetMove<T>() where T : Move;

        /// <summary>
        /// Tells whether the move has already finished.
        /// </summary>
        bool Finished { get; }

        /// <summary>
        /// The number of wrestlers involved.
        /// </summary>
        int WrestlerCount { get; }

        /// <summary>
        /// Enables or disables the move collider (if available).
        /// </summary>
        bool ColliderEnabled { get; set; }

        /// <summary>
        /// Enable or disable rotation of the move participants by instigator input.
        /// Whether and how exactly this works is specific to the move type.
        /// </summary>
        bool RotationInputEnabled { get; set; }

        /// <summary>
        /// Set the move's default outcome direction in the case that no move rotation is applied.
        /// </summary>
        /// <param name="dir">the move's default outcome direction</param>
        void SetOutcomeDir(Vector3 dir);

        /// <summary>
        /// Retrieves the move context.
        /// </summary>
        IMoveContext MoveContext { get; }

        /// <summary>
        /// Gets a participating wrestler.
        /// </summary>
        /// <param name="i">the number of the wrestler, must be less than <see cref="WrestlerCount"/></param>
        /// <returns>the wrestler</returns>
        IWrestler Wrestler(int i);

        /// <summary>
        /// The target position of a participating wrestler.
        /// 
        /// Note that the position only contains a meaningful value depending on the context.
        /// </summary>
        /// <param name="i">the number of the wrestler, must be less than <see cref="WrestlerCount"/></param>
        /// <returns>the wrestler's target position</returns>
        Vector3 TargetPosition(int i);

        /// <summary>
        /// Compute the placement of the participants.
        /// 
        /// This is mostly useful only to moves that transition two wrestlers into a synchronized state, such as entering a grapple.
        /// </summary>
        /// <param name="data">the placement data to use</param>
        IMoveParticipantPlacement ComputePlacement(ParticipantPlacementData data);

        /// <summary>
        /// Uses the move collider (if available) and computes the required depenetration.
        /// 
        /// The move participants will be simulated at those positions.
        /// </summary>
        /// <param name="positions">used to simulate the participants' positions</param>
        /// <returns>the depenetration vector</returns>
        Vector3 Depenetrate(InlineArray<Vector3> positions);

        /// <summary>
        /// Uses the move collider (if available) and computes the required depenetration for the participants' current positions.
        /// </summary>
        /// <returns>the depenetration vector</returns>
        Vector3 Depenetrate();

        /// <summary>
        /// Casts the move collider (if available) along the given direction and computes the depenetration required to stay within bounds.
        /// The cast origin is computed automatically.
        /// </summary>
        /// <param name="positions">the participants' target positions</param>
        /// <returns>the depenetration vector</returns>
        Vector3 DepenetrationCast(InlineArray<Vector3> startPositions, InlineArray<Vector3> targetPositions);

        /// <summary>
        /// When the move has finished, wait for the specified amount of seconds before re-enabling collisions between the participants.
        /// 
        /// Defaults to no delay.
        /// </summary>
        /// <param name="delay">the delay in seconds</param>
        void SetCollisionReenableDelay(float delay);

        /// <summary>
        /// Change the move's collision settings.
        /// </summary>
        /// <param name="characterCollisionsEnabled">enable character collisions</param>
        /// <param name="gravityEnabled">enable gravity</param>
        void SetWrestlerCollisionMode(bool characterCollisionsEnabled, bool gravityEnabled);

        /// <summary>
        /// Reset the move's collision settings to what they are in the move object.
        /// </summary>
        void ResetWrestlerCollisionMode();

        /// <summary>
        /// Plays an animation for a given wrestler.
        /// 
        /// Should the move be canceled, the animation playback will be cleaned up properly.
        /// </summary>
        /// <param name="w">the wrestler to play the animation</param>
        /// <param name="address">the animation's asset ID</param>
        /// <param name="transitionDuration">the time, in seconds, to blend into the animation</param>
        /// <param name="keepAlive">
        ///     keep this animation alive after the move ends script ends;
        ///     this may be necessary for proper IK transitions in some cases,
        ///     but it also causes animation events to be called beyond the scope of the move,
        ///     which may be problematic in some other situations
        ///     -> leave it false unless you know what you are doing! :-)
        /// </param>
        /// <returns>the animation playback</returns>
        IAnimPlayback PlayAnim(IWrestler w, AssetAddress address, float transitionDuration = 0.5f, bool keepAlive = false);

        /// <summary>
        /// Perform a strike using the move's own strike info, if any.
        /// 
        /// The strike must be ended by calling <c>End</c>, otherwise it ends once the move ends.
        /// </summary>
        IStrike Strike();

        /// <summary>
        /// Perform a grab.
        /// 
        /// The grab ends automatically when it was successfully processed by the callback, when the move ends or when
        /// End is called explicitly.
        /// </summary>
        /// <param name="parts">the body parts with which the instigator performs the grab</param>
        /// <param name="sizeMultiplier">the size multiplier for the parts' colliders</param>
        /// <returns>the grab instance</returns>
        IGrab Grab(StrikeBodyParts parts, float sizeMultiplier = 1.0f);

        /// <summary>
        /// Anticipate an impact and count it as a strike using the move's own strike info, if any.
        /// </summary>
        /// <param name
        /// <param name="timeout">the time, in seconds, after which the impact will expire</param>
        IAnticipatedImpact AnticipateStrikeImpact(float successDizziness = 0.0f, float timeout = 3.0f);

        /// <summary>
        /// Marks the sweet spot for a counter and switches to the given move script on success.
        /// 
        /// If the player has pressed "block" before this is called and the counter succeeds, the move script is immediately terminated.
        /// If the player presses "block" after this is called and the counter succeeds, the script is terminated in the instant that "block" is pressed.
        /// To cancel the opportunity to counter, use CancelCounter.
        /// </summary>
        /// <param name="script">the move script</param>
        /// <param name="victim">the victim, who would counter the move</param>
        /// <param name="instigator">the instigator</param>
        /// <param name="staminaTest">the stamina test for countering the move (only to test - no drain is applied here)</param>
        /// <param name="bias">the success bias (chance multiplier)</param>
        void Counter(MoveScriptDelegate script, IWrestler victim, IWrestler instigator, float staminaTest, float bias = 1.0f);

        /// <summary>
        /// Cancels the opportunity to counter that was registered using Counter.
        /// </summary>
        void CancelCounter();

        /// <summary>
        /// Performs a pinfall.
        /// </summary>
        /// <param name="instigator">the pinning wrestler</param>
        /// <param name="victim">the wrestler being pinned</param>
        /// <returns>the winning maneuver object</returns>
        IWinningManeuver EnterPinfall(IWrestler instigator, IWrestler victim);

        /// <summary>
        /// Enters a submission hold.
        /// </summary>
        /// <param name="instigator">the instigator</param>
        /// <param name="victim">the victim</param>
        /// <returns>the submission hold object</returns>
        ISubmissionHold EnterSubmission(IWrestler instigator, IWrestler victim);

        /// <summary>
        /// Finalizes the move for the given wrestler, making him no longer part of the move.
        ///
        /// This happens automatically when the move script has completely finished.
        /// </summary>
        /// <param name="w">the wrestler to finalize</param>
        void Finalize(IWrestler w);

        /// <summary>
        /// Finalizes the move for the given wrestlers and forces them into a synchronized state.
        /// <param name="w1">the first wrestler</param>
        /// <param name="w2">the second wrestler</param>
        /// <param name="state">the synchronized state</param>
        /// <param name="param1">a parameter for the first wrestler, the meaning of which (if any) is defined by the target state</param>
        /// <param name="param2">a parameter for the second wrestler, the meaning of which (if any) is defined by the target state</param>
        /// </summary>
        void Finalize(IWrestler w1, IWrestler w2, TargetSyncState state, float param1 = float.NaN, float param2 = float.NaN);

        /// <summary>
        /// Tests whether a wrestler is still active, i.e., not yet finalized.
        /// </summary>
        /// <param name="w">the wrestler in question</param>
        bool IsActive(IWrestler w);

        /// <summary>
        /// Perform a jump.
        /// 
        /// This will cause a special movement script to take over the wrestler's movement, computing a jump parabola
        /// according to the given animation, key times and distance to the target.
        /// </summary>
        /// <param name="p">the jump animation</param>
        /// <param name="jumpDir">the jump direction</param>
        /// <param name="peakTime">the normalized time in the animation at which the wrestler at the peak jump height</param>
        /// <param name="landTime">the normalized time in the animation at which the wrestler lands and the jump ends</param>
        /// <param name="extraDistance">the extra distance to account for when computing the jump parabola (may be negative)</param>
        /// <param name="maxDistanceMultiplier">a multiplier for the maximum jump distance</param>
        void PerformJump(IAnimPlayback p, Vector3 jumpDir, float peakTime, float landTime = 1.0f, float extraDistance = 0.0f, float maxDistanceMultiplier = 1.0f);

        /// <summary>
        /// Notifies the system that a taunt has been performed.
        /// </summary>
        /// <param name="w">the wrestler who performed the taunt</param>
        /// <param name="momentumMultiplier">the multiplier for the caused momentum changes</param>
        void NotifyTaunt(IWrestler w, float momentumMultiplier = 1.0f);
    }
}
