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

namespace FluidSim.Tests
{
    public class TwoStrokeScenario : Scenario
    {
        private Crankshaft crankshaft;
        private TwoStrokeCylinder cylinder;

        private PipeSystem pipeSystem;
        private BoundarySystem boundaries;
        private Solver solver;

        private Volume0D intakePlenum;
        private Port plenumInlet, plenumOutlet;
        private Volume0D exhaustMuffler;
        private Port mufflerIn, mufflerOut;

        private Vehicle vehicle;

        private int throttleAreaIdx, plenumRunnerIdx, intakeValveIdx, exhaustValveIdx;
        private float[] orificeAreas;
        private int intakeOpenIdx, exhaustOpenIdx;

        private SoundProcessor exhaustSound, intakeSound;
        private OutdoorExhaustReverb reverb;

        private double dt;
        private int stepCount;

        private float _maxThrottleArea;
        private float intakePipeArea, exhaustHeaderArea;

        // -- Override shift from Scenario base class --
        public override void ShiftUp() => vehicle.ShiftUp();
        public override void ShiftDown() => vehicle.ShiftDown();

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

            // ---- Vehicle ----
            vehicle = new Vehicle();

            // ---- Throttle (38 mm) ----
            _maxThrottleArea = (float)Units.AreaFromDiameter(38 * Units.mm);

            // ---- Crankshaft ----
            crankshaft = new Crankshaft(2000);
            crankshaft.CycleLength = 2f * MathF.PI;   // two‑stroke
            crankshaft.Inertia = 0.05f;               // engine's own inertia (light)
            crankshaft.FrictionConstant = 2.5f;
            crankshaft.FrictionViscous = 0.0015f;

            // ---- Cylinder (125cc) ----
            float bore = 0.054f, stroke = 0.0545f, conRod = 0.109f, compRatio = 12.5f;

            // Symmetric durations (around BDC)
            float transferDuration = 130f;   // 130°
            float exhaustDuration = 190f;    // 190°

            cylinder = new TwoStrokeCylinder(bore, stroke, conRod, compRatio,
                                             transferDuration, exhaustDuration,
                                             crankshaft)
            {
                IntakeValveDiameter  = 0.038f,
                IntakeValveLift      = 0.010f,
                ExhaustValveDiameter = 0.040f,
                ExhaustValveLift     = 0.010f
            };

            // ---- Pipe system (60 exhaust cells, simple diffuser) ----
            int intakeCells  = 8;
            int runnerCells  = 8;
            int exhaustCells = 60;
            int totalCells   = intakeCells + runnerCells + exhaustCells;
            int[] pipeStart  = { 0, intakeCells, intakeCells + runnerCells };
            int[] pipeEnd    = { intakeCells, intakeCells + runnerCells, totalCells };

            float[] area = new float[totalCells];
            float[] dx   = new float[totalCells];

            float intakeDia = 0.038f;
            float intakeLenBefore = 0.15f;
            float intakeLenRunner = 0.20f;
            intakePipeArea = MathF.PI * 0.25f * intakeDia * intakeDia;

            // Single‑stage diffuser – 840 mm total, easy to tune
            float headerDia   = 0.042f, headerLen   = 0.160f;
            float diffuserLen = 0.250f, diffuserEndDia = 0.070f;   // belly
            float bellyLen    = 0.240f;
            float convergentLen = 0.120f;
            float stingerDia  = 0.026f, stingerLen  = 0.070f;
            // total = 0.16 + 0.25 + 0.24 + 0.12 + 0.07 = 0.84 m

            exhaustHeaderArea = MathF.PI * 0.25f * headerDia * headerDia;
            float bellyArea   = MathF.PI * 0.25f * diffuserEndDia * diffuserEndDia;
            float stingerArea = MathF.PI * 0.25f * stingerDia * stingerDia;

