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

namespace FluidSim.Tests
{
    public class Inline4Scenario : Scenario
    {
        // Crankshaft
        private Crankshaft crankshaft;

        // Cylinders
        private Cylinder cyl1, cyl2, cyl3, cyl4;

        // Intake
        private Pipe1D intakePipeBeforeThrottle;
        private Volume0D intakePlenum;

        // Runners (shorter, fewer cells)
        private Pipe1D runner1, runner2, runner3, runner4;

        // Exhaust collector + tailpipe
        private Volume0D exhaustCollector;
        private Pipe1D tailPipe;

        // Exhaust stubs (short pipes between cylinders and collector)
        private Pipe1D exhStub1, exhStub2, exhStub3, exhStub4;

        // Links – intake
        private OpenEndLink intakeOpenEnd;
        private OrificeLink throttleOrifice;

        // Plenum‑to‑runner orifices
        private OrificeLink plenumToRunner1, plenumToRunner2, plenumToRunner3, plenumToRunner4;

        // Intake valves
        private OrificeLink intakeValve1, intakeValve2, intakeValve3, intakeValve4;

        // Exhaust valves (cylinder → stub)
        private OrificeLink exhaustValve1, exhaustValve2, exhaustValve3, exhaustValve4;

        // Stub‑to‑collector orifices
        private OrificeLink stubToCollector1, stubToCollector2, stubToCollector3, stubToCollector4;

        // Collector‑to‑tailpipe orifice
        private OrificeLink collectorToTailpipe;

        // Exhaust open end (tailpipe exit)
        private OpenEndLink exhaustOpenEnd;

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

        public double MaxThrottleArea { get; set; } = 10 * Units.cm2;

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

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

            // ---- Shared crankshaft ----
            crankshaft = new Crankshaft(800);
            crankshaft.Inertia = 1;
            crankshaft.FrictionConstant = 3;
            crankshaft.FrictionViscous = 0.2;

            // ---- Cylinder geometry ----
            double bore = 0.056, stroke = 0.057, conRod = 0.110, compRatio = 10;
            double ivo = 350.0, ivc = 580.0, evo = 120.0, evc = 370.0;

            // Firing order 1-3-4-2 with 180° intervals (0°, 180°, 360°, 540°)
            double phaseCyl1 = 0.0;
            double phaseCyl3 = Math.PI;          // 180°
            double phaseCyl4 = 2.0 * Math.PI;    // 360°
            double phaseCyl2 = 3.0 * Math.PI;    // 540°

