package com.foxx.androidcast.network;

/*********************************************************************
 * UdpCastTransportFecTest.java
 * Created at: Mon 18 May 2026 14:25:02 +0200
 * Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
 * Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
 * class UdpCastTransportFecTest
 * Contributors:
 *    - Anton Afanasyeu <a.afanasieff@gmail.com> (3 commits, 124 lines)
 *    - Cursor Agent (project assistant)
 * Digest: SHA256	52f01a8ad1607693ac16ed7445a5f17797ab34f23a7a0b307184055ef2f9f0d3
**********************************************************************/
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertNotNull;

import com.foxx.androidcast.CastConfig;
import com.foxx.androidcast.CastSettings;
import com.foxx.androidcast.network.transport.StreamProtectionFactory;

import com.foxx.androidcast.network.CastFramingContext;

import org.junit.Test;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.util.concurrent.atomic.AtomicReference;

public class UdpCastTransportFecTest {
    @Test
    public void fecShardGroup_deliversPayloadOverLoopback() throws Exception {
        int portB = 37000 + (int) (System.nanoTime() % 2000);
        UdpCastTransport sender = new UdpCastTransport();
        UdpCastTransport receiver = new UdpCastTransport();
        try {
            receiver.prepareListen(portB);
            sender.connect("127.0.0.1", portB);
            CastSettings fec = settingsWith(CastSettings.StreamProtection.FEC_3_4);
            CastTransportFactory.applyNegotiatedUdpProtection(receiver, fec, fec, true);
            CastTransportFactory.applyNegotiatedUdpProtection(sender, fec, fec, false);

            byte[] payload = new byte[900];
            for (int i = 0; i < payload.length; i++) {
                payload[i] = (byte) (i & 0xFF);
            }

            Thread t = new Thread(() -> {
                try {
                    sender.send(CastProtocol.MSG_VIDEO_FRAME, payload);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();

            AtomicReference<byte[]> received = new AtomicReference<>();
            long deadline = System.currentTimeMillis() + 5000;
            while (System.currentTimeMillis() < deadline && received.get() == null) {
                CastProtocol.Message msg = receiver.receive(200);
                if (msg != null && msg.type == CastProtocol.MSG_VIDEO_FRAME) {
                    received.set(msg.payload);
                }
            }
            t.join(2000);
            assertNotNull(received.get());
            assertArrayEquals(payload, received.get());
        } finally {
            sender.close();
            receiver.close();
        }
    }

    @Test
    public void fecShardGroup_wireFramedLargeKeyframe_delivers() throws Exception {
        int portB = 38000 + (int) (System.nanoTime() % 2000);
        UdpCastTransport sender = new UdpCastTransport();
        UdpCastTransport receiver = new UdpCastTransport();
        CastFramingContext framing = new CastFramingContext(42, 1, 0, 0);
        try {
            receiver.prepareListen(portB);
            sender.connect("127.0.0.1", portB);
            sender.setFramingContext(framing);
            receiver.setFramingContext(framing);
            CastSettings fec = settingsWith(CastSettings.StreamProtection.FEC_3_4);
            CastTransportFactory.applyNegotiatedUdpProtection(receiver, fec, fec, true);
            CastTransportFactory.applyNegotiatedUdpProtection(sender, fec, fec, false);

            byte[] video = new byte[171_077];
            for (int i = 0; i < video.length; i++) {
                video[i] = (byte) (i & 0xFF);
            }
            byte[] videoMsg = CastProtocol.videoFramePayload(0, true, video);

            Thread t = new Thread(() -> {
                try {
                    sender.send(CastProtocol.MSG_VIDEO_FRAME, videoMsg);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            });
            t.start();

            AtomicReference<byte[]> received = new AtomicReference<>();
            long deadline = System.currentTimeMillis() + 15_000;
            while (System.currentTimeMillis() < deadline && received.get() == null) {
                CastProtocol.Message msg = receiver.receive(500);
                if (msg != null && msg.type == CastProtocol.MSG_VIDEO_FRAME) {
                    CastProtocol.VideoFrame vf = CastProtocol.parseVideoFrame(msg.payload);
                    received.set(vf.data);
                }
            }
            t.join(3000);
            assertNotNull(received.get());
            assertArrayEquals(video, received.get());
        } finally {
            sender.close();
            receiver.close();
        }
    }

    private static CastSettings settingsWith(CastSettings.StreamProtection protection) {
        CastSettings s = new CastSettings();
        s.setTransport(CastConfig.TRANSPORT_UDP);
        s.setStreamProtection(protection);
        return s;
    }
}
