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

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

        // ---------- Fluid network ----------
        private PipeSystem pipeSystem;
        private BoundarySystem boundaries;
        private Solver solver;

        // Volumes
        private Volume0D intakePlenum;

        // Ports
        private Port plenumInlet, plenumOutlet;

        // Orifice / open‑end indices
        private int throttleAreaIdx, plenumRunnerIdx, intakeValveIdx, exhaustValveIdx;
        private int intakeOpenIdx, exhaustOpenIdx;
        private float[] orificeAreas;

        // Sound
        private SoundProcessor exhaustSound, intakeSound;
        private OutdoorExhaustReverb reverb;

        // ---------- Simulation state ----------
        private double dt;
        private int stepCount;
        public float MaxThrottleArea = 100e-4f;   // 1 cm²

        // ---------- Geometry (Lifan YX140) ----------
        // Bore 56 mm, Stroke 57 mm, CR 9.5
        private const float Bore = 0.056f;
        private const float Stroke = 0.057f;
        private const float ConRod = 0.110f;           // typical for 57 mm stroke
        private const float CompressionRatio = 9.5f;

        // Valve diameters (intake 27 mm, exhaust 23 mm)
        private const float IntakeValveDiam = 0.027f;
        private const float ExhaustValveDiam = 0.023f;
        private const float ValveLift = 0.006f;         // 6 mm peak lift

        // Valve timings (degrees, 720° four‑stroke)
        // Intake: 15° BTDC → 45° ABDC
        private const float IVO = 345f;   // 15° BTDC
        private const float IVC = 585f;   // 45° ABDC (180°+45°)
        // Exhaust: 45° BBDC → 15° ATDC
        private const float EVO = 135f;   // 45° BBDC (180°-45°)
        private const float EVC = 375f;   // 15° ATDC (360°+15°)

        // Spark advance: 30° BTDC
        private const float SparkAdv = 30f;

        // Pipe / plenum sizes
        private const float PipeDiam = 0.025f;          // 25 mm intake / exhaust
        private const float PipeArea = 0.00049087f;      // π*D²/4
        private const float PlenumVolume = 0.0005f;      // 500 mL
        private const float MaxThrottleArea = 1e-4f;     // ~1 cm² (fully open)

        // Pipe lengths and cell counts
        private const float IntakeLenBefore = 0.15f;     // 15 cm before throttle
        private const float RunnerLen = 0.25f;           // 25 cm runner
        private const float ExhaustLen = 0.60f;          // 60 cm exhaust
        private const int CellsBefore = 6;
        private const int CellsRunner = 10;
        private const int CellsExhaust = 24;

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

            // ---- Crankshaft ----
            crankshaft = new Crankshaft(600);
            crankshaft.Inertia = 0.05f;
            crankshaft.FrictionConstant = 2f;
            crankshaft.FrictionViscous = 0.01f;

            // ---------- Cylinder ----------
            cylinder = new Cylinder(Bore, Stroke, ConRod, CompressionRatio,
                                    IVO, IVC, EVO, EVC, crankshaft)
            {
                IntakeValveDiameter = IntakeValveDiam,
                ExhaustValveDiameter = ExhaustValveDiam,
                IntakeValveLift = ValveLift,
                ExhaustValveLift = ValveLift,
                SparkAdvance = SparkAdv,
                EnergyVariationFraction = 0.03f,   // small cycle‑to‑cycle variation
                MisfireProbability = 0.0f
            };

            // ---------- Pipe system ----------
            int totalCells = CellsBefore + CellsRunner + CellsExhaust;
            int[] pipeStart = { 0, CellsBefore, CellsBefore + CellsRunner };
            int[] pipeEnd   = { CellsBefore, CellsBefore + CellsRunner, totalCells };

            float[] areas = new float[totalCells];
            float[] dxs   = new float[totalCells];
            float dxBefore = IntakeLenBefore / CellsBefore;
            float dxRunner = RunnerLen / CellsRunner;
            float dxExh    = ExhaustLen / CellsExhaust;

            for (int i = 0; i < totalCells; i++)
            {
                areas[i] = PipeArea;
                if (i < CellsBefore)
                    dxs[i] = dxBefore;
                else if (i < CellsBefore + CellsRunner)
                    dxs[i] = dxRunner;
                else
                    dxs[i] = dxExh;
            }

            pipeSystem = new PipeSystem(totalCells, pipeStart, pipeEnd, area, dx,
                                        1.225f, 0f, 101325f);
            pipeSystem.DampingMultiplier = 1.0f;
            pipeSystem.EnergyRelaxationRate = 0.5f;
            pipeSystem.AmbientPressure = 101325f;

            // ---- Volumes ----
            intakePlenum = new Volume0D(100e-6f, 101325f, 300f);  // 100 mL
            plenumInlet = intakePlenum.CreatePort();
            plenumOutlet = intakePlenum.CreatePort();

            // ---------- Boundary system ----------
            boundaries = new BoundarySystem(pipeSystem, maxOrifices: 4, maxOpenEnds: 2);

            throttleAreaIdx  = 0;
            plenumRunnerIdx  = 1;
            intakeValveIdx   = 2;
            exhaustValveIdx  = 3;

            // Open ends
            boundaries.AddOpenEnd(pipeIndex: 0, isLeftEnd: true, 101325f, PipeArea);
            intakeOpenIdx = 0;
            boundaries.AddOpenEnd(pipeIndex: 2, isLeftEnd: false, 101325f, PipeArea);
            exhaustOpenIdx = 1;

            // Orifices
            // throttle – variable area, low discharge for restriction
            boundaries.AddOrifice(plenumInlet, pipeIndex: 0, isLeftEnd: false,
                                  throttleAreaIdx, dischargeCoeff: 0.8f);
            // plenum → runner
            boundaries.AddOrifice(plenumOutlet, pipeIndex: 1, isLeftEnd: true,
                                  plenumRunnerIdx, dischargeCoeff: 1.0f);
            // intake valve
            boundaries.AddOrifice(cylinder.IntakePort, pipeIndex: 1, isLeftEnd: false,
                                  intakeValveIdx, dischargeCoeff: 1.0f);
            // exhaust valve
            boundaries.AddOrifice(cylinder.ExhaustPort, pipeIndex: 2, isLeftEnd: true,
                                  exhaustValveIdx, dischargeCoeff: 1.0f);

            orificeAreas = new float[4];
            orificeAreas[plenumRunnerIdx] = PipeArea;   // fixed full‑bore

            // ---------- Solver ----------
            solver = new Solver { SubStepCount = 5, EnableProfiling = false };
            solver.SetTimeStep(dt);
            solver.SetPipeSystem(pipeSystem);
            solver.SetBoundarySystem(boundaries);
            solver.AddComponent(cylinder);
            solver.AddComponent(intakePlenum);

            // ---- Sound ----
            exhaustSound = new SoundProcessor(sampleRate, 1f) { Gain = 20f };
            intakeSound = new SoundProcessor(sampleRate, 1f) { Gain = 20f };
            reverb = new OutdoorExhaustReverb(sampleRate);

            stepCount = 0;
            Console.WriteLine("Single‑cylinder engine (YX140) ready.");
        }

        public override float Process()
        {
            // ---- Crank and cylinder pre‑step ----
            crankshaft.Step((float)dt);
            cylinder.PreStep((float)dt);

            // ---- Update variable areas ----
            float throttledArea = MaxThrottleArea * Math.Clamp(Throttle, 0.0001f, 1.0f);
            orificeAreas[throttleAreaIdx] = throttledArea;
            orificeAreas[intakeValveIdx]  = cylinder.IntakeValveArea;
            orificeAreas[exhaustValveIdx] = cylinder.ExhaustValveArea;
            boundaries.SetOrificeAreas(orificeAreas);

            // ---- Fluids step ----
            solver.Step();
            stepCount++;

            // ---- Sound ----
            float exhaustFlow = boundaries.GetOpenEndMassFlow(exhaustOpenIdx);
            float intakeFlow  = boundaries.GetOpenEndMassFlow(intakeOpenIdx);

            float exhaustDry = exhaustSound.Process(exhaustFlow);
            float intakeDry  = intakeSound.Process(intakeFlow);

            if (stepCount % 2000 == 0)
            {
                float rpm = crankshaft.AngularVelocity * 60f / (2f * MathF.PI);
                float crankDeg = crankshaft.CrankAngle;   // public property on Cylinder
                Console.WriteLine($"Step {stepCount}, CA={crankDeg:F1} deg, RPM={rpm:F0}, CylP={cylinder.Pressure / 1e5f:F2} bar");
                Console.WriteLine($"  intake flow: {intakeFlow:F6}, exhaust flow: {exhaustFlow:F6}");

                // Pipe 0 (intake before throttle)
                var (r0L, u0L, p0L) = pipeSystem.GetInteriorStateLeft(0);
                var (r0R, u0R, p0R) = pipeSystem.GetInteriorStateRight(0);
                Console.WriteLine($"  Pipe0 L: rho={r0L:F4} u={u0L:F3} p={p0L/1e5:F3}bar | R: rho={r0R:F4} u={u0R:F3} p={p0R/1e5:F3}bar");

                // Pipe 1 (runner)
                var (r1L, u1L, p1L) = pipeSystem.GetInteriorStateLeft(1);
                var (r1R, u1R, p1R) = pipeSystem.GetInteriorStateRight(1);
                Console.WriteLine($"  Pipe1 L: rho={r1L:F4} u={u1L:F3} p={p1L/1e5:F3}bar | R: rho={r1R:F4} u={u1R:F3} p={p1R/1e5:F3}bar");

                // Pipe 2 (exhaust)
                var (r2L, u2L, p2L) = pipeSystem.GetInteriorStateLeft(2);
                var (r2R, u2R, p2R) = pipeSystem.GetInteriorStateRight(2);
                Console.WriteLine($"  Pipe2 L: rho={r2L:F4} u={u2L:F3} p={p2L/1e5:F3}bar | R: rho={r2R:F4} u={u2R:F3} p={p2R/1e5:F3}bar");

                // Plenum and cylinder mass
                Console.WriteLine($"  Plenum P={intakePlenum.Pressure/1e5:F3}bar, mass={intakePlenum.Mass:E4} kg");
                Console.WriteLine($"  Cyl mass={cylinder.Mass:E4} kg");
            }

            return reverb.Process(intakeDry + exhaustDry);
        }

        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;
            float leftX = 40f;

            // Intake open end marker
            var om = new CircleShape(5f) { FillColor = Color.Cyan };
            om.Position = new Vector2f(leftX - 5f, intakeY - 5f);
            target.Draw(om);

            // Pipe 0 – before throttle
            float p0EndX = leftX + 80f;
            DrawPipe(target, pipeSystem, 0, intakeY, leftX, p0EndX);

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

            // Plenum volume
            float plenW = 60f, plenH = 50f;
            float plenLeftX = thrX + 12f;
            float plenCenterX = plenLeftX + plenW / 2f;
            float plenTopY = intakeY - plenH / 2f;
            DrawVolume(target, intakePlenum, plenCenterX, plenTopY, plenW, plenH);

            // Pipe 1 – runner
            float rStartX = plenLeftX + plenW + 10f;
            float rEndX = rStartX + 100f;
            DrawPipe(target, pipeSystem, 1, intakeY, rStartX, rEndX);

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

            // Pipe 2 – exhaust
            float exhStartX = cylCX + cylW / 2f + 20f;
            float exhEndX = winW - 60f;
            DrawPipe(target, pipeSystem, 2, exhaustY, exhStartX, exhEndX);

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