// Airlock logic.
//
// We don't use a Sequencer for Airlocks for a number of reasons. Most notably:
//
// * One door in each cycle needs to be both closed and then opened.
// * The lights (and in the future, displays) need to emit signal information at several stages.
//
// We could add functionality to the Sequencer for these use cases, but right now we'll keep the logic separate,
// even though *for the most part* it follows the same "Enumerator that waits for various bits of state" logic.

using Sandbox.ModAPI.Ingame;
using SpaceEngineers.Game.ModAPI.Ingame;
using System.Collections.Generic;
using VRage.Game.ModAPI.Ingame.Utilities;
using VRageMath;

namespace IngameScript
{
    partial class Program
    {
        public class Airlock
        {
            enum AirlockLightState
            {
                Off,
                Cycling,
                Cooldown,
                Error,
            }

            public bool DoorsClosed
            {
                get
                {
                    return _innerDoor.Status == DoorStatus.Closed && _outerDoor.Status == DoorStatus.Closed;
                }
            }

            // TODO: this should check for depressurizing to the *reference* amount
            public bool PressureStabilized
            {
                get
                {
                    return ((_airVent.Depressurize && _airVent.GetOxygenLevel() <= _targetOxygenLevel) ||
                            (!_airVent.Depressurize && _airVent.Status == VentStatus.Pressurized));
                }
            }

            public bool OxygenBalanced
            {
                get { return _oxygenTank.FilledRatio > 0.25; }
            }

            public bool Functional
            {
                get
                {
                    return (_innerDoor != null && _outerDoor != null && _airVent != null && _oxygenTank != null);
                }
            }

            public bool Cycling { get; private set; } = false;

            public IMyAirVent ReferenceVent { get; set; }

            private bool DoorOpened
            {
                get
                {
                    return _innerDoor.Status == DoorStatus.Open || _outerDoor.Status == DoorStatus.Open;
                }
            }

            private string _name;
            private PrefixedConsole _console;
            private MyIni _ini;

            private List<IMyLightingBlock> _lights = new List<IMyLightingBlock>();
            private IMyDoor _innerDoor;
            private IMyDoor _outerDoor;
            private IMyGasTank _oxygenTank;
            private IMyAirVent _airVent;

            private float _targetOxygenLevel = 0.0F;

            private const int CooldownTicks = 12;
            private const int SealTimeoutTicks = 30;

            public Airlock(string name, Program _program)
            {
                _ini = _program.Ini;
                _name = name;
                _console = new PrefixedConsole(_program.Console, _name);
            }

            public void AddBlock(IMyTerminalBlock block)
            {
                if (block is IMyDoor) addDoor(block as IMyDoor);
                else if (block is IMyLightingBlock) _lights.Add(block as IMyLightingBlock);
                else if (block is IMyAirVent) addVent(block as IMyAirVent);
                else if (block is IMyGasTank) addOxygenTank(block as IMyGasTank);
                else _console.Print($"Tried to add invalid block '{block.CustomName}'");
            }

            // Precondition: _ini.TryParse() should be called on the block before calling this function.
            private void addDoor(IMyDoor door)
            {
                if (_ini.Get("airlock", "doorPosition").ToString() == "inner" && _innerDoor == null)
                {
                    _innerDoor = door;
                    return;
                }
                if (_ini.Get("airlock", "doorPosition").ToString() == "outer" && _outerDoor == null)
                {
                    _outerDoor = door;
                    return;
                }
                _console.Print($"Couldn't add door '{door.CustomName}'");
            }

            // Precondition: _ini.TryParse() should be called on the block before calling this function.
            private void addVent(IMyAirVent vent)
            {
                if (_airVent == null)
                {
                    _airVent = vent;
                    return;
                }
                _console.Print($"Couldn't add air vent '{vent.CustomName}'");
            }

            private void addOxygenTank(IMyGasTank tank)
            {
                if (_oxygenTank == null)
                {
                    _oxygenTank = tank;
                    return;
                }
                _console.Print($"Couldn't add oxygen tank '{tank.CustomName}'");
            }

