using System;
using SFML.Graphics;
using SFML.System;
using FluidSim.Components;
using FluidSim.Core;
using FluidSim.Utils;

namespace FluidSim.Tests
{
    public class TestScenario : Scenario
    {
        // Engine
        private Cylinder cylinder;
        private Crankshaft crankshaft;

        // Intake side
        private Pipe1D intakePipeBeforeThrottle;
        private Volume0D intakePlenum;          // 5 mL
        private Pipe1D intakeRunner;

        // Exhaust side
        private Pipe1D exhaustPipe;

        // Links
        private OpenEndLink intakeOpenEnd;
        private OrificeLink throttleOrifice;
        private OrificeLink plenumToRunner;
        private OrificeLink intakeValve;
        private OrificeLink exhaustValve;
        private OpenEndLink exhaustOpenEnd;

        private Solver solver;
        private SoundProcessor exhaustSoundProcessor;
        private SoundProcessor intakeSoundProcessor;
        private OutdoorExhaustReverb reverb;
        private double dt;
        private int stepCount;

        // ---------- Throttle control ----------
        public double MaxThrottleArea { get; set; } = 1 * Units.cm2;   // 2 cm²

        public override void Initialize(int sampleRate)
        {
            dt = 1.0 / sampleRate;

            solver = new Solver();
            solver.SetTimeStep(dt);
            solver.CflTarget = 0.9;

            // ---- Crankshaft (external, passed to cylinder) ----
            crankshaft = new Crankshaft(600);
            crankshaft.Inertia = 0.2;
            crankshaft.FrictionConstant = 2;
            crankshaft.FrictionViscous = 0.04;

            // ---- Cylinder ----
            double bore = 0.056, stroke = 0.057, conRod = 0.110, compRatio = 9.2;
            double ivo = 350.0, ivc = 580.0, evo = 120.0, evc = 370.0;
            cylinder = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft)
            {
                IntakeValveDiameter = 30 * Units.mm,   // 30 mm
                IntakeValveLift = 5 * Units.mm,   // 5 mm
                ExhaustValveDiameter = 28 * Units.mm,   // 28 mm
                ExhaustValveLift = 5 * Units.mm    // 5 mm
            };
            solver.AddComponent(cylinder);

            double pipeDiameter = 2 * Units.cm;
            double pipeArea = Units.AreaFromDiameter(pipeDiameter);

            exhaustSoundProcessor = new SoundProcessor(sampleRate, 1, pipeDiameter) { Gain = 0.1f };
            intakeSoundProcessor = new SoundProcessor(sampleRate, 1, pipeDiameter) { Gain = 0.1f };
            reverb = new OutdoorExhaustReverb(sampleRate);

            // ---- Pipes ----
            intakePipeBeforeThrottle = new Pipe1D(0.2, pipeArea, 10);
            intakeRunner = new Pipe1D(0.2, pipeArea, 10);
            exhaustPipe = new Pipe1D(0.5, pipeArea, 50);
            solver.AddComponent(intakePipeBeforeThrottle);
            solver.AddComponent(intakeRunner);
            solver.AddComponent(exhaustPipe);

            intakePlenum = new Volume0D(5 * Units.mL, 101325.0, 300.0);
            var plenumInlet = intakePlenum.CreatePort();
            var plenumOutlet = intakePlenum.CreatePort();
            solver.AddComponent(intakePlenum);

            // ---- Intake open end ----
            intakeOpenEnd = new OpenEndLink(intakePipeBeforeThrottle, isLeftEnd: true)
            {
                AmbientPressure = 101325.0,
                Gamma = 1.4
            };
            solver.AddOpenEndLink(intakeOpenEnd);

            // ---- Throttle orifice (variable area) ----
            throttleOrifice = new OrificeLink(plenumInlet, intakePipeBeforeThrottle, isPipeLeftEnd: false,
                                              areaProvider: () => MaxThrottleArea * Math.Clamp(Throttle, 0.0001, 1))
            {
                DischargeCoefficient = 0.2,
                UseInertance = false
            };
            solver.AddOrificeLink(throttleOrifice);

            // ---- Plenum to runner (fixed area) ----
            plenumToRunner = new OrificeLink(plenumOutlet, intakeRunner, isPipeLeftEnd: true,
                                             areaProvider: () => pipeArea)
            {
                DischargeCoefficient = 1.0,
                UseInertance = false
            };
            solver.AddOrificeLink(plenumToRunner);

            // ---- Intake valve ----
            intakeValve = new OrificeLink(cylinder.IntakePort, intakeRunner, isPipeLeftEnd: false,
                                          areaProvider: () => cylinder.IntakeValveArea)
            {
                DischargeCoefficient = 1.0,
                UseInertance = false
            };
            solver.AddOrificeLink(intakeValve);

