using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

namespace Aerostats
{
    public class ModuleBalloon : PartModule
    {
        /// <summary>
        /// Volume at which the balloon is full. Trying to add more gas, or simply having already stored gas expand due to lower exterior pressure or increased temperature, will cause gas to be vented through the security valve.
        /// Air density at sea level is about 1.2kg/m^3, which means a 1000m^3 balloon will be able to lift about 1 ton at sea level (after substracting the balloon gas weight)
        /// </summary>
        [KSPField(isPersistant = false, guiActive = false)]
        public float MaxVolume = 15000;

        /// <summary>
        /// Drag coefficient of the balloon
        /// </summary>
        [KSPField(isPersistant = false, guiActive = false)]
        public float DragCoeff = 0.3f;

        public float GasMolarMass { get; private set; }

        /// <summary>
        /// Mass of the empty balloon, in kg
        /// </summary>
        public float EmptyMass { get; private set; }

        public float LiftingGasQuantity { get; private set; }
        public float CurrentVolume { get; private set; }
        public float Lift { get; private set; }
        public bool IsVenting { get; private set; }

        /// <summary>
        /// Inflation ratio of the balloon (0=empty, 1=maximum)
        /// </summary>
        private float Inflation = 0;

        public float Radius { get; private set; }

        public ModuleBalloonCase AttachedCase { get; private set; }

        /// <summary>
        /// Must be called right after creating a balloon
        /// </summary>
        public void Initialize(ModuleBalloonCase attachedCase, Vector3 initialVelocity, float gasDensity, float initialGasQuantity)
        {
            AttachedCase = attachedCase;
            GasMolarMass = gasDensity / GasUtilities.StpVolumeToMoles;
            EmptyMass = part.mass * 1000.0f;

            // replace mesh and collider by a sphere (hack until proper assets are created)
            var sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            gameObject.GetComponentInChildren<MeshFilter>().mesh = sphere.GetComponent<MeshFilter>().mesh;
            Destroy(gameObject.collider);
            var collider = gameObject.AddComponent<SphereCollider>();
            collider.radius = 0.67f; // don't ask me, I don't know why
            Destroy(sphere);

            gameObject.AddComponent<Rigidbody>();
            rigidbody.mass = EmptyMass * 0.001f;
            rigidbody.velocity = initialVelocity; // start with the same velocity or everything explodes when deploying from a moving vessel
            rigidbody.angularDrag = 10.0f;
            rigidbody.position = transform.position;

            InjectGas(initialGasQuantity);

            part.OnJustAboutToBeDestroyed += OnPartDestroyed;
        }

        public void OnDetached()
        {
            AttachedCase = null;
        }

        private void OnPartDestroyed()
        {
            if(AttachedCase != null)
                AttachedCase.Cut();

            part.OnJustAboutToBeDestroyed -= OnPartDestroyed;
        }

        /// <summary>
        /// Injects (positive quantity) or removes (negative quantity) gas, and returns the contained quantity after that operation
        /// </summary>
        public float InjectGas(float quantity)
        {
            LiftingGasQuantity += quantity;

            float currentMaxQuantity = GetCurrentMaxQuantity();

            // balloon security valve
            if (LiftingGasQuantity > currentMaxQuantity)
            {
                IsVenting = true;
                Util.PostDebugSingleScreenMessage("security valve", "Some gas has been vented by the balloon security valve");
                LiftingGasQuantity = currentMaxQuantity;
            }
            else
            {
                IsVenting = false;
            }

            Vector3d worldPos = vessel.GetWorldPos3D();
            CelestialBody body = vessel.mainBody;

            float externalTemperature = (float)FlightGlobals.getExternalTemperature(worldPos, body);
            float balloonInternalTemperature = externalTemperature;
            float externalPressure = Math.Max((float)FlightGlobals.getStaticPressure(worldPos, body) * 1000.0f, 0.00001f);

            float currentGasMoles = GasUtilities.StpVolumeToMoles * LiftingGasQuantity;
            CurrentVolume = currentGasMoles * GasUtilities.R * balloonInternalTemperature / externalPressure;
            Inflation = CurrentVolume / MaxVolume;

            Util.PostDebugSingleScreenMessage("inflation", "Inflation = " + (Inflation * 100.0f).ToString("0.00") + "%");

            float airDensity = (float)FlightGlobals.getAtmDensity(externalPressure / 1000.0f, externalTemperature, body);
            Lift = CurrentVolume * airDensity;
            float balloonGasMass = GasMolarMass * currentGasMoles;
            Util.PostDebugSingleScreenMessage("lift", "Air density = " + airDensity + "kg/m^3, Lift = " + (Lift - balloonGasMass) + "kg");

            // V = 4/3*pi*r^3
            Radius = Mathf.Pow(CurrentVolume * 0.75f / Mathf.PI, 0.333f);
            float scale = Radius + 0.1f;
            transform.localScale = new Vector3(scale, scale, scale);
            rigidbody.mass = (EmptyMass + balloonGasMass) * 0.001f;

            return LiftingGasQuantity;
        }

        /// <summary>
        /// Computes the maximum gas quantity that the balloon can current hold, given the external temperature and pressure conditions
        /// </summary>
        public float GetCurrentMaxQuantity()
        {
            Vector3d worldPos = vessel.GetWorldPos3D();
            CelestialBody body = vessel.mainBody;

            float externalTemperature = (float)FlightGlobals.getExternalTemperature(worldPos, body);
            float balloonInternalTemperature = externalTemperature;
            float externalPressure = Math.Max((float)FlightGlobals.getStaticPressure(worldPos, body) * 1000.0f, 0.00001f);
            Util.PostDebugSingleScreenMessage("external atmo", "Temperature = " + externalTemperature + "K, Pressure=" + externalPressure + "Pa");

            float currentMaxQuantity = MaxVolume / GasUtilities.R / balloonInternalTemperature * externalPressure / GasUtilities.StpVolumeToMoles;

            return currentMaxQuantity;
        }

        private void FixedUpdate()
        {
            Vector3d worldPos = vessel.GetWorldPos3D();
            CelestialBody body = vessel.mainBody;

            rigidbody.WakeUp();

            InjectGas(0); // update security valve venting

            var gravityAccel = FlightGlobals.getGeeForceAtPosition(worldPos);
            //Util.PostSingleScreenMessage("gravity", "Gravity accel = (" + gravityAccel.x + ", " + gravityAccel.y + ", " + gravityAccel.z + ")");

            float externalTemperature = (float)FlightGlobals.getExternalTemperature(worldPos, body);
            float balloonInternalTemperature = externalTemperature;
            float externalPressure = Math.Max((float)FlightGlobals.getStaticPressure(worldPos, body) * 1000.0f, 0.00001f);
            float airDensity = (float)FlightGlobals.getAtmDensity(externalPressure / 1000.0f, externalTemperature, body);

            rigidbody.AddForce(-gravityAccel * Lift / 1000.0f, ForceMode.Force);

            // balloon drag
            var airVelocity = rigidbody.velocity + Krakensbane.GetFrameVelocity() /*- vessel.mainBody.getRFrmVel(vessel.GetWorldPos3D())*/;
            float sqVel = (float)airVelocity.magnitude;
            sqVel *= sqVel;
            float dragForce = 0.5f * airDensity * sqVel * DragCoeff * (Mathf.PI * Radius * Radius);
            Util.PostDebugSingleScreenMessage("balloon drag", "Drag = " + dragForce + "N");
            rigidbody.AddForce(-airVelocity.normalized * dragForce / 1000.0f, ForceMode.Force);
        }
    }
}
