using UnityEngine;

// Sci-Fi Ship Controller. Copyright (c) 2018-2023 SCSM Pty Ltd. All rights reserved.
namespace SciFiShipController
{
    /// <summary>
    /// Attach to stationary ground-based turrets.
    /// </summary>
    [AddComponentMenu("Sci-Fi Ship Controller/Weapon Components/Surface Turret Module")]
    [HelpURL("http://scsmmedia.com/ssc-documentation")]
    public class SurfaceTurretModule : MonoBehaviour
    {
        #region Enumerations

        #endregion

        #region Public variables

        /// <summary>
        /// If enabled, the Initialise() will be called as soon as Start() runs. This should be disabled if you are
        /// initialising the turret through code and using the SurfaceTurretModule API methods.
        /// </summary>
        public bool initialiseOnStart = false;

        /// <summary>
        /// The faction or alliance the item belongs to. This can be used to identify if an item is friend or foe.
        /// Default (neutral) is 0
        /// </summary>
        public int factionId = 0;

        /// <summary>
        /// Although normally representing a squadron of ships, this can be used on a turret to group it with other things in your scene
        /// </summary>
        public int squadronId = -1;

        /// <summary>
        /// Automatically create a Location in the SSCManager when turret is initialised
        /// </summary>
        public bool autoCreateLocation = false;

        /// <summary>
        /// Is this turret (Location) visible to radar queries? The turret needs a LocationData
        /// entry in SSCManager to appear on radar.
        /// </summary>
        public bool isVisibleToRadar;

        /// <summary>
        /// The relative size of the blip on the radar mini-map
        /// </summary>
        [Range(1, 5)] public byte radarBlipSize = 1;

        /// <summary>
        /// The magnitude of the acceleration (in metres per second squared) due to gravity.
        /// This affects how a projectile travels after it has been fired.
        /// </summary>
        public float gravitationalAcceleration;

        /// <summary>
        /// The direction in world space in which gravity acts upon the turret.
        /// This affects how a projectile travels after it has been fired.
        /// </summary>
        public Vector3 gravityDirection;

        /// <summary>
        /// The sound or particle FX used when the turret is destroyed. It should automatically be despawned when the turret is destroyed.
        /// If you modify this, call ReinitialiseTurretProjectilesAndEffects().
        /// </summary>
        public EffectsModule destructionEffectsObject;

        /// <summary>
        /// This is used when you want pre-build fragments of the turret to explode out from the turrets position when it is destroyed.
        /// If you modify this, call ReinitialiseTurretDestructObjects().
        /// </summary>
        public DestructModule destructObject;

        /// <summary>
        /// Should the turret be removed from the scene when its health reaches 0?
        /// </summary>
        public bool isDestroyOnNoHealth = false;

        /// <summary>
        /// [READONLY] Has the turret been initialised?
        /// </summary>
        public bool IsInitialised { get { return isInitialised; } }

        /// <summary>
        /// [READONLY] The position of the turret as a vector. Derived from the position of the transform.
        /// </summary>
        public Vector3 TransformPosition { get { return trfmPos; } }

        /// <summary>
        /// [READONLY] The forward direction of the turret in world space as a vector. Derived from the forward direction of the transform. 
        /// </summary>
        public Vector3 TransformForward { get { return trfmFwd; } }

        /// <summary>
        /// [READONLY] The right direction of the turret in world space as a vector. Derived from the right direction of the transform.
        /// </summary>
        public Vector3 TransformRight { get { return trfmRight; } }

        /// <summary>
        /// [READONLY] The up direction of the turret in world space as a vector. Derived from the up direction of the transform.
        /// </summary>
        public Vector3 TransformUp { get { return trfmUp; } }

        /// <summary>
        /// [READONLY] The rotation of the turret in world space as a quaternion. Derived from the rotation of the transform.
        /// </summary>
        public Quaternion TransformRotation { get { return trfmRot; } }

        /// <summary>
        /// [READONLY] The inverse rotation of the turret in world space as a quaternion. Derived from the rotation of the transform.
        /// </summary>
        public Quaternion TransformInverseRotation { get { return trfmInvRot; } }

        /// <summary>
        /// [READONLY] The number used by the SSCRadar system to identify this Surface Turret at a point in time.
        /// This should not be stored across frames and is updated as required by the system.
        /// </summary>
        public int RadarId { get { return radarItemIndex; } }

        /// <summary>
        /// [READONLY] Has the surface turret been destroyed (typically when there is no weapon Health)
        /// </summary>
        public bool IsDestroyed { get; private set; }

        /// <summary>
        /// If a ship is being targeted, will return its name.
        /// </summary>
        public string TargetShipName { get { return weapon.targetShip != null ? weapon.targetShip.name : "-"; } }

        /// <summary>
        /// If a ship damage region is being targeted, will return its name.
        /// </summary>
        public string TargetShipDamageRegionName { get { return weapon.targetShipDamageRegion != null ? weapon.targetShipDamageRegion.name : "-"; } }

        /// <summary>
        /// If a gameobject is being targeted, will return its name
        /// </summary>
        public string TargetGameObjectName { get { return weapon.target != null ? (string.IsNullOrEmpty(weapon.target.name) ? "no name" : weapon.target.name) : "-"; } }

