using System;
using Bmaju.InnerMinmus.RingShadows;
class CloudLightTests
{
    static int passed;
    static void Check(bool good,string name) { if(!good) throw new Exception(name);passed++;Console.WriteLine("PASS "+name); }
    static bool Near(double a,double b) { return Math.Abs(a-b)<1e-9; }
    static RingEmitter E(D3 point,D3 colour) { return new RingEmitter { Point=point,Radiance=colour,ToSun=CloudLightMath.Unit(point*(-1)) }; }
    sealed class Receiver { public double Value; }
    static void Main()
    {
        var none=new GlowOccluder[0];D3 up=new D3(0,1,0);
        var e=E(new D3(0,10,0),new D3(10,5,2));
        var a=CloudLightMath.Sample(new[]{e},new D3(),up,none,1,100);
        Check(Near(a.Colour.x,0.1)&&Near(a.Colour.y,0.05)&&Near(a.Colour.z,0.02),"cloud irradiance preserves source colour and inverse square");
        Check(Near(a.Direction.y,1)&&Near(a.Spread,0),"single patch has zero angular spread");
        var b=CloudLightMath.Sample(new[]{e,e},new D3(),up,none,1,100);
        Check(Near(b.Colour.x,2*a.Colour.x),"independent illuminated ring patches add energy");
        b=CloudLightMath.Sample(new[]{e},new D3(),up,none,4,0.12);
        Check(Near(b.Colour.x,0.12)&&Near(b.Colour.y,0.06),"cloud intensity cap preserves chromatic ratios");
        b=CloudLightMath.Sample(new[]{e},new D3(),up,none,0,0.12);
        Check(Near(b.Colour.Length,0),"zero exposure produces no artificial brightness floor");
        b=CloudLightMath.Sample(new[]{E(new D3(0,-10,0),new D3(1,1,1))},new D3(),up,none,1,100);
        Check(Near(b.Colour.Length,0),"below-horizon ring patch is rejected");
        b=CloudLightMath.Sample(new[]{e},new D3(),up,new[]{new GlowOccluder(new D3(0,5,0),1)},1,100);
        Check(Near(b.Colour.Length,0),"solid body between cloud and ring blocks reflection");
        b=CloudLightMath.Sample(new[]{e},new D3(),up,new[]{new GlowOccluder(new D3(0,20,0),1)},1,100);
        Check(Near(b.Colour.x,a.Colour.x),"body beyond ring does not block finite light segment");
        b=CloudLightMath.Sample(new[]{E(new D3(10,10,0),new D3(10,10,10)),E(new D3(-10,10,0),new D3(10,10,10))},new D3(),up,none,1,100);
        Check(Near(b.Direction.y,1)&&b.Spread>0.29&&b.Spread<0.30,"distributed ring has stable moment direction and broad angular extent");
        double weight;var d=CloudLightMath.CombineDirection(new D3(1,0,0),new D3(1,1,1),new CloudLight(),out weight);
        Check(Near(d.x,1)&&Near(weight,0),"dark ring leaves native direction unchanged");
        d=CloudLightMath.CombineDirection(new D3(1,0,0),new D3(),a,out weight);
        Check(Near(d.y,1)&&Near(weight,1),"ring-only night works without any moon contribution");
        var ring=new CloudLight { Colour=new D3(1,1,1),Direction=new D3(0,1,0) };
        d=CloudLightMath.CombineDirection(new D3(1,0,0),new D3(1,1,1),ring,out weight);
        Check(Near(d.x,Math.Sqrt(0.5))&&Near(d.y,Math.Sqrt(0.5))&&Near(weight,0.5),"native and ring light retain luminance-weighted direction");
        ring.Direction=new D3(-1,0,0);d=CloudLightMath.CombineDirection(new D3(1,0,0),new D3(1,1,1),ring,out weight);
        Check(Near(d.Length,1),"opposed sources cannot generate NaN direction");
        Check(Near(CloudLightMath.Broaden(0.95,0.8,0),0.95)&&CloudLightMath.Broaden(0.95,0.8,1)<0.4,"phase broadening is limited to ring fraction");
        Check(CloudLightMath.Broaden(-0.8,0.8,1)<0,"backward scattering lobe retains its sign");
        var r=new Receiver { Value=2 };var scopes=new RenderOverrides<Receiver,double>((o,v)=>o.Value=v);
        scopes.Apply(r,o=>o.Value,o=>o.Value+=0.12);scopes.Apply(r,o=>o.Value,o=>o.Value+=0.12);
        Check(Near(r.Value,2.12),"repeated render update cannot accumulate ring energy");
        scopes.Restore(r);r.Value=3;scopes.Apply(r,o=>o.Value,o=>o.Value+=0.12);scopes.RestoreAll();
        Check(Near(r.Value,3),"solar uniform refresh establishes a fresh native baseline");
        try { scopes.Apply(r,o=>o.Value,o=>{o.Value=99;throw new Exception("write failure");}); } catch(Exception) { }
        scopes.RestoreAll();Check(Near(r.Value,3),"partial shader write remains recoverable");
        var other=new Receiver { Value=4 };
        scopes.Apply(r,o=>o.Value,o=>o.Value=8);scopes.Apply(other,o=>o.Value,o=>o.Value=9);scopes.RestoreAll();scopes.RestoreAll();
        Check(Near(r.Value,3)&&Near(other.Value,4),"render exit restores every layer and is idempotent");
        Console.WriteLine("TOTAL "+passed+" passed; managed light and render transaction tests, not native EVE rendering");
    }
}
