package com.foxx.androidcast.network.quic;

/*********************************************************************
 * QuicCronetCastTransport.java
 * Created at: Sat 16 May 2026 18:50:36 +0200
 * Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
 * Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
 * class QuicCronetCastTransport
 * Contributors:
 *    - Anton Afanasyeu <a.afanasieff@gmail.com> (3 commits, 150 lines)
 *    - Cursor Agent (project assistant)
 * Digest: SHA256	388e37e6d9c73a9e2647d5e792807d03ba046dd4b5f542d2a3bc67fb82c5c5ea
**********************************************************************/
import android.content.Context;
import android.util.Log;

import com.foxx.androidcast.CastConfig;
import com.foxx.androidcast.network.CastFramingContext;
import com.foxx.androidcast.network.CastProtocol;
import com.foxx.androidcast.network.CastTransport;
import com.foxx.androidcast.network.TcpCastTransport;
import com.foxx.androidcast.network.UdpCastTransport;

import org.chromium.net.CronetEngine;

import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Experimental QUIC cast transport (Cronet stack).
 * <p>
 * Cronet on Android is a QUIC/HTTP3 <em>client</em> only. This transport uses:
 * <ul>
 *   <li>{@link CronetEngine} with QUIC enabled (shared via {@link CronetEngineProvider})</li>
 *   <li>Framed cast messages on {@link CastConfig#QUIC_PORT} over UDP (same as legacy ACUD framing)</li>
 *   <li>TCP fallback on the same port if UDP bind fails</li>
 * </ul>
 * A future revision can move the data plane to Cronet {@code BidirectionalStream} once an HTTP/3
 * terminator is available on the receiver.
 */
public final class QuicCronetCastTransport implements CastTransport {
    private static final String TAG = "QuicCronetTransport";

    private final Context appContext;
    private final UdpCastTransport udp = new UdpCastTransport();
    private final TcpCastTransport tcpFallback = new TcpCastTransport();
    private final AtomicBoolean usingTcp = new AtomicBoolean(false);
    private CronetEngine engine;

    public QuicCronetCastTransport(Context context) {
        this.appContext = context.getApplicationContext();
    }

    /** Underlying UDP data plane (ACUD framing, FEC/NACK). */
    public UdpCastTransport getUdpTransport() {
        return udp;
    }

    private CastTransport active() {
        return usingTcp.get() ? tcpFallback : udp;
    }

    @Override
    public void setFramingContext(CastFramingContext context) {
        udp.setFramingContext(context);
        tcpFallback.setFramingContext(context);
    }

    @Override
    public void setInboundSessionGate(com.foxx.androidcast.network.CastSessionGate gate) {
        udp.setInboundSessionGate(gate);
        tcpFallback.setInboundSessionGate(gate);
    }

    private void ensureCronet() {
        if (engine != null) {
            return;
        }
        try {
            engine = CronetEngineProvider.getEngine(appContext);
            Log.i(TAG, "Cronet engine ready (QUIC/HTTP3 enabled for experimental transport)");
        } catch (Exception e) {
            Log.w(TAG, "Cronet engine unavailable: " + e.getMessage());
        }
    }

    @Override
    public void prepareListen(int port) throws IOException {
        ensureCronet();
        int quicPort = port > 0 ? port : CastConfig.QUIC_PORT;
        try {
            udp.prepareListen(quicPort);
            usingTcp.set(false);
            Log.i(TAG, "QUIC listen UDP :" + quicPort);
        } catch (IOException e) {
            Log.w(TAG, "QUIC UDP listen failed, TCP fallback :" + quicPort, e);
            tcpFallback.prepareListen(quicPort);
            usingTcp.set(true);
        }
    }

    @Override
    public void acceptClient() throws IOException {
        active().acceptClient();
    }

    @Override
    public void listen(int port) throws IOException {
        prepareListen(port);
        acceptClient();
    }

    @Override
    public void connect(String host, int port) throws IOException {
        ensureCronet();
        int quicPort = port > 0 ? port : CastConfig.QUIC_PORT;
        try {
            udp.connect(host, quicPort);
            usingTcp.set(false);
            Log.i(TAG, "QUIC connect UDP " + host + ":" + quicPort);
        } catch (IOException e) {
            Log.w(TAG, "QUIC UDP connect failed, TCP fallback", e);
            tcpFallback.connect(host, quicPort);
            usingTcp.set(true);
        }
    }

    @Override
    public void send(byte type, byte[] payload) throws IOException {
        active().send(type, payload);
    }

    @Override
    public CastProtocol.Message receive(int timeoutMs) throws IOException {
        return active().receive(timeoutMs);
    }

    @Override
    public String getModeLabel() {
        String base = usingTcp.get() ? "QUIC(exp/TCP)" : "QUIC(exp/UDP)";
        return engine != null ? base + "+Cronet" : base;
    }

    @Override
    public void releaseConnection() {
        active().releaseConnection();
    }

    @Override
    public void close() {
        udp.close();
        tcpFallback.close();
    }
}