        /// <summary>
        /// [INTERNAL ONLY]
        /// </summary>
        public Weapon weapon;

        /// <summary>
        /// [INTERNAL ONLY]
        /// </summary>
        public bool allowRepaint = false;

        #endregion

        #region Public Delegates

        public delegate void CallbackOnDestroy(SurfaceTurretModule surfaceTurretModule);

        /// <summary>
        /// The name of the custom method that is called immediately
        /// before the turret is destroyed. Your method must take 1
        /// parameter of class SurfaceTurretModule. This should be a lightweight
        /// method to avoid performance issues. It could be used to update
        /// a score or affect the status of a mission.
        /// </summary>
        public CallbackOnDestroy callbackOnDestroy = null;

        #endregion

        #region Internal variables
        /// <summary>
        /// The ID number for this turret's destruction effects object prefab (as assigned by the SSCManager in the scene).
        /// This is the index in the SSCManager effectsObjectTemplatesList.
        /// [INTERNAL USE ONLY]
        /// </summary>
        internal int effectsObjectPrefabID = -1;

        /// <summary>
        /// Flag for whether the destruction effects object has been instantiated.
        /// [INTERNAL USE ONLY]
        /// </summary>
        internal bool isDestructionEffectsObjectInstantiated = false;

        /// <summary>
        /// The ID number for this turret's destruct prefab (as assigned by the SSCManager in the scene).
        /// This is the index in the SSCManager destructTemplateList.
        /// [INTERNAL USE ONLY]
        /// </summary>
        internal int destructObjectPrefabID = -1;

        /// <summary>
        /// Flag for whether the destruct object has been activated.
        /// [INTERNAL USE ONLY]
        /// </summary>
        internal bool isDestructObjectActivated = false;

        // Radar variables
        /// <summary>
        /// [INTERNAL USE ONLY]
        /// </summary>
        [System.NonSerialized] internal int radarItemIndex = -1;

        #endregion

        #region Private varibles
        private bool isInitialised = false;
        private SSCManager sscManager = null;
        private SSCRadar sscRadar = null;
        private LocationData locationData = null;

        /// <summary>
        /// [INTERNAL ONLY] - instead use FireIfReady()
        /// Attempt to manually fire the weapon
        /// </summary>
        private bool isManualFireNow = false;

        /// <summary>
        /// [INTERNAL ONLY] - instead use SetAutoFire()
        /// </summary>
        private bool isAutoFire = false;

        // Temp firing variables
        private Vector3 weaponWorldBasePosition = Vector3.zero;
        private Vector3 weaponWorldFirePosition = Vector3.zero;
        private Vector3 weaponWorldFireDirection = Vector3.zero;
        private Vector3 weaponRelativeFirePosition = Vector3.zero;
        private Vector3 weaponRelativeFireDirection = Vector3.zero;

        // cached data
        private Vector3 worldVelocity = Vector3.zero;
        private Vector3 worldAngularVelocity = Vector3.zero;
        private Vector3 trfmUp;
        private Vector3 trfmFwd;
        private Vector3 trfmRight;
        private Vector3 trfmPos;
        private Quaternion trfmRot;
        private Quaternion trfmInvRot;

        private int weaponTypeInt;

        #endregion

        #region Initialise Methods

        void Start()
        {
            if (initialiseOnStart) { Initialise(); }
        }

        /// <summary>
        /// Initialise the Turret
        /// </summary>
        public void Initialise()
        {
            // Only initialise once
            if (!isInitialised)
            {
                #if UNITY_EDITOR
                if (gameObject.GetComponent<ShipControlModule>())
                {
                    Debug.LogWarning("ERROR: This Turret Module is NOT designed to be used on a Ship. Please configure a Turret under Weapons on the Ship Control Module Combat tab.");
                    return;
                }
                #endif

                UpdatePositionData();

                if (weapon != null)
                {
                    // Before v1.2.3, surface turrets didn't set the weaponType in the editor.
                    if (weapon.weaponType != Weapon.WeaponType.TurretProjectile && weapon.weaponType != Weapon.WeaponType.TurretBeam) { weapon.weaponType = Weapon.WeaponType.TurretProjectile; }
                    weapon.Health = weapon.startingHealth;

                    // Added in v1.2.3
                    weapon.Initialise(trfmInvRot);

                    // Lookup the enumeration once
                    weaponTypeInt = weapon.weaponTypeInt;

                    // Get a reference to the Ship Controller Manager instance
                    sscManager = SSCManager.GetOrCreateManager();

                    // Initialise projectiles and effects objects
                    ReinitialiseTurretProjectilesAndEffects();

                    // Initialise beams and (beam) effects
                    ReinitialiseTurretBeams();

                    // Initialise destruct modules
                    ReinitialiseTurretDestructObjects();

                    // When initialising, there should never be an existing Location
                    if (autoCreateLocation) { CreateLocation(false); }
                    else if (isVisibleToRadar)
                    {
                        // No location created but visible to radar, so create as a RadarItemType.GameObject
                        if (sscRadar == null) { sscRadar = SSCRadar.GetOrCreateRadar(); }
                        if (sscRadar != null) { radarItemIndex = sscRadar.EnableRadar(gameObject, transform.position, factionId, squadronId, 0, radarBlipSize); }
                    }

                    isManualFireNow = false;
                    isAutoFire = weapon.firingButton == Weapon.FiringButton.AutoFire;

                    if (weapon.turretPivotY == null)
                    {
                        isInitialised = false;
                        #if UNITY_EDITOR
                        Debug.LogWarning("ERROR: The surface turret (" + gameObject.name + ") needs to be assigned a Pivot Y transform before it can fire projectiles." );
                        #endif
                    }
                    else if (weapon.turretPivotX == null)
                    {
                        isInitialised = false;
                        #if UNITY_EDITOR
                        Debug.LogWarning("ERROR: The surface turret (" + gameObject.name + ") needs to be assigned a Pivot X transform before it can fire projectiles." );
                        #endif
                    }
                    else
                    {
                        // If there is a DamageReceiver component attached, setup the callback.
                        DamageReceiver damageReceiver = GetComponent<DamageReceiver>();
                        if (damageReceiver != null) { damageReceiver.callbackOnHit = TakeDamage; }
                        isInitialised = true;
                    }
                }
            }
        }