            // ---- Exhaust valve ----
            exhaustValve = new OrificeLink(cylinder.ExhaustPort, exhaustPipe, isPipeLeftEnd: true,
                                           areaProvider: () => cylinder.ExhaustValveArea)
            {
                DischargeCoefficient = 1.0,
                UseInertance = false
            };
            solver.AddOrificeLink(exhaustValve);

            // ---- Exhaust open end ----
            exhaustOpenEnd = new OpenEndLink(exhaustPipe, isLeftEnd: false)
            {
                AmbientPressure = 101325.0,
                Gamma = 1.4
            };
            solver.AddOpenEndLink(exhaustOpenEnd);

            stepCount = 0;
            Console.WriteLine("4‑Stroke engine test (plenum + two pipes)");
            Console.WriteLine($"Bore {bore * 1000:F0}mm, Stroke {stroke * 1000:F0}mm, CR {compRatio}");
            Console.WriteLine($"IVO {ivo}°, IVC {ivc}°, EVO {evo}°, EVC {evc}° (no overlap)");
        }

        public override float Process()
        {
            cylinder.Crankshaft.Step(dt);
            cylinder.PreStep(dt);
            solver.Step();
            stepCount++;

            if (stepCount % 10000 == 0)
            {
                double crankDeg = cylinder.Crankshaft.CrankAngle * 180.0 / Math.PI % 720.0;
                double cylP = cylinder.Pressure / 1e5;
                double cylT = cylinder.Temperature;
                double cylMass = cylinder.Mass * 1e6;
                double mdotI = intakeValve.LastMassFlowRate;
                double mdotE = exhaustValve.LastMassFlowRate;
                double pipeR = exhaustPipe.GetCellPressure(exhaustPipe.CellCount - 1) / 1e5;
                double plenumP = intakePlenum.Pressure / 1e5;
                double actualArea = MaxThrottleArea * Throttle;

                Console.WriteLine($"Step {stepCount}: Angle={crankDeg:F1}°, " +
                                  $"CylP={cylP:F2} bar, T={cylT:F0} K, mass={cylMass:F1} mg, " +
                                  $"mdotI={mdotI:E4} kg/s, mdotE={mdotE:E4} kg/s, PipeR={pipeR:F2} bar");
                Console.WriteLine($"Throttle = {Throttle * 100:F0}%  area = {actualArea * 1e6:F2} mm², Plenum P = {plenumP:F3} bar");
            }

            float exhaustDry = exhaustSoundProcessor.Process(exhaustOpenEnd);
            float intakeDry = intakeSoundProcessor.Process(intakeOpenEnd);
            return reverb.Process(exhaustDry + intakeDry);
        }

        public override void Draw(RenderWindow target)
        {
            float winW = target.GetView().Size.X;
            float winH = target.GetView().Size.Y;

            float intakeY = winH / 2f - 40f;
            float exhaustY = winH / 2f + 80f;

            // Open end marker
            float openEndX = 40f;
            var openEndMark = new CircleShape(5f) { FillColor = Color.Cyan };
            openEndMark.Position = new Vector2f(openEndX - 5f, intakeY - 5f);
            target.Draw(openEndMark);

            // First intake pipe
            float pipe1StartX = openEndX;
            float pipe1EndX = pipe1StartX + 120f;
            DrawPipe(target, intakePipeBeforeThrottle, intakeY, pipe1StartX, pipe1EndX);

            // Throttle symbol
            float throttleX = pipe1EndX + 5f;
            var throttleRect = new RectangleShape(new Vector2f(8f, 30f))
            {
                FillColor = Color.Yellow,
                Position = new Vector2f(throttleX, intakeY - 15f)
            };
            target.Draw(throttleRect);

            // Plenum
            float plenW = 60f, plenH = 80f;
            float plenLeftX = throttleX + 10f;
            float plenCenterX = plenLeftX + plenW / 2f;
            float plenTopY = intakeY - plenH / 2f;
            DrawVolume(target, intakePlenum, plenCenterX, plenTopY, plenW, plenH);

            // Runner pipe
            float runnerStartX = plenLeftX + plenW + 5f;
            float runnerEndX = runnerStartX + 100f;
            DrawPipe(target, intakeRunner, intakeY, runnerStartX, runnerEndX);

            // Cylinder
            float cylCX = runnerEndX + 50f;
            float cylTopY = intakeY - 120f;
            float cylW = 80f, cylMaxH = 240f;
            DrawCylinder(target, cylinder, cylCX, cylTopY, cylW, cylMaxH);

            // Exhaust pipe
            float exhStartX = cylCX + cylW / 2f + 20f;
            float exhEndX = winW - 60f;
            DrawPipe(target, exhaustPipe, exhaustY, exhStartX, exhEndX);

            // Exhaust open end marker
            var exhOpenEndMark = new CircleShape(5f) { FillColor = Color.Magenta };
            exhOpenEndMark.Position = new Vector2f(exhEndX - 5f, exhaustY - 5f);
            target.Draw(exhOpenEndMark);
        }
    }
}