using System;
using Svelto.ECS;
using Unity.Mathematics;
using UnityEngine;

using Gamecraft.Damage;
using RobocraftX.Common;
using RobocraftX.Physics;

namespace TechbloxModdingAPI
{
    /// <summary>
    /// A rigid body (like a chunk of connected blocks) during simulation.
    /// </summary>
    public class SimBody : EcsObjectBase, IEquatable<SimBody>, IEquatable<EGID>
    {
        public override EGID Id { get; }

        /// <summary>
        /// The cluster this chunk belongs to, or null if no cluster destruction manager present or the chunk doesn't exist.
        /// Get the SimBody from a Block if possible for good performance here.
        /// </summary>
        public Cluster Cluster => cluster ?? (cluster = clusterId == uint.MaxValue ? Block.BlockEngine.GetCluster(Id.entityID) : new Cluster(clusterId));

        private Cluster cluster;
        private readonly uint clusterId = uint.MaxValue;

        public SimBody(EGID id)
        {
            Id = id;
        }

        public SimBody(uint id) : this(new EGID(id, CommonExclusiveGroups.SIMULATION_BODIES_GROUP))
        {
        }

        internal SimBody(uint id, uint clusterID) : this(id)
        {
            clusterId = clusterID;
        }

        /// <summary>
        /// The position of this body. When setting the position, update the position of the connected bodies as well,
        /// otherwise unexpected forces may arise.
        /// </summary>
        public float3 Position
        {
            get => GetStruct().position;
            set => GetStruct().position = value;
        }

        public float3 Velocity
        {
            get => GetStruct().velocity;
            set => GetStruct().velocity = value;
        }

        public float3 AngularVelocity
        {
            get => GetStruct().angularVelocity;
            set => GetStruct().angularVelocity = value;
        } //Delta versions are used internally, can't be set or get

        public float3 Rotation
        {
            get => ((Quaternion) GetStruct().rotation).eulerAngles;
            set
            {
                ref var str = ref GetStruct();
                Quaternion quaternion = str.rotation;
                quaternion.eulerAngles = value;
                str.rotation = quaternion;
            }
        }

        public float Mass
        {
            get => math.rcp(GetStruct().physicsMass.InverseMass);
            //set => GetStruct().physicsMass.InverseMass = math.rcp(value);
        }

        public float3 CenterOfMass
        {
            get => GetStruct().physicsMass.CenterOfMass;
            //set => GetStruct().physicsMass.CenterOfMass = value;
        }

        public float Volume
        {
            get => GetStruct().volume;
        }

        public float InitialHealth
        {
            get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).initialHealth;
            set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).initialHealth = value;
        }

        public float CurrentHealth
        {
            get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).currentHealth;
            set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).currentHealth = value;
        }

        public float HealthMultiplier
        {
            get => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).healthMultiplier;
            set => Block.BlockEngine.GetBlockInfo<HealthEntityComponent>(this).healthMultiplier = value;
        }

        /// <summary>
        /// Whether the body can be moved or static.
        /// </summary>
        public bool Static => Block.BlockEngine.GetBlockInfo<MassEntityStruct>(this).isStatic; //Setting it doesn't have any effect

        /// <summary>
        /// The rigid bodies connected to this one via functional joints (broken ones don't count).
        /// </summary>
        public SimBody[] GetConnectedBodies()
        {
            return Block.BlockEngine.GetConnectedSimBodies(Id.entityID);
        }

        /// <summary>
        /// The blocks that form this rigid body.
        /// </summary>
        /// <returns></returns>
        public Block[] GetBlocks()
        {
            return Block.BlockEngine.GetBodyBlocks(Id.entityID);
        }

        private ref RigidBodyEntityStruct GetStruct()
        {
            return ref Block.BlockEngine.GetBlockInfo<RigidBodyEntityStruct>(this);
        }

        public override string ToString()
        {
            return $"{nameof(Id)}: {Id}, {nameof(Position)}: {Position}, {nameof(Mass)}: {Mass}, {nameof(Static)}: {Static}";
        }

        public bool Equals(SimBody other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return Id.Equals(other.Id);
        }

        public bool Equals(EGID other)
        {
            return Id.Equals(other);
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != this.GetType()) return false;
            return Equals((SimBody) obj);
        }

        public override int GetHashCode()
        {
            return Id.GetHashCode();
        }

        /// <summary>
        /// Returns the object identified by the given ID (A-Z).
        /// This has the same result as calling ObjectIdentifier.GetByID(id) and then GetRigidBody() with the duplicates filtered out.
        /// </summary>
        /// <param name="id">The alphabetical ID</param>
        /// <returns>An array that may be empty</returns>
        public static SimBody[] GetFromObjectID(char id) => Block.BlockEngine.GetSimBodiesFromID((byte) (id - 'A'));
    }
}