enum AirlockState {
    Off,
    DoorsClosing,
    Cycling,
    DoorOpening,
    Cooldown,
}

enum AirlockLightState {
    Off,
    Cycling,
    Cooldown,
    Error,
}

string airlockName;
IEnumerator<bool> state;
float targetOxygenLevel = 0.0F;

// Blocks we need to track
IMyDoor innerDoor;
IMyDoor outerDoor;
List<IMyLightingBlock> lights;
IMyAirVent airVent;
IMyAirVent airSensor; // TODO: we don't use this yet
IMyGasTank oxygenTank;

public Program()
{
    airlockName = Me.CustomName.Split(' ')[0];
    state = null;

    // Find the correct objects
    InitDoors();
    InitVents();
    InitLights();
    InitOxygen();
}

private void InitDoors() {
    List<IMyDoor> onlyDoors = new List<IMyDoor>();
    GridTerminalSystem.GetBlocksOfType(onlyDoors);
    foreach (IMyDoor door in onlyDoors) {
        if (door.CustomName.StartsWith(airlockName)) {
            if (door.CustomName.Contains("Inner")) {
                innerDoor = door;
            }
            else if (door.CustomName.Contains("Outer")) {
                outerDoor = door;
            }
        }

        if (innerDoor != null && outerDoor != null) {
            break;
        }
    }
}

private void InitVents() {
    List<IMyAirVent> onlyFans = new List<IMyAirVent>();
    GridTerminalSystem.GetBlocksOfType(onlyFans);
    foreach (IMyAirVent vent in onlyFans) {
        if (vent.CustomName.StartsWith(airlockName)) {
            if (vent.CustomName.Contains("Main")) {
                airVent = vent;
            }
            else if (vent.CustomName.Contains("Reference")) {
                airSensor = vent;
            }
        }

        // A global reference vent will be used if we don't have one specific to our airlock.
        //   A specific vent found later will overwrite this assignment.
        if(vent.CustomName.StartsWith("Airlock Reference") && airSensor == null) {
            airSensor = vent;
        }
    }
}

private void InitLights() {
    lights = new List<IMyLightingBlock>();
    List<IMyLightingBlock> allLights = new List<IMyLightingBlock>();
    GridTerminalSystem.GetBlocksOfType(allLights);
    foreach (IMyLightingBlock light in allLights) {
        if (light.CustomName.StartsWith(airlockName)) {
            lights.Add(light);
        }
    }
}

private void InitOxygen() {
    List<IMyGasTank> allTanks = new List<IMyGasTank>();
    GridTerminalSystem.GetBlocksOfType(allTanks);
    foreach (IMyGasTank tank in allTanks) {
        if (tank.CustomName.StartsWith(airlockName)) {
            oxygenTank = tank;
            break;
        }
    }
}

public void Main(string argument, UpdateType updateSource)
{
    if (state == null) {
        state = CycleAirlock();
        Runtime.UpdateFrequency = UpdateFrequency.Update1;
        return;
    }

    if (!state.MoveNext()) {
        state.Dispose();
        state = null;
        Runtime.UpdateFrequency = UpdateFrequency.None;
    }
}

private IEnumerator<bool> CycleAirlock() {
    SetLights(AirlockLightState.Cycling);

    CloseDoors();
    while(!DoorsClosed()) { yield return true; }
    LockDoors();

    if (!airVent.CanPressurize) {
        Error("Airlock is not airtight.");
        yield return false;
    }

    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
    Runtime.UpdateFrequency = UpdateFrequency.Update100;
    yield return true;
    Runtime.UpdateFrequency = UpdateFrequency.Update1;

    SetLights(AirlockLightState.Off);
}

private void CloseDoors() {
    Echo("DEBUG: Closing Doors");

    // close the doors
    innerDoor.Enabled = true;
    outerDoor.Enabled = true;
    innerDoor.CloseDoor();
    outerDoor.CloseDoor();
}

private bool DoorsClosed() {
    return innerDoor.Status == DoorStatus.Closed && outerDoor.Status == DoorStatus.Closed;
}

private void PressurizeDepressurize() {
    Echo("DEBUG: 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 && airSensor != null) {
        targetOxygenLevel = airSensor.GetOxygenLevel();
        Echo($"Set depressurization target to {targetOxygenLevel}");
    }
}

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

// Open the appropriate door based on pressure state.
private void OpenDoor() {
    Echo("DEBUG: Opening Door");

    if (airVent.Status == VentStatus.Pressurized) {
        innerDoor.Enabled = true;
        innerDoor.OpenDoor();
    } else {
        outerDoor.Enabled = true;
        outerDoor.OpenDoor();
    }
}

private bool DoorOpened() {
    return innerDoor.Status == DoorStatus.Open || outerDoor.Status == DoorStatus.Open;
}

// 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; }

    Echo("DEBUG: Balancing Oxygen Tank");

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

private bool OxygenBalanced() {
    return oxygenTank.FilledRatio > 0.25;
}

// 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 LockDoors() {
    innerDoor.Enabled = false;
    outerDoor.Enabled = false;
}

private void Error(string error) {
    Echo(error);
    SetLights(AirlockLightState.Error);
}