        #endregion

        #region Update Methods
        // Update is called once per frame
        void Update()
        {
            UpdateWeapon();
        }

        #endregion

        #region Private and Internal Member Methods

        /// <summary>
        /// This gets called from FixedUpdate in BeamModule. It performs the following:
        /// 1. Checks if the beam should be despawned
        /// 2. Moves the beam
        /// 3. Changes the length
        /// 4. Checks if it hits anything
        /// 5. Updates damage on what it hits
        /// 6. Instantiate effects object at hit point
        /// 7. Consumes weapon power
        /// It needs to be a member of the surfaceTurretModule instance as it requires both turret and beam data.
        /// Assumes the beam linerenderer has useWorldspace enabled.
        /// </summary>
        /// <param name="beamModule"></param>
        internal void MoveBeam(BeamModule beamModule)
        {
            if (beamModule.isInitialised && beamModule.isBeamEnabled && beamModule.weaponIndex >= 0 && beamModule.firePositionIndex >= 0)
            {
                SSCBeamItemKey beamItemKey = weapon.beamItemKeyList[beamModule.firePositionIndex];

                if (weapon.weaponTypeInt == Weapon.TurretBeamInt && beamItemKey.beamSequenceNumber == beamModule.itemSequenceNumber)
                {
                    // Is ready to fire - taken from UpdateWeapon()
                    bool isReadyToFire = (isManualFireNow || isAutoFire) && weapon.isLockedOnTarget && (!weapon.checkLineOfSight || WeaponHasLineOfSight(weapon.target));

                    // Should this beam be despawned or returned to the pool?
                    // a) No charge in weapon
                    // b) Weapon has no health
                    // c) Weapon has no performance
                    // d) user stopped firing and has fired for min time permitted
                    // e) has exceeded the maximum firing duration
                    if (weapon.chargeAmount <= 0f || weapon.health <= 0f || weapon.currentPerformance == 0f || (!isReadyToFire && beamModule.burstDuration > beamModule.minBurstDuration) || beamModule.burstDuration > beamModule.maxBurstDuration)
                    {
                        // Unassign the beam from this weapon's fire position
                        weapon.beamItemKeyList[beamModule.firePositionIndex] = new SSCBeamItemKey(-1, -1, 0);

                        beamModule.DestroyBeam();
                    }
                    else
                    {
                        // Move the beam start
                        // Calculate the World-space Fire Position (like when weapon is first fired)
                        // Note: That the surface turret could have moved and rotated, so we recalc here.
                        Vector3 _weaponWSBasePos = trfmPos;
                        Vector3 _weaponWSFireDir = GetWeaponWorldFireDirection();
                        Vector3 _weaponWSFirePos = GetWeaponWorldFirePosition(_weaponWSBasePos, beamModule.firePositionIndex);

                        // Move the beam transform but keep the first LineRenderer position at 0,0,0
                        beamModule.transform.position = _weaponWSFirePos;

                        beamModule.transform.SetPositionAndRotation(_weaponWSFirePos, Quaternion.LookRotation(_weaponWSFireDir, trfmUp));

                        // Calc length and end position
                        float _desiredLength = beamModule.burstDuration * beamModule.speed;

                        if (_desiredLength > weapon.maxRange) { _desiredLength = weapon.maxRange; }

                        Vector3 _endPosition = _weaponWSFirePos + (_weaponWSFireDir * _desiredLength);

                        RaycastHit raycastHitInfo;

                        // Check if it hit anything
                        if (Physics.Linecast(_weaponWSFirePos, _endPosition, out raycastHitInfo, ~0, QueryTriggerInteraction.Ignore))
                        {
                            // Adjust the end position to the hit point
                            _endPosition = raycastHitInfo.point;

                            // Update damage if it hit a ship or an object with a damage receiver
                            if (!BeamModule.CheckShipHit(raycastHitInfo, beamModule, Time.deltaTime))
                            {
                                // If it hit an object with a DamageReceiver script attached, take appropriate action like call a custom method
                                BeamModule.CheckObjectHit(raycastHitInfo, beamModule, Time.deltaTime);
                            }

                            // ISSUES - the effect may not be visible while firing if:
                            // 1) if the effect despawn time is less than the beam max burst duration (We have a warning in the BeamModule editor)
                            // 2) if the effect does not have looping enabled (this could be more expensive to check)

                            // Add or Move the effects object
                            if (beamModule.effectsObjectPrefabID >= 0)
                            {
                                if (sscManager == null) { sscManager = SSCManager.GetOrCreateManager(); }

                                if (sscManager != null)
                                {
                                    // If the effect has not been spawned, do now
                                    if (beamModule.effectsItemKey.effectsObjectSequenceNumber == 0)
                                    {
                                        InstantiateEffectsObjectParameters ieParms = new InstantiateEffectsObjectParameters
                                        {
                                            effectsObjectPrefabID = beamModule.effectsObjectPrefabID,
                                            position = _endPosition,
                                            rotation = beamModule.transform.rotation
                                        };

                                        // Instantiate the hit effects
                                        if (sscManager.InstantiateEffectsObject(ref ieParms) != null)
                                        {
                                            if (ieParms.effectsObjectSequenceNumber > 0)
                                            {
                                                // Record the muzzle effect item key
                                                beamModule.effectsItemKey = new SSCEffectItemKey(ieParms.effectsObjectPrefabID, ieParms.effectsObjectPoolListIndex, ieParms.effectsObjectSequenceNumber);
                                            }
                                        }
                                    }
                                    // Move the existing effects object to the end of the beam
                                    else
                                    {
                                        // Currently we are not checking sequence number matching (for pooled effects) as it is faster and can
                                        // avoid doing an additional GetComponent().
                                        sscManager.MoveEffectsObject(beamModule.effectsItemKey, _endPosition, beamModule.transform.rotation, false);
                                    }
                                }
                            }
                        }
                        // The beam isn't hitting anything AND there is an active effects object
                        else if (beamModule.effectsObjectPrefabID >= 0 && beamModule.effectsItemKey.effectsObjectSequenceNumber > 0)
                        {
                            // Destroy the effects object or return it to the pool
                            if (sscManager == null) { sscManager = SSCManager.GetOrCreateManager(); }

                            if (sscManager != null)
                            {
                                sscManager.DestroyEffectsObject(beamModule.effectsItemKey);
                            }
                        }

                        // Move the beam end (assumes world space)
                        // useWorldspace is enabled in beamModule.InitialiseBeam(..)
                        beamModule.lineRenderer.SetPosition(0, _weaponWSFirePos);
                        beamModule.lineRenderer.SetPosition(1, _endPosition);

                        // Consume weapon power. Consumes upt to 2x power when overheating.
                        if (weapon.rechargeTime > 0f) { weapon.chargeAmount -= Time.deltaTime * (weapon.heatLevel > weapon.overHeatThreshold ? 2f - weapon.currentPerformance : 1f) / beamModule.dischargeDuration; }

                        weapon.ManageHeat(Time.deltaTime, 1f);

                        // If we run out of power, de-activate it before weapon starts recharging
                        if (weapon.chargeAmount <= 0f)
                        {
                            weapon.chargeAmount = 0f;

                            // Unassign the beam from this weapon's fire position
                            weapon.beamItemKeyList[beamModule.firePositionIndex] = new SSCBeamItemKey(-1, -1, 0);

                            beamModule.DestroyBeam();
                        }
                    }
                }
            }
            #if UNITY_EDITOR
            else { Debug.LogWarning("ERROR surfaceTurretModule.MoveBeam has been called on the wrong beam. isInitialised: " + beamModule.isInitialised + 
                  " isBeamEnabled: " + beamModule.isBeamEnabled + " weaponIndex: " + beamModule.weaponIndex + " firePositionIndex: " + beamModule.firePositionIndex); }
            #endif
        }

