using System;
using System.Collections.Generic;
using FluidSim.Interfaces;

namespace FluidSim.Components
{
    public class Volume0D : IComponent
    {
        public List<Port> Ports { get; } = new List<Port>();

        private double _airMass;
        private double _exhaustMass;
        public double InternalEnergy { get; set; }
        public double Volume { get; set; }
        public double Dvdt { get; set; }
        public double Gamma { get; set; } = 1.4;
        public double GasConstant { get; set; } = 287.0;

        public double AmbientPressure { get; set; } = 101325.0;

        // Derived quantities
        public double Mass => _airMass + _exhaustMass;
        public double AirFraction => _airMass / Math.Max(Mass, 1e-12);
        public double Density => Mass / Math.Max(Volume, 1e-12);
        public double Pressure => (Gamma - 1.0) * InternalEnergy / Math.Max(Volume, 1e-12);
        public double Temperature => Pressure / Math.Max(Density * GasConstant, 1e-12);
        public double SpecificEnthalpy => Gamma / (Gamma - 1.0) * Pressure / Math.Max(Density, 1e-12);

        public Volume0D(double initialVolume, double initialPressure,
                        double initialTemperature, double gasConstant = 287.0, double gamma = 1.4)
        {
            GasConstant = gasConstant;
            Gamma = gamma;
            Volume = initialVolume;
            Dvdt = 0.0;

            double rho0 = initialPressure / (GasConstant * initialTemperature);
            _airMass = rho0 * Volume;   // starts with all air
            _exhaustMass = 0.0;
            InternalEnergy = (initialPressure * Volume) / (Gamma - 1.0);
        }

        public Port CreatePort()
        {
            var port = new Port { Owner = this };
            port.Pressure = Pressure;
            port.Density = Density;
            port.Temperature = Temperature;
            port.SpecificEnthalpy = SpecificEnthalpy;
            port.AirFraction = AirFraction;
            Ports.Add(port);
            return port;
        }

        public void SetPressure(double pressure, double? temperature = null)
        {
            double V = Math.Max(Volume, 1e-12);
            double T = temperature ?? Temperature;
            double rho = pressure / (GasConstant * T);
            double totalMass = rho * V;
            // Keep current air fraction when setting pressure?
            double af = AirFraction;
            _airMass = totalMass * af;
            _exhaustMass = totalMass * (1.0 - af);
            InternalEnergy = pressure * V / (Gamma - 1.0);
        }

        public void UpdateState(double dt)
        {
            double totalMdotAir = 0.0;
            double totalMdotExhaust = 0.0;
            double totalEdot = 0.0;

            foreach (var port in Ports)
            {
                double mdot = port.MassFlowRate;   // positive INTO volume
                double af = mdot >= 0 ? port.AirFraction : AirFraction;   // inflow: use port's fraction; outflow: well-mixed
                totalMdotAir += mdot * af;
                totalMdotExhaust += mdot * (1.0 - af);
                totalEdot += mdot * port.SpecificEnthalpy;
            }

            double dAir = totalMdotAir * dt;
            double dExhaust = totalMdotExhaust * dt;
            double dE = totalEdot * dt - Pressure * Dvdt * dt;

            _airMass += dAir;
            _exhaustMass += dExhaust;
            InternalEnergy += dE;

            double V = Math.Max(Volume, 1e-12);
            double totalMass = _airMass + _exhaustMass;
            if (totalMass < 1e-9)
            {
                _airMass = 1e-9;
                _exhaustMass = 0.0;
                InternalEnergy = AmbientPressure * V / (Gamma - 1.0);
            }
            else if (InternalEnergy < 0.0)
            {
                InternalEnergy = AmbientPressure * V / (Gamma - 1.0);
            }

            if (_airMass < 0.0) _airMass = 0.0;
            if (_exhaustMass < 0.0) _exhaustMass = 0.0;

            double p = Pressure, rho = Density, T = Temperature, h = SpecificEnthalpy, afrac = AirFraction;
            foreach (var port in Ports)
            {
                port.Pressure = p;
                port.Density = rho;
                port.Temperature = T;
                port.SpecificEnthalpy = h;
                port.AirFraction = afrac;
            }
        }

        IReadOnlyList<Port> IComponent.Ports => Ports;
    }
}