            float totalExhaustLen = headerLen + diffuserLen + bellyLen + convergentLen + stingerLen; // 840 mm
            int headerCells     = (int)(exhaustCells * (headerLen / totalExhaustLen));
            int diffuserCells   = (int)(exhaustCells * (diffuserLen / totalExhaustLen));
            int bellyCells      = (int)(exhaustCells * (bellyLen / totalExhaustLen));
            int convergentCells = (int)(exhaustCells * (convergentLen / totalExhaustLen));
            int stingerCells    = exhaustCells - headerCells - diffuserCells - bellyCells - convergentCells;

            // Fill cells
            for (int i = 0; i < intakeCells; i++)
            { area[i] = intakePipeArea; dx[i] = intakeLenBefore / intakeCells; }
            for (int i = intakeCells; i < intakeCells + runnerCells; i++)
            { area[i] = intakePipeArea; dx[i] = intakeLenRunner / runnerCells; }

            int exhStart = intakeCells + runnerCells;
            int idx = 0;
            for (int i = exhStart; i < totalCells; i++)
            {
                if (idx < headerCells)
                { area[i] = exhaustHeaderArea; dx[i] = headerLen / headerCells; }
                else if (idx < headerCells + diffuserCells)
                {
                    float t = (idx - headerCells) / (float)(diffuserCells - 1);
                    float dia = headerDia + (diffuserEndDia - headerDia) * t;
                    area[i] = MathF.PI * 0.25f * dia * dia;
                    dx[i] = diffuserLen / diffuserCells;
                }
                else if (idx < headerCells + diffuserCells + bellyCells)
                { area[i] = bellyArea; dx[i] = bellyLen / bellyCells; }
                else if (idx < headerCells + diffuserCells + bellyCells + convergentCells)
                {
                    float t = (idx - headerCells - diffuserCells - bellyCells) / (float)(convergentCells - 1);
                    float dia = diffuserEndDia + (stingerDia - diffuserEndDia) * t;
                    area[i] = MathF.PI * 0.25f * dia * dia;
                    dx[i] = convergentLen / convergentCells;
                }
                else
                { area[i] = stingerArea; dx[i] = stingerLen / stingerCells; }
                idx++;
            }

            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(0.5e-3f, 101325f, 300f);
            plenumInlet  = intakePlenum.CreatePort();
            plenumOutlet = intakePlenum.CreatePort();

            exhaustMuffler = new Volume0D(5e-4f, 101325f, 600f);
            mufflerIn  = exhaustMuffler.CreatePort();
            mufflerOut = exhaustMuffler.CreatePort();

            // ---- Boundary system ----
            boundaries = new BoundarySystem(pipeSystem, maxOrifices: 4, maxOpenEnds: 2);
            throttleAreaIdx = 0; plenumRunnerIdx = 1; intakeValveIdx = 2; exhaustValveIdx = 3;

            boundaries.AddOpenEnd(pipeIndex: 0, isLeftEnd: true,  101325f, intakePipeArea);
            intakeOpenIdx = 0;
            boundaries.AddOpenEnd(pipeIndex: 2, isLeftEnd: false, 101325f, stingerArea);
            exhaustOpenIdx = 1;

            boundaries.AddOrifice(plenumInlet,         0, false, throttleAreaIdx, 0.7f);
            boundaries.AddOrifice(plenumOutlet,        1, true,  plenumRunnerIdx, 1.0f);
            boundaries.AddOrifice(cylinder.IntakePort, 1, false, intakeValveIdx, 0.65f);
            boundaries.AddOrifice(cylinder.ExhaustPort,2, true,  exhaustValveIdx, 0.68f);

            orificeAreas = new float[4];
            orificeAreas[plenumRunnerIdx] = intakePipeArea;

            // ---- Solver ----
            solver = new Solver { SubStepCount = 4, EnableProfiling = false };   // 4 sub‑steps for 60 cells
            solver.SetTimeStep(dt);
            solver.SetPipeSystem(pipeSystem);
            solver.SetBoundarySystem(boundaries);
            solver.AddComponent(cylinder);
            solver.AddComponent(intakePlenum);
            solver.AddComponent(exhaustMuffler);

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

