using Microsoft.Xna.Framework;
using System.Collections.Generic;

namespace Barotrauma
{
    class FishWalkParams : FishGroundedParams
    {
        public static FishWalkParams GetDefaultAnimParams(Character character)
        {
            return Check(character) ? GetDefaultAnimParams<FishWalkParams>(character, AnimationType.Walk) : Empty;
        }
        public static FishWalkParams GetAnimParams(Character character, Either<string, ContentPath> file, bool throwErrors = true)
        {
            return Check(character) ? GetAnimParams<FishWalkParams>(character, AnimationType.Walk, file, throwErrors) : null;
        }

        protected static readonly FishWalkParams Empty = new FishWalkParams();

        public override void StoreSnapshot() => StoreSnapshot<FishWalkParams>();
    }

    class FishRunParams : FishGroundedParams
    {
        public static FishRunParams GetDefaultAnimParams(Character character)
        {
            return Check(character) ? GetDefaultAnimParams<FishRunParams>(character, AnimationType.Run) : Empty;
        }
        public static FishRunParams GetAnimParams(Character character, Either<string, ContentPath> file, bool throwErrors = true)
        {
            return Check(character) ? GetAnimParams<FishRunParams>(character, AnimationType.Run, file, throwErrors) : null;
        }

        protected static readonly FishRunParams Empty = new FishRunParams();

        public override void StoreSnapshot() => StoreSnapshot<FishRunParams>();
    }

    class FishSwimFastParams : FishSwimParams
    {
        public static FishSwimFastParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<FishSwimFastParams>(character, AnimationType.SwimFast);
        public static FishSwimFastParams GetAnimParams(Character character, Either<string, ContentPath> file, bool throwErrors = true)
        {
            return GetAnimParams<FishSwimFastParams>(character, AnimationType.SwimFast, file, throwErrors);
        }

        public override void StoreSnapshot() => StoreSnapshot<FishSwimFastParams>();
    }

    class FishSwimSlowParams : FishSwimParams
    {
        public static FishSwimSlowParams GetDefaultAnimParams(Character character) => GetDefaultAnimParams<FishSwimSlowParams>(character, AnimationType.SwimSlow);
        public static FishSwimSlowParams GetAnimParams(Character character, Either<string, ContentPath> file, bool throwErrors = true)
        {
            return GetAnimParams<FishSwimSlowParams>(character, AnimationType.SwimSlow, file, throwErrors);
        }

        public override void StoreSnapshot() => StoreSnapshot<FishSwimSlowParams>();
    }

    abstract class FishGroundedParams : GroundedMovementParams, IFishAnimation
    {
        protected static bool Check(Character character)
        {
            if (!character.AnimController.CanWalk)
            {
                DebugConsole.ThrowError($"{character.SpeciesName} cannot use run animations!", 
                    contentPackage: character.Prefab.ContentPackage);
                return false;
            }
            return true;
        }

        [Editable, Serialize(true, IsPropertySaveable.Yes, description: "Should the character be flipped depending on which direction it faces. Should usually be enabled on all characters that have distinctive upper and lower sides.")]
        public bool Flip { get; set; }

        [Serialize(1f, IsPropertySaveable.Yes, description: "Reduces continuous flipping when the character abruptly changes direction."), Editable]
        public float FlipCooldown { get; set; }

        [Serialize(0.5f, IsPropertySaveable.Yes, description: "How much it takes before the character flips. The timer starts when the character starts to move in the different direction."), Editable]
        public float FlipDelay { get; set; }