            public IEnumerator<bool> CycleAirlock()
            {
                if (Cycling) yield break;
                Cycling = true;
                setLights(AirlockLightState.Cycling);

                closeDoors();
                while (!DoorsClosed) { yield return true; }
                lockDoors();

                int ticks = 0;
                while (!_airVent.CanPressurize && ticks < SealTimeoutTicks)
                {
                    ticks++;
                    yield return true;
                }

                if (!_airVent.CanPressurize)
                {
                    error("Airlock is not airtight.");
                    Cycling = false;
                    yield break;
                }

                pressurizeDepressurize();
                while (!PressureStabilized) { yield return true; }
                _airVent.Enabled = false;

                openDoor();
                while (!DoorOpened) { yield return true; }
                lockDoors();

                // Balance oxygen storage
                setLights(AirlockLightState.Cooldown);
                if (balanceOxygen())
                {
                    while (!OxygenBalanced) { yield return true; }
                }
                _airVent.Enabled = false;

                // Cooldown period
                int cooldown = 0;
                while (cooldown < CooldownTicks)
                {
                    cooldown++;
                    yield return true;
                }

                setLights(AirlockLightState.Off);
                _console.Print("Cycling Complete.");
                Cycling = false;
            }

            private void closeDoors()
            {
                _console.Print("Closing Doors");

                // close the doors
                _innerDoor.Enabled = true;
                _outerDoor.Enabled = true;
                _innerDoor.CloseDoor();
                _outerDoor.CloseDoor();
            }

            private void pressurizeDepressurize()
            {
                _console.Print("Cycling");

                // toggle the current state
                _airVent.Depressurize = !_airVent.Depressurize;
                _airVent.Enabled = true;

                // When depressurizing, check the external pressure and only depressurize to that value.
                // TODO: test this for floating point errors
                if (_airVent.Depressurize && ReferenceVent != null)
                {
                    _targetOxygenLevel = ReferenceVent.GetOxygenLevel();
                    _console.Print($"Set depressurization target to {_targetOxygenLevel}");
                }
            }

            // Open the appropriate door based on pressure state.
            private void openDoor()
            {
                _console.Print($"Opening Door");

                if (_airVent.Status == VentStatus.Pressurized)
                {
                    _innerDoor.Enabled = true;
                    _innerDoor.OpenDoor();
                }
                else
                {
                    _outerDoor.Enabled = true;
                    _outerDoor.OpenDoor();
                }
            }

            // TODO: blinkenlights are unsatisfying right now...
            private void setLights(AirlockLightState lightState)
            {
                float blinkLength = 1.0F;
                float blinkInterval = 0.0F;
                Color color = Color.Red;

                switch (lightState)
                {
                    case AirlockLightState.Off:
                        color = Color.Green;
                        break;
                    case AirlockLightState.Cycling:
                        blinkInterval = 1.0F;
                        blinkLength = 0.75F;
                        break;
                    case AirlockLightState.Cooldown:
                        color = Color.Yellow;
                        break;
                    case AirlockLightState.Error:
                        // the defaults already set this correctly
                        break;
                }

                foreach (IMyLightingBlock light in _lights)
                {
                    light.Enabled = true;
                    light.BlinkIntervalSeconds = blinkInterval;
                    light.BlinkLength = blinkLength;
                    light.Color = color;
                }
            }

            // private void setDisplays(string text) {
            //     foreach (IMyTextSurface display in _displays) {
            //         display.WriteText(text);
            //     }
            // }

            // Returns false if we are in a state where we can't or don't need to balance
            private bool balanceOxygen()
            {
                if (_innerDoor.Status == DoorStatus.Closed || _outerDoor.Status == DoorStatus.Open || OxygenBalanced) { return false; }

                _console.Print($"{_name}: Balancing Oxygen Tank");

                // Configure the vent to suck in Oxygen.
                _airVent.Depressurize = true;
                _airVent.Enabled = true;
                return true;
            }

            private void lockDoors()
            {
                _innerDoor.Enabled = false;
                _outerDoor.Enabled = false;
            }

            private void error(string error)
            {
                _console.Print(error);
                setLights(AirlockLightState.Error);
            }
        }
    }
}