        /// <summary>
        /// If muzzle FX on this weapon are pooled and have been parented to the weapons,
        /// when a surface turret is destroyed, they need to be reparented to the pool.
        /// NOTE: This may impact GC (if this becomes a problem, we could precreate a list
        /// with the appropriate capacity.
        /// </summary>
        private void DestroyFX()
        {
            EffectsModule[] effectsModules = GetComponentsInChildren<EffectsModule>(true);

            int numEffectsModules = effectsModules == null ? 0 : effectsModules.Length;

            for (int emIdx = 0; emIdx < numEffectsModules; emIdx++)
            {
                EffectsModule effectsModule = effectsModules[emIdx];

                if (effectsModule.usePooling && effectsModule.isReparented)
                {
                    effectsModule.DestroyEffectsObject();
                }
            }
        }

        private Vector3 GetWeaponWorldFireDirection()
        {
            // Get relative fire direction
            weaponRelativeFireDirection = weapon.fireDirectionNormalised;

            // Adjust relative fire direction based on turret rotation
            // (turret rotation less parent object rotation) - (original turret rotation less original rotation) + relative fire direction
            weaponRelativeFireDirection = (trfmInvRot * weapon.turretPivotX.rotation) * weapon.intPivotYInvRot * weaponRelativeFireDirection;

            // Get weapon world fire direction
            return (trfmRight * weaponRelativeFireDirection.x) +
                   (trfmUp * weaponRelativeFireDirection.y) +
                   (trfmFwd * weaponRelativeFireDirection.z);
        }


