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 Crankcase crankcase;

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

        private Volume0D intakePlenum;
        private Port plenumInlet, plenumOutlet;

        private int throttleAreaIdx, reedInletIdx, reedOutletIdx,
                    transferInletIdx, transferOutletIdx, 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, reedPipeArea, transferPipeArea, exhaustHeaderArea;
        private bool reedOpen;

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

            maxThrottleArea = (float)Units.AreaFromDiameter(42 * Units.mm);

            // ---- Crankshaft ----
            crankshaft = new Crankshaft(3000);
            crankshaft.CycleLength = 2f * MathF.PI;
            crankshaft.Inertia = 0.01f;
            crankshaft.FrictionConstant = 1.0f;
            crankshaft.FrictionViscous = 0.002f;

            // ---- Cylinder (125cc) ----
            float bore = 0.054f, stroke = 0.0545f, conRod = 0.110f, compRatio = 7.2f;
            float transferDur = 140f, exhaustDur = 195f;
            cylinder = new TwoStrokeCylinder(bore, stroke, conRod, compRatio,
                                            transferDur, exhaustDur, crankshaft)
            {
                // FIX: realistic transfer port diameter (was 40mm)
                IntakeValveDiameter  = 0.030f,   // 30 mm
                IntakeValveLift      = 0.010f,
                ExhaustValveDiameter = 0.040f,
                ExhaustValveLift     = 0.010f
            };

            // ---- Crankcase ----
            float crankRadius = stroke * 0.5f;
            float ccClearance = 150e-6f;
            crankcase = new Crankcase(crankshaft, crankRadius, conRod, bore,
                                    ccClearance, 101325f, 300f);
            cylinder.SetCrankcase(crankcase);

            // ---- Pipe system ----
            int intakeCells    = 8;
            int reedCells      = 4;
            int transferCells  = 8;
            int exhaustCells   = 60;
            int totalCells = intakeCells + reedCells + transferCells + exhaustCells;

            int[] pipeStart = {
                0,
                intakeCells,
                intakeCells + reedCells,
                intakeCells + reedCells + transferCells
            };
            int[] pipeEnd = {
                intakeCells,
                intakeCells + reedCells,
                intakeCells + reedCells + transferCells,
                totalCells
            };

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

            float intakeDia = 0.042f, reedDia = 0.040f, transferDia = 0.040f;
            intakePipeArea   = MathF.PI * 0.25f * intakeDia * intakeDia;
            reedPipeArea     = MathF.PI * 0.25f * reedDia * reedDia;
            transferPipeArea = MathF.PI * 0.25f * transferDia * transferDia;

            for (int i = 0; i < intakeCells; i++)
            { area[i] = intakePipeArea; dx[i] = 0.100f / intakeCells; }

            for (int i = intakeCells; i < intakeCells + reedCells; i++)
            { area[i] = reedPipeArea; dx[i] = 0.030f / reedCells; }

            for (int i = intakeCells + reedCells; i < intakeCells + reedCells + transferCells; i++)
            { area[i] = transferPipeArea; dx[i] = 0.200f / transferCells; }

            float hdrD = 0.040f, hdrL = 0.130f;
            float difEndD = 0.070f, difL = 0.250f;
            float belL = 0.220f;
            float convEndD = 0.028f, convL = 0.160f;
            float stiL = 0.080f;
            float totL = hdrL + difL + belL + convL + stiL;
            exhaustHeaderArea = MathF.PI * 0.25f * hdrD * hdrD;
            float bellyArea = MathF.PI * 0.25f * difEndD * difEndD;
            float stingerArea = MathF.PI * 0.25f * convEndD * convEndD;

            int exhStart = intakeCells + reedCells + transferCells;
            int hdrC = (int)(exhaustCells * hdrL / totL);
            int difC = (int)(exhaustCells * difL / totL);
            int belC = (int)(exhaustCells * belL / totL);
            int conC = (int)(exhaustCells * convL / totL);
            int stiC = exhaustCells - hdrC - difC - belC - conC;

            int idx = 0;
            for (int i = exhStart; i < totalCells; i++)
            {
                if (idx < hdrC) { area[i] = exhaustHeaderArea; dx[i] = hdrL / hdrC; }
                else if (idx < hdrC + difC)
                {
                    float t = (idx - hdrC) / (float)(difC - 1);
                    float dia = hdrD + (difEndD - hdrD) * t;
                    area[i] = MathF.PI * 0.25f * dia * dia; dx[i] = difL / difC;
                }
                else if (idx < hdrC + difC + belC) { area[i] = bellyArea; dx[i] = belL / belC; }
                else if (idx < hdrC + difC + belC + conC)
                {
                    float t = (idx - hdrC - difC - belC) / (float)(conC - 1);
                    float dia = difEndD + (convEndD - difEndD) * t;
                    area[i] = MathF.PI * 0.25f * dia * dia; dx[i] = convL / conC;
                }
                else { area[i] = stingerArea; dx[i] = stiL / stiC; }
                idx++;
            }

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

            // ---- Volumes ----
            intakePlenum = new Volume0D(0.5e-3f, 101325f, 300f);
            plenumInlet  = intakePlenum.CreatePort();
            plenumOutlet = intakePlenum.CreatePort();

            // ---- Boundary system ----
            boundaries = new BoundarySystem(pipeSystem, maxOrifices: 6, maxOpenEnds: 2);
            throttleAreaIdx   = 0;
            reedInletIdx      = 1;
            reedOutletIdx     = 2;
            transferInletIdx  = 3;
            transferOutletIdx = 4;
            exhaustValveIdx   = 5;

            boundaries.AddOpenEnd(0, true,  101325f, intakePipeArea);
            intakeOpenIdx = 0;
            boundaries.AddOpenEnd(3, false, 101325f, stingerArea);
            exhaustOpenIdx = 1;

            boundaries.AddOrifice(plenumInlet,           0, false, throttleAreaIdx, 0.72f);
            boundaries.AddOrifice(plenumOutlet,          1, true,  reedInletIdx,   1.0f);
            boundaries.AddOrifice(crankcase.IntakePort,  1, false, reedOutletIdx,  0.9f);
            boundaries.AddOrifice(crankcase.TransferPort,2, true,  transferInletIdx,1.0f);
            boundaries.AddOrifice(cylinder.IntakePort,   2, false, transferOutletIdx,1.0f);
            boundaries.AddOrifice(cylinder.ExhaustPort,  3, true,  exhaustValveIdx, 0.7f);

            orificeAreas = new float[6];
            orificeAreas[reedInletIdx]      = reedPipeArea;
            orificeAreas[reedOutletIdx]     = 0f;
            orificeAreas[transferInletIdx]  = transferPipeArea;
            orificeAreas[transferOutletIdx] = 0f;

            // ---- Solver ----
            solver = new Solver { SubStepCount = 4 };
            solver.SetTimeStep(dt);
            solver.SetPipeSystem(pipeSystem);
            solver.SetBoundarySystem(boundaries);
            solver.AddComponent(cylinder);
            solver.AddComponent(crankcase);
            solver.AddComponent(intakePlenum);

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

            stepCount = 0;
            Console.WriteLine("Two‑Stroke engine ready.");
        }

        public override float Process()
        {
            const float reedMargin = 200f;
            if (crankcase.Pressure < intakePlenum.Pressure - reedMargin)
                reedOpen = true;
            else if (crankcase.Pressure > intakePlenum.Pressure + reedMargin)
                reedOpen = false;

            float throttledFraction = Throttle;
            if (throttledFraction < 0.001f) throttledFraction = 0f;
            throttledFraction = Math.Clamp(throttledFraction, 0f, 1f);
            float throttledArea = maxThrottleArea * throttledFraction;

            orificeAreas[throttleAreaIdx] = throttledArea;
            orificeAreas[reedOutletIdx] = reedOpen ? reedPipeArea : 0f;
            orificeAreas[transferOutletIdx] = cylinder.IntakeValveArea;
            orificeAreas[exhaustValveIdx] = cylinder.ExhaustValveArea;
            boundaries.SetOrificeAreas(orificeAreas);

            if (stepCount < 20000)
                crankshaft.AddTorque(5.0f);

            // FIX: update crankshaft BEFORE volumes, so crankcase and cylinder see the same new angle
            crankshaft.Step((float)dt);
            cylinder.PreStep((float)dt);
            crankcase.PreStep((float)dt);

            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} | CylP={cylinder.Pressure/1e5f:F2} bar | CCP={crankcase.Pressure/1e5f:F3} bar | Plenum={intakePlenum.Pressure/1e5f:F3} bar | Reed={reedOpen}");
            }

            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 startX = 40f;
            float endX = winW - 80f;

            DrawPipe(target, pipeSystem, 0, winH * 0.25f, startX, startX + 120f);
            var throttleRect = new RectangleShape(new Vector2f(8f, 30f))
            {
                FillColor = Color.Yellow,
                Position = new Vector2f(startX + 125f, winH * 0.25f - 15f)
            };
            target.Draw(throttleRect);
            float plenX = startX + 140f;
            DrawVolume(target, intakePlenum, plenX + 30f, winH * 0.25f - 25f, 60f, 50f);

            float reedStartX = plenX + 70f;
            DrawPipe(target, pipeSystem, 1, winH * 0.25f, reedStartX, reedStartX + 30f);

            float transStartX = reedStartX + 40f;
            DrawPipe(target, pipeSystem, 2, winH * 0.45f, transStartX, transStartX + 120f);

            float cylCX = transStartX + 180f;
            float cylTopY = winH * 0.45f - 90f;
            DrawCylinder(target, cylinder, cylCX, cylTopY, 80f, 240f);

            float exhStartX = cylCX + 60f;
            DrawPipe(target, pipeSystem, 3, winH * 0.65f, exhStartX, endX, areaScale: 800f);

            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);

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