            cyl1 = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft)
            {
                IntakeValveDiameter = 30 * Units.mm,
                IntakeValveLift = 5 * Units.mm,
                ExhaustValveDiameter = 28 * Units.mm,
                ExhaustValveLift = 5 * Units.mm,
                PhaseOffset = phaseCyl1,
                EnergyVariationFraction = 0.03,
                MisfireProbability = 0.0
            };
            cyl2 = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft)
            {
                IntakeValveDiameter = 30 * Units.mm,
                IntakeValveLift = 5 * Units.mm,
                ExhaustValveDiameter = 28 * Units.mm,
                ExhaustValveLift = 5 * Units.mm,
                PhaseOffset = phaseCyl2,
                EnergyVariationFraction = 0.03,
                MisfireProbability = 0.0
            };
            cyl3 = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft)
            {
                IntakeValveDiameter = 30 * Units.mm,
                IntakeValveLift = 5 * Units.mm,
                ExhaustValveDiameter = 28 * Units.mm,
                ExhaustValveLift = 5 * Units.mm,
                PhaseOffset = phaseCyl3,
                EnergyVariationFraction = 0.03,
                MisfireProbability = 0.0
            };
            cyl4 = new Cylinder(bore, stroke, conRod, compRatio, ivo, ivc, evo, evc, crankshaft)
            {
                IntakeValveDiameter = 30 * Units.mm,
                IntakeValveLift = 5 * Units.mm,
                ExhaustValveDiameter = 28 * Units.mm,
                ExhaustValveLift = 5 * Units.mm,
                PhaseOffset = phaseCyl4,
                EnergyVariationFraction = 0.03,
                MisfireProbability = 0.0
            };
            solver.AddComponent(cyl1);
            solver.AddComponent(cyl2);
            solver.AddComponent(cyl3);
            solver.AddComponent(cyl4);

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

            // Sound processors (only one exhaust source now)
            exhaustSoundProcessor = new SoundProcessor(sampleRate, 1, pipeDiameter) { Gain = 0.2f };
            intakeSoundProcessor = new SoundProcessor(sampleRate, 1, pipeDiameter) { Gain = 0.2f };
            reverb = new OutdoorExhaustReverb(sampleRate);

            // ---- Intake pipe before throttle (shorter, fewer cells) ----
            intakePipeBeforeThrottle = new Pipe1D(0.1, pipeArea, 10);
            intakePipeBeforeThrottle.Name = "Intake pipe";
            solver.AddComponent(intakePipeBeforeThrottle);

            // ---- Plenum ----
            intakePlenum = new Volume0D(100 * Units.mL, 101325.0, 300.0);
            var plenumInlet = intakePlenum.CreatePort();   // port 0
            var plenumOut1 = intakePlenum.CreatePort();   // port 1
            var plenumOut2 = intakePlenum.CreatePort();   // port 2
            var plenumOut3 = intakePlenum.CreatePort();   // port 3
            var plenumOut4 = intakePlenum.CreatePort();   // port 4
            solver.AddComponent(intakePlenum);

            // ---- Intake runners (shorter, fewer cells) ----
            runner1 = new Pipe1D(0.1, pipeArea, 5) { Name = "Runner 1" };
            runner2 = new Pipe1D(0.1, pipeArea, 5) { Name = "Runner 2" };
            runner3 = new Pipe1D(0.1, pipeArea, 5) { Name = "Runner 3" };
            runner4 = new Pipe1D(0.1, pipeArea, 5) { Name = "Runner 4" };
            solver.AddComponent(runner1);
            solver.AddComponent(runner2);
            solver.AddComponent(runner3);
            solver.AddComponent(runner4);

            // ---- Exhaust collector volume ----
            exhaustCollector = new Volume0D(200 * Units.mL, 101325.0, 800.0);
            var colIn1 = exhaustCollector.CreatePort();   // cylinder 1 stub
            var colIn2 = exhaustCollector.CreatePort();   // cylinder 2 stub
            var colIn3 = exhaustCollector.CreatePort();   // cylinder 3 stub
            var colIn4 = exhaustCollector.CreatePort();   // cylinder 4 stub
            var colOut = exhaustCollector.CreatePort();   // to tailpipe
            solver.AddComponent(exhaustCollector);

            // ---- Exhaust stub pipes (short connection cylinder → collector) ----
            exhStub1 = new Pipe1D(0.1, pipeArea, 5) { Name = "ExhStub 1" };
            exhStub2 = new Pipe1D(0.1, pipeArea, 5) { Name = "ExhStub 2" };
            exhStub3 = new Pipe1D(0.1, pipeArea, 5) { Name = "ExhStub 3" };
            exhStub4 = new Pipe1D(0.1, pipeArea, 5) { Name = "ExhStub 4" };
            solver.AddComponent(exhStub1);
            solver.AddComponent(exhStub2);
            solver.AddComponent(exhStub3);
            solver.AddComponent(exhStub4);

            foreach (var p in new[] { runner1, runner2, runner3, runner4, exhStub1, exhStub2, exhStub3, exhStub4, intakePipeBeforeThrottle })
            {
                p.DampingMultiplier = 0.5;
                p.EnergyRelaxationRate = 0.0;
            }

            // ---- Tailpipe (single exhaust pipe) ----
            tailPipe = new Pipe1D(0.5, pipeArea, 20)
            {
                Name = "Tailpipe",
                DampingMultiplier = 0.5,
                EnergyRelaxationRate = 0.0
            };
            solver.AddComponent(tailPipe);

            // ---- Plenum → runner orifices (volume port to pipe left end) ----
            plenumToRunner1 = new OrificeLink(plenumOut1, runner1, isPipeLeftEnd: true, areaProvider: () => pipeArea)
            { DischargeCoefficient = 1.0, UseInertance = false };
            plenumToRunner2 = new OrificeLink(plenumOut2, runner2, isPipeLeftEnd: true, areaProvider: () => pipeArea)
            { DischargeCoefficient = 1.0, UseInertance = false };
            plenumToRunner3 = new OrificeLink(plenumOut3, runner3, isPipeLeftEnd: true, areaProvider: () => pipeArea)
            { DischargeCoefficient = 1.0, UseInertance = false };
            plenumToRunner4 = new OrificeLink(plenumOut4, runner4, isPipeLeftEnd: true, areaProvider: () => pipeArea)
            { DischargeCoefficient = 1.0, UseInertance = false };
            solver.AddOrificeLink(plenumToRunner1);
            solver.AddOrificeLink(plenumToRunner2);
            solver.AddOrificeLink(plenumToRunner3);
            solver.AddOrificeLink(plenumToRunner4);

            // ---- Intake valves (cylinder port to runner right end) ----
            intakeValve1 = new OrificeLink(cyl1.IntakePort, runner1, isPipeLeftEnd: false, areaProvider: () => cyl1.IntakeValveArea)
            { DischargeCoefficient = 1.0, UseInertance = false };
            intakeValve2 = new OrificeLink(cyl2.IntakePort, runner2, isPipeLeftEnd: false, areaProvider: () => cyl2.IntakeValveArea)
            { DischargeCoefficient = 1.0, UseInertance = false };
            intakeValve3 = new OrificeLink(cyl3.IntakePort, runner3, isPipeLeftEnd: false, areaProvider: () => cyl3.IntakeValveArea)
            { DischargeCoefficient = 1.0, UseInertance = false };
            intakeValve4 = new OrificeLink(cyl4.IntakePort, runner4, isPipeLeftEnd: false, areaProvider: () => cyl4.IntakeValveArea)
            { DischargeCoefficient = 1.0, UseInertance = false };
            solver.AddOrificeLink(intakeValve1);
            solver.AddOrificeLink(intakeValve2);
            solver.AddOrificeLink(intakeValve3);
            solver.AddOrificeLink(intakeValve4);

            // ---- Exhaust valves (cylinder port to stub left end) ----
            exhaustValve1 = new OrificeLink(cyl1.ExhaustPort, exhStub1, isPipeLeftEnd: true, areaProvider: () => cyl1.ExhaustValveArea)
            { DischargeCoefficient = 1.0, UseInertance = false };
            exhaustValve2 = new OrificeLink(cyl2.ExhaustPort, exhStub2, isPipeLeftEnd: true, areaProvider: () => cyl2.ExhaustValveArea)
            { DischargeCoefficient = 1.0, UseInertance = false };
            exhaustValve3 = new OrificeLink(cyl3.ExhaustPort, exhStub3, isPipeLeftEnd: true, areaProvider: () => cyl3.ExhaustValveArea)
            { DischargeCoefficient = 1.0, UseInertance = false };
            exhaustValve4 = new OrificeLink(cyl4.ExhaustPort, exhStub4, isPipeLeftEnd: true, areaProvider: () => cyl4.ExhaustValveArea)
            { DischargeCoefficient = 1.0, UseInertance = false };
            solver.AddOrificeLink(exhaustValve1);
            solver.AddOrificeLink(exhaustValve2);
            solver.AddOrificeLink(exhaustValve3);
            solver.AddOrificeLink(exhaustValve4);

            // ---- Stub → collector orifices (collector port to stub right end) ----
            stubToCollector1 = new OrificeLink(colIn1, exhStub1, isPipeLeftEnd: false, areaProvider: () => pipeArea)
            { DischargeCoefficient = 1.0, UseInertance = false };
            stubToCollector2 = new OrificeLink(colIn2, exhStub2, isPipeLeftEnd: false, areaProvider: () => pipeArea)
            { DischargeCoefficient = 1.0, UseInertance = false };
            stubToCollector3 = new OrificeLink(colIn3, exhStub3, isPipeLeftEnd: false, areaProvider: () => pipeArea)
            { DischargeCoefficient = 1.0, UseInertance = false };
            stubToCollector4 = new OrificeLink(colIn4, exhStub4, isPipeLeftEnd: false, areaProvider: () => pipeArea)
            { DischargeCoefficient = 1.0, UseInertance = false };
            solver.AddOrificeLink(stubToCollector1);
            solver.AddOrificeLink(stubToCollector2);
            solver.AddOrificeLink(stubToCollector3);
            solver.AddOrificeLink(stubToCollector4);

            // ---- Collector → tailpipe (collector port to tailpipe left end) ----
            collectorToTailpipe = new OrificeLink(colOut, tailPipe, isPipeLeftEnd: true, areaProvider: () => pipeArea)
            { DischargeCoefficient = 1.0, UseInertance = false };
            solver.AddOrificeLink(collectorToTailpipe);

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

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

            // ---- Throttle ----
            throttleOrifice = new OrificeLink(plenumInlet, intakePipeBeforeThrottle, isPipeLeftEnd: false,
                                              areaProvider: () => MaxThrottleArea * Math.Clamp(Throttle, 0.0005, 1.0))
            {
                DischargeCoefficient = 0.9,
                UseInertance = false
            };
            solver.AddOrificeLink(throttleOrifice);

            stepCount = 0;
            Console.WriteLine("Inline-4 engine test");
            Console.WriteLine($"Bore {bore * 1000:F0}mm, Stroke {stroke * 1000:F0}mm, CR {compRatio}");
            Console.WriteLine("Firing order 1-3-4-2, 180° intervals");
        }

        public override float Process()
        {
            crankshaft.Step(dt);

            cyl1.PreStep(dt);
            cyl2.PreStep(dt);
            cyl3.PreStep(dt);
            cyl4.PreStep(dt);

            solver.Step();
            stepCount++;

            if (stepCount % 10000 == 0)
            {
                double rpm = crankshaft.AngularVelocity * 60.0 / (2.0 * Math.PI);
                Console.WriteLine($"Step {stepCount}, RPM = {rpm:F0}, " +
                                  $"cyl1 P = {cyl1.Pressure / 1e5:F2} bar, " +
                                  $"plenum P = {intakePlenum.Pressure / 1e5:F2} bar");
            }

            // Sound: only one exhaust source now
            float exhaustSound = exhaustSoundProcessor.Process(exhaustOpenEnd);
            float intakeSound = intakeSoundProcessor.Process(intakeOpenEnd);
            return reverb.Process(exhaustSound * 0.25f + intakeSound);
        }

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

            // --- Layout constants ---
            float leftMargin = 40f;
            float plenumW = 50f, plenumH = 120f;
            float cylinderWidth = 60f, cylinderMaxHeight = 180f;
            float cylinderSpacing = 90f;
            float cylinderTopY = winH * 0.25f;

            // Plenum position
            float plenumCenterX = leftMargin + plenumW / 2f;
            float plenumTopY = cylinderTopY - 20f;
            DrawVolume(target, intakePlenum, plenumCenterX, plenumTopY, plenumW, plenumH);

            // Throttle symbol (yellow rectangle) left of plenum
            float throttleWidth = 8f, throttleHeight = 30f;
            float throttleCenterX = leftMargin - 10f;
            var throttleRect = new RectangleShape(new Vector2f(throttleWidth, throttleHeight))
            {
                FillColor = Color.Yellow,
                Position = new Vector2f(throttleCenterX - throttleWidth / 2f, plenumTopY + plenumH / 2f - throttleHeight / 2f)
            };
            target.Draw(throttleRect);

            // Intake pipe before throttle (left of throttle)
            float intakePipeEndX = throttleCenterX - throttleWidth / 2f;
            float intakePipeStartX = intakePipeEndX - 100f;
            float intakePipeY = plenumTopY + plenumH / 2f;
            DrawPipe(target, intakePipeBeforeThrottle, intakePipeY, intakePipeStartX, intakePipeEndX);

            // Intake open end marker
            var intakeMark = new CircleShape(4f) { FillColor = Color.Magenta };
            intakeMark.Position = new Vector2f(intakePipeStartX - 4f, intakePipeY - 4f);
            target.Draw(intakeMark);

            // Cylinders and runners
            float runnerStartX = leftMargin + plenumW;
            Cylinder[] cyls = { cyl1, cyl2, cyl3, cyl4 };
            Pipe1D[] runners = { runner1, runner2, runner3, runner4 };

            for (int i = 0; i < 4; i++)
            {
                float cylCenterX = runnerStartX + 40f + i * cylinderSpacing;
                float runnerEndX = cylCenterX;
                DrawPipe(target, runners[i], plenumTopY + plenumH / 2f, runnerStartX, runnerEndX);
                DrawCylinder(target, cyls[i], cylCenterX, cylinderTopY, cylinderWidth, cylinderMaxHeight);
            }

            // Exhaust collector below cylinders
            float collectorLeftX = runnerStartX + 40f - cylinderWidth / 2f;
            float collectorWidth = 3 * cylinderSpacing + cylinderWidth;
            float collectorTopY = cylinderTopY + cylinderMaxHeight + 40f;
            float collectorHeight = 50f;
            float collectorCenterX = collectorLeftX + collectorWidth / 2f;
            DrawVolume(target, exhaustCollector, collectorCenterX, collectorTopY, collectorWidth, collectorHeight);

            // Tailpipe from right edge of collector
            float tailStartX = collectorLeftX + collectorWidth;
            float tailEndX = tailStartX + 150f;
            float tailCenterY = collectorTopY + collectorHeight / 2f;
            DrawPipe(target, tailPipe, tailCenterY, tailStartX, tailEndX);

            // Exhaust open end marker
            var exhaustMark = new CircleShape(4f) { FillColor = Color.Magenta };
            exhaustMark.Position = new Vector2f(tailEndX - 4f, tailCenterY - 4f);
            target.Draw(exhaustMark);

            // Exhaust stubs (vertical connections from cylinder bottom to collector)
            Pipe1D[] stubs = { exhStub1, exhStub2, exhStub3, exhStub4 };
            for (int i = 0; i < 4; i++)
            {
                float cylCenterX = runnerStartX + 40f + i * cylinderSpacing;
                float vertStartY = cylinderTopY + cylinderMaxHeight;
                float vertEndY = collectorTopY;
                // Draw stub as a vertical pipe
                DrawPipeVertical(target, stubs[i], cylCenterX, vertStartY, vertEndY);
            }
        }

        // Helper to draw a pipe vertically (reuse temperature coloring)
        private void DrawPipeVertical(RenderWindow target, Pipe1D pipe, float centerX, float topY, float bottomY)
        {
            int n = pipe.CellCount;
            if (n < 2) return;

            float pipeLengthPx = bottomY - topY;
            float dy = pipeLengthPx / (n - 1);

            float baseRadius = 25f;
            float rangeFactor = 2f;
            float scaleFactor = 2f;

            static float SmoothStep(float edge0, float edge1, float x)
            {
                float t = Math.Clamp((x - edge0) / (edge1 - edge0), 0f, 1f);
                return t * t * (3f - 2f * t);
            }

            var centersY = new float[n];
            var radii = new float[n];
            var temperatures = new double[n];
            double R_gas = 287.0;

            for (int i = 0; i < n; i++)
            {
                double p = pipe.GetCellPressure(i);
                double rho = pipe.GetCellDensity(i);
                double T = p / Math.Max(rho * R_gas, 1e-12);
                temperatures[i] = T;

                float deviation = (float)Math.Tanh((p - AmbientPressure) / AmbientPressure / rangeFactor);
                radii[i] = baseRadius * (1f + deviation * scaleFactor);
                if (radii[i] < 2f) radii[i] = 2f;
                centersY[i] = topY + i * dy;
            }

            int segmentsPerCell = 8;
            int totalPoints = n + (n - 1) * segmentsPerCell;
            Vertex[] stripVertices = new Vertex[totalPoints * 2];
            int idx = 0;

            for (int i = 0; i < n; i++)
            {
                float y = centersY[i];
                float r = radii[i];
                Color col = TemperatureColor(temperatures[i]);

                stripVertices[idx++] = new Vertex(new Vector2f(centerX - r, y), col);
                stripVertices[idx++] = new Vertex(new Vector2f(centerX + r, y), col);

                if (i < n - 1)
                {
                    for (int s = 1; s <= segmentsPerCell; s++)
                    {
                        float t = s / (float)segmentsPerCell;
                        float st = SmoothStep(0f, 1f, t);
                        float yi = centersY[i] + (centersY[i + 1] - centersY[i]) * t;
                        float ri = radii[i] + (radii[i + 1] - radii[i]) * st;
                        double Ti = temperatures[i] + (temperatures[i + 1] - temperatures[i]) * st;
                        Color coli = TemperatureColor(Ti);

                        stripVertices[idx++] = new Vertex(new Vector2f(centerX - ri, yi), coli);
                        stripVertices[idx++] = new Vertex(new Vector2f(centerX + ri, yi), coli);
                    }
                }
            }

            var pipeMesh = new VertexArray(PrimitiveType.TriangleStrip, (uint)stripVertices.Length);
            for (int i = 0; i < stripVertices.Length; i++)
                pipeMesh[(uint)i] = stripVertices[i];
            target.Draw(pipeMesh);
        }
    }
}