        private Vector3 GetWeaponWorldFirePosition(Vector3 weaponWorldBasePosition, int firePositionIndex)
        {
            weaponRelativeFirePosition = weapon.relativePosition;

            // Check if there are multiple fire positions
            if (weapon.isMultipleFirePositions)
            {
                // Get relative fire position
                weaponRelativeFirePosition += weapon.firePositionList[firePositionIndex];
            }

            // Adjust relative fire position based on turret rotation
            weaponRelativeFirePosition = (trfmInvRot * weapon.turretPivotX.rotation) * weaponRelativeFirePosition;

            // Get weapon world fire position
            return weaponWorldBasePosition +
                (trfmRight * weaponRelativeFirePosition.x) +
                (trfmUp * weaponRelativeFirePosition.y) +
                (trfmFwd * weaponRelativeFirePosition.z);
        }

        /// <summary>
        /// Update cached values.
        /// NOTE: currently world velocity and angular velocity etc
        /// are NOT updated.
        /// </summary>
        private void UpdatePositionData()
        {
            // Update data obtained from transform
            trfmPos = transform.position;
            trfmFwd = transform.forward;
            trfmRight = transform.right;
            trfmUp = transform.up;
            trfmRot = transform.rotation;
            trfmInvRot = Quaternion.Inverse(trfmRot);
        }

