namespace ReactorMaintenance.Simulation;

internal static class SurfaceInteractionSystem
{
    public static LevelState Resolve(LevelState level)
    {
        var deltas = Enumerable.Range(0, level.Width * level.Height).Select(_ => new SurfaceDelta()).ToArray();
        foreach (var position in LevelTraversal.AllPositions(level).Where(level.IsFloor))
            ApplySameCellInteractions(level, position, deltas);

        foreach (var position in LevelTraversal.AllPositions(level).Where(level.IsFloor))
        {
            foreach (var neighbor in position.Neighbors().Where(level.IsFloor))
            {
                if (level.Index(position) >= level.Index(neighbor) || level.IsClosedDoorEdge(position, neighbor))
                    continue;

                ApplyAdjacentInteractions(level, position, neighbor, deltas);
            }
        }

        var surface = level.Surface.ToArray();
        for (var i = 0; i < surface.Length; i++)
            surface[i] = deltas[i].Apply(surface[i]).Clamp();

        return level with { Surface = surface };
    }

    public static LevelState AdvanceDurations(LevelState level)
    {
        var surface = level.Surface.Select(cell => cell with {
                               FuelBlockTurns = Math.Max(0, cell.FuelBlockTurns - 1),
                               CoolantBlockTurns = Math.Max(0, cell.CoolantBlockTurns - 1),
                               ElectricityBlockTurns = Math.Max(0, cell.ElectricityBlockTurns - 1)
                           })
                           .ToArray();

        return level with { Surface = surface };
    }

    private static void ApplySameCellInteractions(LevelState level, GridPosition position, SurfaceDelta[] deltas)
    {
        var surface = level.GetSurface(position);
        ApplyPair(level, position, ECarrierType.Fuel, SimulationBands.Fuel(surface.Fuel), ECarrierType.Electricity, SimulationBands.Electricity(surface.Electricity), deltas);
        ApplyPair(level, position, ECarrierType.Fuel, SimulationBands.Fuel(surface.Fuel), null, SimulationBands.Heat(surface.Heat), deltas);
        ApplyPair(level, position, ECarrierType.Coolant, SimulationBands.Coolant(surface.Coolant), ECarrierType.Electricity, SimulationBands.Electricity(surface.Electricity), deltas);
        ApplyPair(level, position, ECarrierType.Coolant, SimulationBands.Coolant(surface.Coolant), null, SimulationBands.Heat(surface.Heat), deltas);
    }

    private static void ApplyAdjacentInteractions(LevelState level, GridPosition a, GridPosition b, SurfaceDelta[] deltas)
    {
        var surfaceA = level.GetSurface(a);
        var surfaceB = level.GetSurface(b);
        FlowBetween(level, a, b, surfaceA.Fuel, surfaceB.Fuel, Balancing.Current.FlowInteraction(ESurfaceQuantity.Fuel), deltas);
        FlowBetween(level, a, b, surfaceA.Coolant, surfaceB.Coolant, Balancing.Current.FlowInteraction(ESurfaceQuantity.Coolant), deltas);
        FlowBetween(level, a, b, surfaceA.Electricity, surfaceB.Electricity, Balancing.Current.FlowInteraction(ESurfaceQuantity.Electricity), deltas);
        FlowBetween(level, a, b, surfaceA.Heat, surfaceB.Heat, Balancing.Current.FlowInteraction(ESurfaceQuantity.Heat), deltas);
    }

    private static void ApplyPair(LevelState level, GridPosition position, ECarrierType? rowCarrier, EBand rowBand, ECarrierType? colCarrier, EBand colBand, SurfaceDelta[] deltas)
    {
        ApplyEffect(level, position, Balancing.Current.SameCellInteraction(rowCarrier, rowBand, colCarrier, colBand), deltas);
    }

    private static void ApplyEffect(LevelState level, GridPosition position, SurfaceInteractionEffect effect, SurfaceDelta[] deltas)
    {
        var index = level.Index(position);
        switch (effect.Verb)
        {
            case ESurfaceInteractionVerb.Warm:
                deltas[index].Heat += effect.Amount;
                break;
            case ESurfaceInteractionVerb.Quench:
                deltas[index].Heat -= effect.Amount;
                break;
            case ESurfaceInteractionVerb.Short:
                deltas[index].Heat += effect.Amount;
                deltas[index].Electricity -= effect.SecondaryAmount;
                break;
            case ESurfaceInteractionVerb.Ignite:
                deltas[index].Heat += effect.Amount;
                deltas[index].Fuel -= effect.SecondaryAmount;
                break;
        }
    }

    private static void FlowBetween(LevelState level, GridPosition a, GridPosition b, float valueA, float valueB, SurfaceInteractionEffect effect, SurfaceDelta[] deltas)
    {
        var difference = valueA - valueB;
        if (Math.Abs(difference) < 0.01f)
            return;

        var amount = difference * effect.Amount;
        var indexA = level.Index(a);
        var indexB = level.Index(b);

        switch (effect.Quantity)
        {
            case ESurfaceQuantity.Fuel:
                deltas[indexA].Fuel -= amount;
                deltas[indexB].Fuel += amount;
                break;
            case ESurfaceQuantity.Coolant:
                deltas[indexA].Coolant -= amount;
                deltas[indexB].Coolant += amount;
                break;
            case ESurfaceQuantity.Electricity:
                deltas[indexA].Electricity -= amount;
                deltas[indexB].Electricity += amount;
                break;
            case ESurfaceQuantity.Heat:
                deltas[indexA].Heat -= amount;
                deltas[indexB].Heat += amount;
                break;
        }
    }

    private sealed class SurfaceDelta
    {
        public SurfaceState Apply(SurfaceState surface)
        {
            return surface with {
                Fuel = surface.Fuel + Fuel,
                Coolant = surface.Coolant + Coolant,
                Electricity = surface.Electricity + Electricity,
                Heat = surface.Heat + Heat
            };
        }

        public float Fuel { get; set; }
        public float Coolant { get; set; }
        public float Electricity { get; set; }
        public float Heat { get; set; }
    }
}