using System.Linq;
using SuperStarTrek.Commands;
using SuperStarTrek.Objects;
using SuperStarTrek.Resources;
using SuperStarTrek.Space;

namespace SuperStarTrek.Systems
{
    internal class PhaserControl : Subsystem
    {
        private readonly Enterprise _enterprise;
        private readonly Output _output;
        private readonly Input _input;
        private readonly Random _random;

        internal PhaserControl(Enterprise enterprise, Output output, Input input, Random random)
            : base("Phaser Control", Command.PHA, output)
        {
            _enterprise = enterprise;
            _output = output;
            _input = input;
            _random = random;
        }

        protected override bool CanExecuteCommand() => IsOperational("Phasers inoperative");

        protected override CommandResult ExecuteCommandCore(Quadrant quadrant)
        {
            if (!quadrant.HasKlingons)
            {
                _output.WriteLine(Strings.NoEnemyShips);
                return CommandResult.Ok;
            }

            if (_enterprise.Computer.IsDamaged)
            {
                _output.WriteLine("Computer failure hampers accuracy");
            }

            _output.Write($"Phasers locked on target;  ");

            var phaserStrength = GetPhaserStrength();
            if (phaserStrength < 0) { return CommandResult.Ok; }

            _enterprise.UseEnergy(phaserStrength);

            var perEnemyStrength = GetPerTargetPhaserStrength(phaserStrength, quadrant.KlingonCount);

            foreach (var klingon in quadrant.Klingons.ToList())
            {
                ResolveHitOn(klingon, perEnemyStrength, quadrant);
            }

            return quadrant.KlingonsFireOnEnterprise();
        }

        private float GetPhaserStrength()
        {
            while (true)
            {
                _output.WriteLine($"Energy available = {_enterprise.Energy} units");
                var phaserStrength = _input.GetNumber("Number of units to fire");

                if (phaserStrength <= _enterprise.Energy) { return phaserStrength; }
            }
        }

        private float GetPerTargetPhaserStrength(float phaserStrength, int targetCount)
        {
            if (_enterprise.Computer.IsDamaged)
            {
                phaserStrength *= _random.GetFloat();
            }

            return phaserStrength / targetCount;
        }

        private void ResolveHitOn(Klingon klingon, float perEnemyStrength, Quadrant quadrant)
        {
            var distance = _enterprise.SectorCoordinates.GetDistanceTo(klingon.Sector);
            var hitStrength = (int)(perEnemyStrength / distance * (2 + _random.GetFloat()));

            if (klingon.TakeHit(hitStrength))
            {
                _output.WriteLine($"{hitStrength} unit hit on Klingon at sector {klingon.Sector}");
                _output.WriteLine(
                    klingon.Energy <= 0
                        ? quadrant.Remove(klingon)
                        : $"   (sensors show {klingon.Energy} units remaining)");
            }
            else
            {
                _output.WriteLine($"Sensors show no damage to enemy at {klingon.Sector}");
            }
        }
    }
}