        [Serialize(10.0f, IsPropertySaveable.Yes, description: "How much force is used to move the head to the correct position."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
        public float HeadMoveForce { get; set; }

        [Serialize(10.0f, IsPropertySaveable.Yes, description: "How much force is used to move the torso to the correct position."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
        public float TorsoMoveForce { get; set; }

        [Serialize(8.0f, IsPropertySaveable.Yes, description: "How much force is used to move the feet to the correct position."), Editable(MinValueFloat = 0, MaxValueFloat = 100)]
        public float FootMoveForce { get; set; }

        [Serialize(50.0f, IsPropertySaveable.Yes, description: "How much torque is used to rotate the tail to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 1000, ValueStep = 1)]
        public float TailTorque { get; set; }

        [Serialize(0.0f, IsPropertySaveable.Yes, description: "Optional torque that's constantly applied to legs."), Editable(MinValueFloat = 0, MaxValueFloat = 1000)]
        public float LegTorque { get; set; }

        /// <summary>
        /// The angle of the collider when standing (i.e. out of water).
        /// In degrees.
        /// </summary>
        [Serialize(0f, IsPropertySaveable.Yes, description: "The angle of the character's collider when standing."), Editable(MinValueFloat = -360, MaxValueFloat = 360)]
        public float ColliderStandAngle
        {
            get => MathHelper.ToDegrees(ColliderStandAngleInRadians);
            set => ColliderStandAngleInRadians = MathHelper.ToRadians(value);
        }
        public float ColliderStandAngleInRadians { get; private set; }
        
        [Serialize(null, IsPropertySaveable.Yes), Editable]
        public string FootAngles
        {
            get => ParseFootAngles(FootAnglesInRadians);
            set => SetFootAngles(FootAnglesInRadians, value);
        }
        
        /// <summary>
        /// Key = limb id, value = angle in radians
        /// </summary>
        public Dictionary<int, float> FootAnglesInRadians { get; set; } = new Dictionary<int, float>();

        /// <summary>
        /// In degrees.
        /// </summary>
        [Serialize(float.NaN, IsPropertySaveable.Yes), Editable(-360f, 360f)]
        public float TailAngle
        {
            get => float.IsNaN(TailAngleInRadians) ? float.NaN : MathHelper.ToDegrees(TailAngleInRadians);
            set
            {
                if (!float.IsNaN(value))
                {
                    TailAngleInRadians = MathHelper.ToRadians(value);
                }
            }
        }
        public float TailAngleInRadians { get; private set; } = float.NaN;
    }

    abstract class FishSwimParams : SwimParams, IFishAnimation
    {
        [Serialize(false, IsPropertySaveable.Yes, description: "Instead of linear movement (default), use a wave-like movement. Note: WaveAmplitude and WaveLength don't have any effect on this. It's synced with the movement speed."), Editable]
        public bool UseSineMovement { get; set; }

        [Editable, Serialize(true, IsPropertySaveable.Yes, description: "Should the character be flipped depending on which direction it faces. Should usually be enabled on all characters that have distinctive upper and lower sides.")]
        public bool Flip { get; set; }

        [Serialize(1f, IsPropertySaveable.Yes, description: "Reduces continuous flipping when the character abruptly changes direction."), Editable]
        public float FlipCooldown { get; set; }

        [Serialize(0.5f, IsPropertySaveable.Yes, description: "How much it takes before the character flips. The timer starts when the character starts to move in the different direction."), Editable]
        public float FlipDelay { get; set; }

        [Editable, Serialize(true, IsPropertySaveable.Yes, description: "If enabled, the character will simply be mirrored horizontally when it wants to turn around. If disabled, it will rotate itself to face the other direction.")]
        public bool Mirror { get; set; }

        [Editable, Serialize(true, IsPropertySaveable.Yes, description: "Disabling this will make mirroring instantaneous.")]
        public bool MirrorLerp { get; set; }

        [Serialize(5f, IsPropertySaveable.Yes), Editable]
        public float WaveAmplitude { get; set; }

        [Serialize(10.0f, IsPropertySaveable.Yes), Editable]
        public float WaveLength { get; set; }

        [Editable, Serialize(true, IsPropertySaveable.Yes, description: "Should the character face towards the direction it's heading.")]
        public bool RotateTowardsMovement { get; set; }

        [Serialize(50.0f, IsPropertySaveable.Yes, description: "How much torque is used to rotate the tail to the correct orientation."), Editable(MinValueFloat = 0, MaxValueFloat = 2000, ValueStep = 1)]
        public float TailTorque { get; set; }

        [Serialize(1f, IsPropertySaveable.Yes, description: "Multiplier applied based on the angle difference between the tail and the main limb. Increasing the value prevents snake-like characters from getting tangled on themselves. Default = 1 (no boost)"), Editable(MinValueFloat = 1, MaxValueFloat = 100)]
        public float TailTorqueMultiplier { get; set; }

        [Serialize(null, IsPropertySaveable.Yes), Editable]
        public string FootAngles
        {
            get => ParseFootAngles(FootAnglesInRadians);
            set => SetFootAngles(FootAnglesInRadians, value);
        }

        [Serialize(false, IsPropertySaveable.Yes, description: "Should the animation be updated even if the character is not moving?"), Editable]
        public bool UpdateAnimationWhenNotMoving { get; set; }

        /// <summary>
        /// Key = limb id, value = angle in radians
        /// </summary>
        public Dictionary<int, float> FootAnglesInRadians { get; set; } = new Dictionary<int, float>();

        /// <summary>
        /// In degrees.
        /// </summary>
        [Serialize(float.NaN, IsPropertySaveable.Yes), Editable(-360f, 360f)]
        public float TailAngle
        {
            get => float.IsNaN(TailAngleInRadians) ? float.NaN : MathHelper.ToDegrees(TailAngleInRadians);
            set
            {
                if (!float.IsNaN(value))
                {
                    TailAngleInRadians = MathHelper.ToRadians(value);
                }
            }
        }
        public float TailAngleInRadians { get; private set; } = float.NaN;
    }

    interface IFishAnimation
    {
        string FootAngles { get; set; }
        Dictionary<int, float> FootAnglesInRadians { get; set; }
        float TailAngle { get; set; }
        float TailAngleInRadians { get; }
        float TailTorque { get; set; }
        bool Flip { get; set; }
        float FlipCooldown { get; set; }
        float FlipDelay { get; set; }
    }
}
