package com.foxx.androidcast.network.control;

/*********************************************************************
 * NetworkFeedbackManager.java
 * Created at: Fri 15 May 2026 17:47:09 +0200
 * Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
 * Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
 * class NetworkFeedbackManager
 * Contributors:
 *    - Anton Afanasyeu <a.afanasieff@gmail.com> (2 commits, 98 lines)
 *    - Cursor Agent (project assistant)
 * Digest: SHA256	03dc7300be054e07eef44367194228e77c7c25dab3efe96b9e835df5b14ab902
**********************************************************************/
import android.util.Log;

import com.foxx.androidcast.network.CastProtocol;
import com.foxx.androidcast.network.CastSession;
import com.foxx.androidcast.network.diag.DiagPingSession;

import java.io.IOException;

/** Bridges {@link NetworkControlPlane} to {@link CastSession} send/receive. */
public class NetworkFeedbackManager {
    private static final String TAG = "NetworkFeedback";

    private final NetworkControlPlane plane;
    private final CastSession session;
    private final boolean isSender;
    private DiagPingSession diagPing;

    public NetworkFeedbackManager(CastSession session, boolean isSender, NetworkControlPlane plane) {
        this.session = session;
        this.isSender = isSender;
        this.plane = plane != null ? plane : new PassThroughNetworkControlPlane();
    }

    public NetworkFeedbackManager(CastSession session, boolean isSender) {
        this(session, isSender, new PassThroughNetworkControlPlane());
    }

    public NetworkControlPlane getPlane() {
        return plane;
    }

    public void configureDiagPing(boolean enabled, byte responseMode, DiagPingSession.SampleListener listener) {
        if (!enabled) {
            if (diagPing != null) {
                diagPing.setEnabled(false);
            }
            return;
        }
        diagPing = new DiagPingSession(session, responseMode, listener);
        diagPing.setEnabled(true);
    }

    public DiagPingSession getDiagPingSession() {
        return diagPing;
    }

    /** Call on each stream loop iteration (~500ms). */
    public void tick() {
        long now = System.currentTimeMillis();
        if (diagPing != null) {
            diagPing.tick(now);
        }
        NetworkControlAdvice advice = plane.tick(now);
        if (advice.sendStats) {
            sendStats(plane.buildLocalStats(now));
        }
        if (advice.sendControl) {
            sendControl(advice);
        }
    }

    public boolean handleMessage(CastProtocol.Message msg) {
        if (diagPing != null && diagPing.handleMessage(msg)) {
            applyDiagPingRtt();
            return true;
        }
        try {
            switch (msg.type) {
                case CastProtocol.MSG_NETWORK_STATS:
                    plane.onPeerStats(CastProtocol.parseNetworkStats(msg.payload));
                    return true;
                case CastProtocol.MSG_NETWORK_CONTROL:
                    CastProtocol.NetworkControl ctrl = CastProtocol.parseNetworkControl(msg.payload);
                    plane.onPeerControl(ctrl.suggestedBitrateKbps, ctrl.suggestedTransport,
                            ctrl.switchTransport, ctrl.congestionLevel);
                    applyAdvice(plane.getOutboundAdvice());
                    return true;
                default:
                    return false;
            }
        } catch (IOException e) {
            Log.w(TAG, "Network message parse failed", e);
            return false;
        }
    }

    private void applyDiagPingRtt() {
        if (diagPing == null || !(plane instanceof PassThroughNetworkControlPlane)) {
            return;
        }
        PassThroughNetworkControlPlane pass = (PassThroughNetworkControlPlane) plane;
        int rtt = diagPing.metrics().preferredRttMs(pass.getDiagPingRttMs());
        pass.setDiagPingRttMs(rtt);
    }

    private void sendStats(NetworkStatsSnapshot stats) {
        try {
            session.send(CastProtocol.MSG_NETWORK_STATS, CastProtocol.networkStatsPayload(stats));
        } catch (IOException e) {
            Log.w(TAG, "Send stats failed", e);
        }
    }

    private void sendControl(NetworkControlAdvice advice) {
        try {
            session.send(CastProtocol.MSG_NETWORK_CONTROL,
                    CastProtocol.networkControlPayload(advice));
        } catch (IOException e) {
            Log.w(TAG, "Send control failed", e);
        }
    }

    /** Hook for sender to apply bitrate/transport hints (stub-friendly). */
    public void applyAdvice(NetworkControlAdvice advice) {
        if (advice.suggestedBitrateKbps > 0 && isSender) {
            Log.d(TAG, "Bitrate hint (not applied yet): " + advice.suggestedBitrateKbps);
        }
        if (advice.switchTransport) {
            Log.d(TAG, "Transport switch hint (not applied yet): " + advice.suggestedTransport);
        }
    }
}
