using Unity.Mathematics;
using UnityEngine;


[System.Serializable]
public unsafe struct Health_Native : IComponent_Native
{
    public string healthQuotientString()
    {
        return health + " / " + healthCapacity;
    }
    
    public enum TakeDamageResult
    {
        ALREADY_DEAD,
        TOOK_DAMAGE_DID_NOT_DIE,
        TOOK_DAMAGE_AND_DIED,
    }

    public GameObject_Native* gameObject { get; set; }
    public Transform_Native* transform; //cache for speed

    public float healthCapacity;
    private float _health;
    public float health { get => _health; set => _health = math.clamp(value, 0f, healthCapacity); }
    public bool isDead => health <= 0;
    public bool isFull => health > -healthCapacity;
    
    public byte relativeDifficulty; //cannot be zero

    [SerializeField] private float healthRegenPerFrame;


    [Header("FX")]
    [SerializeField] private VFXID_Native onDieVFX; //attach sound to vfx
    

    public void init(GameObject_Native* _gameObjectPtr)
    {
        this.gameObject = _gameObjectPtr;
        this.transform = this.gameObject->transform;
        this.lastTakeDamageTime = -1000;
        health = healthCapacity;
    }

    public void resetFromPooling()
    {
        health = healthCapacity;
    }


    public Attacker* lastInflictingAttacker { get; private set; }
    public float lastTakeDamageTime { get; private set; }


    public void parallelTick()
    {
        if(!isDead) health += healthRegenPerFrame;
    }

    public TakeDamageResult takeDamage(float amount, Attacker* inflicting, ref UpdateParams p)
    {
        if (isDead) return TakeDamageResult.ALREADY_DEAD;
        health -= amount;
        lastInflictingAttacker = inflicting;
        lastTakeDamageTime = p.time;
        if (isDead) //died
        {
            //    if (deactivateGameObjectOnDie) this.gameObject->active = false;
            if(onDieVFX != VFXID_Native.NONE)p.vfxManagerNative.play(onDieVFX, this.transform->worldPosition.toFloat3());
            return TakeDamageResult.TOOK_DAMAGE_AND_DIED;
        }
        else
        {
            return TakeDamageResult.TOOK_DAMAGE_DID_NOT_DIE;
        }
    }

    public bool tryGetRecentOpposingAttacker(float time, out AttackTarget attackTarget)
    {
        attackTarget = default;
        if (lastInflictingAttacker == null || !lastInflictingAttacker->gameObject->active) return false;
        const float recentTimeThreshold = 1f;
        if (time > lastTakeDamageTime + recentTimeThreshold) return false;
        var opposerHealth = lastInflictingAttacker->gameObject->getComponent<Health_Native>();
        if (opposerHealth->isDead) return false;

        attackTarget = new AttackTarget(opposerHealth, opposerHealth->transform->gridPosition);
        return true;
    }
    
#if UNITY_EDITOR
    public void onValidate()
    {
        if (relativeDifficulty == 0) relativeDifficulty = 1;
    }
    
#endif
}
using System;
using Unity.Mathematics;
using UnityEngine;


public static unsafe class UnitComponent_NativeExtensions
{
    /// <summary>
    /// a shorthand for casting
    /// </summary>
    public static UnitComponent_Native* toUnitComponentPtr(this IntPtr intPtr)
    {
        return (UnitComponent_Native*) intPtr;
    }
    
}

/// <summary>
/// responsible for returning to pool
/// </summary>
[System.Serializable]
public unsafe struct UnitComponent_Native : IComponent_Native
{

    public GameObject_Native* gameObject { get; set; }

    private Health_Native* health;
    private FlowFieldMovement* movement;
    private Attacker* attacker;
    private EncodedAnimator* encodedAnimator;


    public UnitType unitType;
    public byte byteUnitType => (byte) unitType; //convenience, since NativeHashmaps can't use enums...

    public bool inTower { get; set; }

    [SerializeField] private float attackAnimDuration;
    public EncodedAnimState encodedAnimState { get; private set; }
    public float2 pointingDirectionLocal { get; private set; }
    private float lastPointingDirectionUpdateTime;

    public void init(GameObject_Native* _gameObject, Health_Native* _health, FlowFieldMovement* _movement, Attacker* _attacker, EncodedAnimator* _encodedAnimator)
    {
        this.gameObject = _gameObject;
        this.health = _health;
        this.movement = _movement;
        this.attacker = _attacker;
        this.encodedAnimator = _encodedAnimator;
        float2 pointing = new(1, 0);
        pointing = pointing.rotate(this.gameObject->idMod30 * 120); //randomize pointing direction
        pointingDirectionLocal = pointing;
    }

    public void tick(ref UpdateParams p)
    {
        if (health->isDead) 
        {
            this.gameObject->active = false;
            fixed (UnitComponent_Native* selfPtr = &this) p.unitManagerNative.returnToPool(selfPtr);
        }
        else
        {
            resolveAnimState(p.time);
            encodedAnimator->encodedAnimState = encodedAnimState;
            encodedAnimator->pointingDirectionLocal = pointingDirectionLocal;
        }
    }

    public void resetFromPooling()
    {
        health->resetFromPooling();
    }

    private void resolveAnimState(float time)
    {
        
        if (time < attacker->lastAttackTime + attackAnimDuration)
        {
            encodedAnimState = EncodedAnimState.ATTACKING;
            var d = attacker->lastVictimLocalPosition -  this.gameObject->transform->localPosition;
            updatePointingDirection(time,d,3f);
        }
        else if (attacker->isAiming())
        {
            encodedAnimState = EncodedAnimState.AIMING;
            var d = attacker->lastVictimLocalPosition -  this.gameObject->transform->localPosition;
            updatePointingDirection(time,d,3f);
        }
        else if (movement->state == FlowFieldMovement.State.MOVING_TO_DESTINATION)
        {
            encodedAnimState = EncodedAnimState.MOVING;
            updatePointingDirection(time,movement->lastFlowFieldMovementDirectionLocal);
        }
        else
        {
            encodedAnimState = EncodedAnimState.IDLE;
        }
    }

    private void updatePointingDirection(float time, float2 targetDirection, float maxStepMultiplier = 1f)
    {
        if (targetDirection.Equals(float2.zero)) return;
        const float minDirectionTime = 0.1f; //to really stop jumpiness
        if (time < lastPointingDirectionUpdateTime+ minDirectionTime) return;
        const float maxStep = 12f;
        //SMOOTH rotation so it doesn't get all jumpy on diagonal movement (Crossing small sections of the grid that point in different directions)
        pointingDirectionLocal =pointingDirectionLocal.rotateTowardsDirection(targetDirection, maxStep * maxStepMultiplier);
        

        lastPointingDirectionUpdateTime = time;
    }
}