        /// <summary>
        /// [INTERNAL ONLY]
        /// Update weapon and fire if ready
        /// Weapons that have no health, cannot aim, reload or fire.
        /// </summary>
        private void UpdateWeapon()
        {
            // Check that the weapon has had a projectile ID or beam ID assigned
            // Assumes that the IDs have been correctly assigned based on the weaponType
            if (isInitialised && (weapon.projectilePrefabID >= 0 || weapon.beamPrefabID >= 0))
            {
                float _deltaTime = Time.deltaTime;

                // Does the weapon need to cool down??
                weapon.ManageHeat(_deltaTime, 0f);

                // A weapon with no health or performance cannot rotate, reload, or fire.
                if (weapon.health > 0f && weapon.currentPerformance > 0f)
                {
                    weapon.MoveTurret(worldVelocity, true);

                    // Does the beam weapon need charging?
                    if (weaponTypeInt == Weapon.TurretBeamInt && weapon.rechargeTime > 0f && weapon.chargeAmount < 1f)
                    {
                        weapon.chargeAmount += _deltaTime * 1f / weapon.rechargeTime;
                        if (weapon.chargeAmount > 1f) { weapon.chargeAmount = 1f; }
                    }

                    // Check if this projectile weapon is reloading or not
                    // Check if this beam weapon is powering-up or not
                    if (weapon.reloadTimer > 0f)
                    {
                        // If reloading (or powering-up), update reload timer
                        weapon.reloadTimer -= _deltaTime;
                    }
                    else if (weapon.ammunition != 0)
                    {
                        // Is turret locked on target and in direct line of sight?
                        // NOTE: If check LoS is enabled, it will still fire if another enemy is between the turret and the weapon.target.
                        bool isReadyToFire = (isManualFireNow || isAutoFire) && weapon.isLockedOnTarget && (!weapon.checkLineOfSight || WeaponHasLineOfSight(weapon.target));

                        if (isReadyToFire)
                        {
                            // Get base weapon world position (not accounting for relative fire position)
                            weaponWorldBasePosition = trfmPos;

                            // Get relative fire direction
                            weaponRelativeFireDirection = weapon.fireDirectionNormalised;
                            // Adjust relative fire direction based on turret rotation
                            // (turret rotation less parent object rotation) - (original turret rotation less original rotation) + relative fire direction
                            weaponRelativeFireDirection = (trfmInvRot * weapon.turretPivotX.rotation) * weapon.intPivotYInvRot * weaponRelativeFireDirection;

                            // Get weapon world fire direction
                            weaponWorldFireDirection = (trfmRight * weaponRelativeFireDirection.x) +
                                        (trfmUp * weaponRelativeFireDirection.y) +
                                        (trfmFwd * weaponRelativeFireDirection.z);

                            // Start the firing cycle with no heat generated
                            float heatValue = 0f;

                            // Loop through all fire positions
                            int firePositionListCount = weapon.isMultipleFirePositions ? weapon.firePositionList.Count : 1;
                            for (int fp = 0; fp < firePositionListCount; fp++)
                            {
                                // Only fire if there is unlimited ammunition (-1) or greater than 0 projectiles available
                                if (weapon.ammunition != 0)
                                {
                                    // If not unlimited ammo, decrement the quantity available
                                    if (weapon.ammunition > 0) { weapon.ammunition--; }

                                    // Get relative fire position
                                    weaponRelativeFirePosition = weapon.relativePosition;
                                    // If there are multiple fire positions, add the relative fire position
                                    if (weapon.isMultipleFirePositions)
                                    {
                                        weaponRelativeFirePosition += weapon.firePositionList[fp];
                                    }

                                    // Adjust relative fire position based on turret rotation
                                    //weaponRelativeFirePosition = (trfmInvRot * weapon.turretPivotX.rotation) * weapon.intPivotYInvRot * weaponRelativeFirePosition;
                                    weaponRelativeFirePosition = (trfmInvRot * weapon.turretPivotX.rotation) * weaponRelativeFirePosition;

                                    // Get weapon world fire position
                                    weaponWorldFirePosition = weaponWorldBasePosition +
                                        (trfmRight * weaponRelativeFirePosition.x) +
                                        (trfmUp * weaponRelativeFirePosition.y) +
                                        (trfmFwd * weaponRelativeFirePosition.z);

                                    // Create a new beam or projectile using the SSCManager
                                    // Velocity is world velocity of SurfaceTurretModule plus relative velocity of weapon due to angular velocity
                                    if (weaponTypeInt == Weapon.TurretBeamInt)
                                    {
                                        // if the sequence number is 0, it means it is not active
                                        if (weapon.beamItemKeyList[fp].beamSequenceNumber == 0)
                                        {
                                            InstantiateBeamParameters ibParms = new InstantiateBeamParameters()
                                            {
                                                beamPrefabID = weapon.beamPrefabID,
                                                position = weaponWorldFirePosition,
                                                fwdDirection = weaponWorldFireDirection,
                                                upDirection = trfmUp,
                                                shipId = 0,
                                                squadronId = squadronId,
                                                weaponIndex = 0,
                                                firePositionIndex = fp,
                                                beamSequenceNumber = 0,
                                                beamPoolListIndex = -1
                                            };

                                            // Create a beam using the ship control Manager (SSCManager)
                                            // Pass InstantiateBeamParameters by reference so we can get the beam index and sequence number back
                                            BeamModule beamModule = sscManager.InstantiateBeam(ref ibParms);
                                            if (beamModule != null)
                                            {
                                                beamModule.callbackOnMove = MoveBeam;
                                                // Retrieve the unique identifiers for the beam instance
                                                weapon.beamItemKeyList[fp] = new SSCBeamItemKey(weapon.beamPrefabID, ibParms.beamPoolListIndex, ibParms.beamSequenceNumber);
                                                // Immediately update the beam position (required for pooled beams that have previously been used)
                                                MoveBeam(beamModule);

                                                // Was a poolable muzzle fx spawned?
                                                if (beamModule.muzzleEffectsItemKey.effectsObjectSequenceNumber > 0)
                                                {
                                                    // Only get the transform if the muzzle EffectsModule has Is Reparented enabled.
                                                    Transform muzzleFXTrfm = sscManager.GetEffectsObjectTransform(beamModule.muzzleEffectsItemKey.effectsObjectTemplateListIndex, beamModule.muzzleEffectsItemKey.effectsObjectPoolListIndex, true);

                                                    if (muzzleFXTrfm != null)
                                                    {
                                                        muzzleFXTrfm.SetParent(beamModule.transform);
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    else
                                    {
                                        InstantiateProjectileParameters ipParms = new InstantiateProjectileParameters()
                                        {
                                            projectilePrefabID = weapon.projectilePrefabID,
                                            position = weaponWorldFirePosition,
                                            fwdDirection = weaponWorldFireDirection,
                                            upDirection = trfmUp,
                                            weaponVelocity = worldVelocity + Vector3.Cross(worldAngularVelocity, weaponWorldFirePosition - trfmPos),
                                            gravity = gravitationalAcceleration,
                                            gravityDirection = gravityDirection,
                                            shipId = 0,
                                            squadronId = -1,
                                            targetShip = weapon.isProjectileKGuideToTarget ? weapon.targetShip : null,
                                            targetGameObject = weapon.isProjectileKGuideToTarget ? weapon.target : null,
                                            targetguidHash = weapon.isProjectileKGuideToTarget ? weapon.targetguidHash : 0,
                                        };

                                        sscManager.InstantiateProjectile(ref ipParms);

                                        // For now, assume projectile was instantiated
                                        // Heat value is inversely proportional to the firing interval (reload time)
                                        if (weapon.reloadTime > 0f) { heatValue += 1f / weapon.reloadTime; };

                                        // Was a muzzle fx spawned?
                                        if (ipParms.muzzleEffectsObjectPoolListIndex >= 0)
                                        {
                                            // Only get the transform if the muzzle EffectsModule has Is Reparented enabled.
                                            Transform muzzleFXTrfm = sscManager.GetEffectsObjectTransform(ipParms.muzzleEffectsObjectPrefabID, ipParms.muzzleEffectsObjectPoolListIndex, true);

                                            if (muzzleFXTrfm != null)
                                            {
                                                muzzleFXTrfm.SetParent(weapon.turretPivotX);
                                            }
                                        }
                                    }

                                    // Set reload timer to reload time
                                    weapon.reloadTimer = weapon.reloadTime;
                                }
                            }

                            if (heatValue > 0f) { weapon.ManageHeat(_deltaTime, heatValue); }
                        }
                    }
                }
                isManualFireNow = false;
            }
        }

        #endregion

        #region Internal Public Methods

        /// <summary>
        /// [INTERNAL ONLY]
        /// When DamageReceiver component is attached, this routine is called by Sci-Fi Ship Controller when a projectile hits it.
        /// If it is destroyed, it will no longer appear on radar.
        /// </summary>
        /// <param name="callbackOnObjectHitParameters"></param>
        public void TakeDamage(CallbackOnObjectHitParameters callbackOnObjectHitParameters)
        {
            if (isInitialised)
            {
                ProjectileModule projectile = callbackOnObjectHitParameters.projectilePrefab;
                if (projectile != null)
                {
                    weapon.Health -= projectile.damageAmount;

                    // Uncomment if you want to debug in the editor
                    //#if UNITY_EDITOR
                    //Debug.Log("Projectile: " + projectile.name + " hit " + gameObject.name +  " with damage amount of " + projectile.damageAmount);
                    //#endif
                }
                // Must have been hit by a (laser) beam
                else
                {
                    weapon.Health -= callbackOnObjectHitParameters.damageAmount;
                }

                if (weapon.Health <= 0f)
                {
                    if (!IsDestroyed && callbackOnDestroy != null) { callbackOnDestroy.Invoke(this); }
                    IsDestroyed = true;

                    DeactivateBeams(sscManager);

                    if (!isDestructionEffectsObjectInstantiated && effectsObjectPrefabID >= 0 && destructionEffectsObject != null && sscManager != null)
                    {
                        // Instantiate the turret destruction effects prefab
                        InstantiateEffectsObjectParameters ieParms = new InstantiateEffectsObjectParameters
                        {
                            effectsObjectPrefabID = effectsObjectPrefabID,
                            position = transform.position,
                            rotation = transform.rotation
                        };

                        sscManager.InstantiateEffectsObject(ref ieParms);
                        isDestructionEffectsObjectInstantiated = true;
                    }

                    // If this turret has a set Location, remove it
                    if (locationData != null)
                    {
                        if (isVisibleToRadar) { sscManager.DisableRadar(locationData); }
                        sscManager.DeleteLocation(locationData);
                        radarItemIndex = -1;
                        isVisibleToRadar = false;
                    }

                    // Was the turret added to radar as RadarItemType.GameObject?
                    if (isVisibleToRadar && radarItemIndex >= 0 && sscRadar != null)
                    {
                        sscRadar.DisableRadar(radarItemIndex);
                        radarItemIndex = -1;
                        isVisibleToRadar = false;
                    }

                    if (isDestroyOnNoHealth)
                    {
                        // Is there a destruct prefab?
                        if (!isDestructObjectActivated && destructObjectPrefabID >= 0 && destructObject != null && sscManager != null)
                        {
                            // Turn off all colliders. As we are going to destroy the gameobject,
                            // we can simply deactivate it.
                            gameObject.SetActive(false);

                            // Instantiate the turret destruct prefab
                            InstantiateDestructParameters dstParms = new InstantiateDestructParameters
                            {
                                destructPrefabID = destructObjectPrefabID,
                                position = transform.position,
                                //position = transform.position + (Vector3.up * 20f),
                                rotation = transform.rotation,
                                explosionPowerFactor = 1f,
                                explosionRadiusFactor = 1f
                            };

                            sscManager.InstantiateDestruct(ref dstParms);
                            isDestructObjectActivated = true;
                        }

                        DestroyFX();

                        Destroy(gameObject);
                    }
                }
            }
        }

        #endregion

        #region Public API Methods

        /// <summary>
        /// Deactivate any beams that the weapon is currently firing
        /// </summary>
        public void DeactivateBeams (SSCManager sscManager)
        {
            if (sscManager != null && weapon != null && weaponTypeInt == Weapon.TurretBeamInt)
            {
                int numBeams = weapon.beamItemKeyList == null ? 0 : weapon.beamItemKeyList.Count;

                for (int bItemIdx = 0; bItemIdx < numBeams; bItemIdx++)
                {
                    if (weapon.beamItemKeyList[bItemIdx].beamSequenceNumber > 0)
                    {
                        sscManager.DeactivateBeam(weapon.beamItemKeyList[bItemIdx]);
                    }

                    weapon.beamItemKeyList[bItemIdx] = new SSCBeamItemKey(-1, -1, 0);
                }
            }
        }

        /// <summary>
        /// Reinitialises variables required for surface turret beam weapon and effects used by those beams.
        /// Call this after modifying any beams or beam effect data for this surface turret.
        /// </summary>
        public void ReinitialiseTurretBeams()
        {
            if (sscManager != null)
            {
                // Initialise beams, and effects objects used by those beams
                sscManager.UpdateBeamsAndEffects(this);
            }
            #if UNITY_EDITOR
            else
            {
                Debug.LogWarning("SurfaceTurretModule.ReinitialiseTurretBeams Warning: could not find SSCManager to update (weapon) beams");
            }
            #endif
        }

        /// <summary>
        /// Reinitialises variables required for projectiles and effects of the surface turret.
        /// Call after modifying any projectile or effect data for this surface turret.
        /// </summary>
        public void ReinitialiseTurretProjectilesAndEffects ()
        {
            if (sscManager == null) { sscManager = SSCManager.GetOrCreateManager(); }
            if (sscManager != null)
            {
                // Initialise projectiles and effects objects
                sscManager.UpdateProjectilesAndEffects(this);
            }
            #if UNITY_EDITOR
            else
            {
                Debug.LogWarning("SurfaceTurretModule.ReinitialiseTurretProjectilesAndEffects Warning: could not find SSCManager to update projectiles and effects.");
            }
            #endif
        }

        /// <summary>
        /// Reinitialises variables required for destruct objects of the surface turret.
        /// Call after modifying any destruct data for this surface turret.
        /// </summary>
        public void ReinitialiseTurretDestructObjects ()
        {
            if (sscManager == null) { sscManager = SSCManager.GetOrCreateManager(); }
            if (sscManager != null)
            {
                // Initialise destruct objects
                sscManager.UpdateDestructObjects(this);
            }
            #if UNITY_EDITOR
            else
            {
                Debug.LogWarning("SurfaceTurretModule.ReinitialiseTurretDestructObjects Warning: could not find SSCManager to update destruct objects.");
            }
            #endif
        }

        /// <summary>
        /// Create a new Location using the SSCManager and add it to Radar
        /// if required.
        /// </summary>
        /// <param name="removeExisting"></param>
        public void CreateLocation(bool removeExisting)
        {
            if (sscManager == null) { sscManager = SSCManager.GetOrCreateManager(); }
            if (sscManager != null)
            {
                if (removeExisting && locationData != null)
                {
                    sscManager.DeleteLocation(locationData);
                }

                locationData = sscManager.AppendLocation(this.gameObject);

                if (locationData != null)
                {
                    locationData.factionId = factionId;
                    locationData.radarBlipSize = radarBlipSize;
                    if (isVisibleToRadar) { sscManager.EnableRadar(locationData); }

                    // If isn't visible to radar (for whatever reason) it will be -1.
                    radarItemIndex = locationData.radarItemIndex;
                }
            }
        }

        /// <summary>
        /// Clears all targeting information for the weapon. This should be called if you do not know if the target
        /// is a ship or a gameobject.
        /// </summary>
        public void ClearWeaponTarget()
        {
            if (isInitialised) { weapon.ClearTarget(); }
        }

        /// <summary>
        /// The turret will track this gameobject.
        /// </summary>
        /// <param name="target"></param>
        public void SetWeaponTarget(GameObject target)
        {
            if (isInitialised) { weapon.SetTarget(target); }
        }

        /// <summary>
        /// The turret will track this ship
        /// </summary>
        /// <param name="targetShipControlModule"></param>
        public void SetWeaponTargetShip(ShipControlModule targetShipControlModule)
        {
            if (isInitialised) { weapon.SetTargetShip(targetShipControlModule); }
        }

        /// <summary>
        /// The turret will track this ship's localised damage region
        /// </summary>
        /// <param name="targetShipControlModule"></param>
        /// <param name="damageRegion"></param>
        public void SetWeaponTargetShipDamageRegion (ShipControlModule targetShipControlModule, DamageRegion damageRegion)
        {
            if (isInitialised) { weapon.SetTargetShipDamageRegion(targetShipControlModule, damageRegion); }
        }

        /// <summary>
        /// Fire all cannons on the weapon if they are loaded
        /// and ready. This is a single shot action. For continuous
        /// firing, call SetAutoFire().
        /// </summary>
        public void FireIfReady()
        {
            if (isInitialised && !isAutoFire)
            {
                isManualFireNow = true;
            }
        }

        /// <summary>
        /// Sets the weapon to automatically fire if a target is acquired
        /// and the weapon is ready.
        /// </summary>
        public void SetAutoFire()
        {
            isManualFireNow = false;

            if (weapon != null)
            {
                weapon.firingButton = Weapon.FiringButton.AutoFire;
                isAutoFire = true;
            }
        }

        /// <summary>
        /// For manually firing the weapon. After this is set,
        /// call FireIfReady() to fire the weapon.
        /// </summary>
        public void SetManualFire()
        {
            isManualFireNow = false;

            if (weapon != null)
            {
                weapon.firingButton = Weapon.FiringButton.None;
                isAutoFire = false;
            }
        }

        /// <summary>
        /// Returns whether a weapon has line of sight to a target.
        /// If directToTarget is set to true, will raycast directly from the weapon to the target.
        /// If directToTarget is set to false, will raycast in the direction the weapon is facing.
        /// This method will return true if the raycast hits:
        /// a) The target,
        /// b) An enemy ship (distinguished by faction ID) - even if it is not the target and anyEnemy is true,
        /// c) An object that isn't the target (if obstaclesBlockLineOfSight is set to false),
        /// d) Nothing.
        /// This method will return false if the raycast hits:
        /// a) A friendly ship (distinguished by faction ID),
        /// b) An object that isn't the target (if obstaclesBlockLineOfSight is set to true).
        /// c) An enemy ship that is not the target when anyEnemy is false 
        /// </summary>
        /// <param name="target"></param>
        /// <param name="directToTarget"></param>
        /// <param name="obstaclesBlockLineOfSight"></param>
        /// <param name="anyEnemy"></param>
        /// <returns></returns>
        public bool WeaponHasLineOfSight(GameObject target, bool directToTarget = false, bool obstaclesBlockLineOfSight = true, bool anyEnemy = true)
        {
            if (target == null || !isInitialised) { return false; }

            // Update the line-of-sight property
            weapon.UpdateLineOfSight(target, trfmPos, trfmRight, trfmUp, trfmFwd, trfmInvRot, factionId,
                true, directToTarget, obstaclesBlockLineOfSight, anyEnemy);

            // Return the updated property
            return weapon.HasLineOfSight;
        }


        #endregion
    }
}