            stepCount = 0;
            Console.WriteLine("125cc Two‑Stroke with vehicle coupling ready.");
        }

        public override float Process()
        {
            float engineRpm = crankshaft.AngularVelocity * 60f / (2f * MathF.PI);

            vehicle.ClutchInput = Clutch;

            var (clutchTorque, effectiveInertia) = vehicle.Update(engineRpm, crankshaft.Inertia, (float)dt);
            crankshaft.SetEffectiveInertia(effectiveInertia);
            crankshaft.SetLoadTorque(clutchTorque);   // clutch torque now includes drag when locked

            crankshaft.Step((float)dt);
            cylinder.PreStep((float)dt);

            float throttledArea = _maxThrottleArea * Math.Clamp(Throttle, 0.001f, 1f);
            orificeAreas[throttleAreaIdx] = throttledArea;
            orificeAreas[intakeValveIdx] = cylinder.IntakeValveArea;
            orificeAreas[exhaustValveIdx] = cylinder.ExhaustValveArea;
            boundaries.SetOrificeAreas(orificeAreas);

            solver.Step();
            stepCount++;

            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);
                Console.WriteLine($"Step {stepCount}, RPM={rpm:F0}, Gear={vehicle.CurrentGear}, Speed={vehicle.SpeedKmh:F0} km/h");
            }

            return reverb.Process((intakeDry + exhaustDry) * 0.5f);
        }

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

            // Intake pipe
            float x = openEndX;
            float w = 120f;
            DrawPipe(target, pipeSystem, 0, intakeY, x, x + w);

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

            // Plenum
            float plenW = 40f, plenH = 60f;
            float plenX = throttleX + 10f;
            DrawVolume(target, intakePlenum, plenX + plenW / 2f, intakeY - plenH / 2f, plenW, plenH);

            // Runner
            float runnerStartX = plenX + plenW + 5f;
            DrawPipe(target, pipeSystem, 1, intakeY, runnerStartX, runnerStartX + 100f);

            // Cylinder
            float cylCX = runnerStartX + 150f;
            float cylTopY = intakeY - 120f;
            DrawCylinder(target, cylinder, cylCX, cylTopY, 80f, 240f);

            // Exhaust pipe
            float exhStartX = cylCX + 40f + 20f;
            DrawPipe(target, pipeSystem, 2, exhaustY, exhStartX, winW - 60f, areaScale: 1000f);

            // Labels
            float rpm = crankshaft.AngularVelocity * 60f / (2f * MathF.PI);
            float powerKw = crankshaft.AveragePower * 1e-3f;
            DrawLabel(target, $"RPM: {rpm:F0}", new Vector2f(20, 90), Color.White, 24);
            DrawLabel(target, $"Power: {powerKw:F2} kW", new Vector2f(20, 115), Color.White, 24);
            DrawLabel(target, $"Gear: {vehicle.CurrentGear}", new Vector2f(20, 140), Color.Cyan, 20);
            DrawLabel(target, $"Speed: {vehicle.SpeedKmh:F0} km/h", new Vector2f(20, 160), Color.Cyan, 20);

            // Dyno curve
            float torqueNm = crankshaft.AverageTorque;
            UpdateDynoCurve(rpm, powerKw, torqueNm);
            DrawDynoCurve(target, winW - 410f, winH - 260f, 400f, 250f, rpm, powerKw);

            string gearText = vehicle.CurrentGear == 0 ? "N" : vehicle.CurrentGear.ToString();
            DrawLabel(target, $"Gear: {gearText}", new Vector2f(20, 140), Color.Cyan, 20);
            DrawLabel(target, $"Speed: {vehicle.SpeedKmh:F0} km/h", new Vector2f(20, 160), Color.Cyan, 20);
            DrawLabel(target, vehicle.Engagement > 0.99f ? "Clutch Locked" : "Clutch Slipping", new Vector2f(20, 180), Color.Cyan, 14);
        }
    }
}