package com.foxx.androidcast;

/*********************************************************************
 * CastTuningEngine.java
 * Created at: Fri 15 May 2026 20:37:44 +0200
 * Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
 * Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
 * class CastTuningEngine
 * Contributors:
 *    - Anton Afanasyeu <a.afanasieff@gmail.com> (2 commits, 180 lines)
 *    - Cursor Agent (project assistant)
 * Digest: SHA256	fd7ab546e1d7cbf75ad2f827d9ee46c291cf966f6cbd7b5d7da7ff36203ce8b8
**********************************************************************/
import com.foxx.androidcast.network.control.NetworkStatsSnapshot;

/** Resolves AUTO presets into concrete encoder/network/capture parameters. */
public final class CastTuningEngine {
    private CastTuningEngine() {}

    public static final class EffectiveParams {
        public final int videoBitrate;
        public final int videoFps;
        public final int keyframeIntervalSec;
        public final boolean requestLowLatency;
        public final boolean skipTinyPFrames;

        EffectiveParams(int videoBitrate, int videoFps, int keyframeIntervalSec,
                boolean requestLowLatency, boolean skipTinyPFrames) {
            this.videoBitrate = videoBitrate;
            this.videoFps = videoFps;
            this.keyframeIntervalSec = keyframeIntervalSec;
            this.requestLowLatency = requestLowLatency;
            this.skipTinyPFrames = skipTinyPFrames;
        }
    }

    public static final class CaptureLimits {
        public final int maxWidth;
        public final int maxHeight;

        public CaptureLimits(int maxWidth, int maxHeight) {
            this.maxWidth = maxWidth;
            this.maxHeight = maxHeight;
        }
    }

    public static CaptureLimits resolveCaptureLimits(CastSettings settings, NetworkStatsSnapshot peer) {
        CastSettings.Resolution mode = settings.getResolution();
        int capLong = 0;
        int boxW = 0;
        int boxH = 0;

        switch (mode) {
            case HD_720P:
                capLong = 720;
                break;
            case SD_480P:
                capLong = 480;
                break;
            case FIXED_1080P:
                boxW = 1920;
                boxH = 1080;
                break;
            case FIXED_720P:
                boxW = 1280;
                boxH = 720;
                break;
            case ADAPTIVE:
                capLong = 720;
                if (peer != null) {
                    if (peer.recvBitrateKbps > 0 && peer.recvBitrateKbps < 800) {
                        capLong = 480;
                    } else if (peer.recvBitrateKbps > 0 && peer.recvBitrateKbps < 1500) {
                        capLong = 540;
                    }
                    if (peer.rttMs > 500) {
                        capLong = Math.min(capLong, 540);
                    }
                    if (peer.rttMs > 800) {
                        capLong = Math.min(capLong, 480);
                    }
                }
                break;
            default:
                return new CaptureLimits(settings.getMaxWidth(), settings.getMaxHeight());
        }

        if (boxW > 0 && boxH > 0) {
            return new CaptureLimits(boxW, boxH);
        }
        return new CaptureLimits(0, capLong);
    }

    public static EffectiveParams resolve(CastSettings settings, NetworkStatsSnapshot peerStats) {
        CastSettings.Quality quality = settings.getQuality();
        int bitrate = quality.videoBitrate;
        int fps = quality.videoFps;
        int gop = CastConfig.VIDEO_I_FRAME_INTERVAL;
        boolean lowLatency = true;
        boolean skipTiny = true;

        switch (settings.getAdjustmentPreset()) {
            case NONE:
                lowLatency = false;
                skipTiny = false;
                break;
            case QUALITY:
                bitrate = (int) (bitrate * 1.15f);
                fps = Math.min(30, fps + 2);
                gop = 2;
                break;
            case PERFORMANCE:
                bitrate = (int) (bitrate * 0.85f);
                fps = Math.max(24, fps);
                break;
            case SPEED:
            case AUTO:
            default:
                break;
        }

        switch (settings.getBitrateMode()) {
            case NONE:
                bitrate = Integer.MAX_VALUE / 4;
                break;
            case QUALITY:
                bitrate = (int) (bitrate * 1.25f);
                break;
            case SPEED:
                bitrate = (int) (bitrate * 0.65f);
                fps = Math.min(fps, 24);
                break;
            case ROBUST_LESS:
                if (CastConfig.TRANSPORT_UDP.equals(settings.getTransport())) {
                    bitrate = (int) (bitrate * 0.55f);
                    gop = 1;
                }
                break;
            case ESTIMATED:
            case BALANCED:
            case AUTO:
            default:
                bitrate = applyBandwidthEstimate(peerStats, bitrate);
                break;
        }

        switch (settings.getNetworkAdoption()) {
            case QUALITY:
                bitrate = (int) (bitrate * 1.1f);
                break;
            case SPEED:
                bitrate = (int) (bitrate * 0.7f);
                fps = Math.min(fps, 24);
                break;
            case ESTIMATED:
                bitrate = applyBandwidthEstimate(peerStats, bitrate);
                break;
            case AUTO:
            default:
                break;
        }

        bitrate = Math.max(600_000, Math.min(16_000_000, bitrate));
        fps = Math.max(15, Math.min(30, fps));
        return new EffectiveParams(bitrate, fps, gop, lowLatency, skipTiny);
    }

    private static int applyBandwidthEstimate(NetworkStatsSnapshot peer, int bitrate) {
        if (peer == null) {
            return bitrate;
        }
        if (peer.rttMs > 400) {
            float factor = Math.max(0.35f, 1f - (peer.rttMs - 400) / 2000f);
            bitrate = (int) (bitrate * factor);
        }
        if (peer.recvBitrateKbps > 0) {
            int target = peer.recvBitrateKbps * 900;
            if (target < bitrate) {
                bitrate = Math.max(600_000, target);
            }
        }
        if (peer.sendBitrateKbps > 0 && peer.sendBitrateKbps * 1000 < bitrate) {
            bitrate = Math.max(600_000, peer.sendBitrateKbps * 950);
        }
        return bitrate;
    }
}
