package com.foxx.androidcast;

/*********************************************************************
 * CastSettings.java
 * Created at: Fri 15 May 2026 16:49:02 +0200
 * Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
 * Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
 * class CastSettings
 * Contributors:
 *    - Anton Afanasyeu <a.afanasieff@gmail.com> (9 commits, 293 lines)
 *    - Cursor Agent (project assistant)
 * Digest: SHA256	f505c9e26b14289d81453dde3bfc161af9a37ec38d04f158f1bea928410ce3ff
**********************************************************************/
import java.io.Serializable;

/** User-selected cast parameters (passed sender → service → receiver via protocol). */
public class CastSettings implements Serializable {
    public static final String EXTRA = "cast_settings";
    public static final int SETTINGS_VERSION = 4;

    public enum Quality {
        LOW(1_500_000, 24),
        MEDIUM(5_000_000, 30),
        HIGH(10_000_000, 30);

        public final int videoBitrate;
        public final int videoFps;

        Quality(int videoBitrate, int videoFps) {
            this.videoBitrate = videoBitrate;
            this.videoFps = videoFps;
        }
    }

    public enum VideoCodec {
        AUTO, H264, H265, PASSTHROUGH,
        /** Future software VP8 (UI disabled until integrated). */
        LIBVPX_VP8,
        /** Future software VP9 (UI disabled until integrated). */
        LIBVPX_VP9
    }

    public enum AudioCodec {
        AUTO, AAC,
        /** Speex (native when built; otherwise stub AAC + Speex backend label). */
        SPEEX,
        /** Opus with in-band FEC when native; otherwise stub AAC + Opus backend label. */
        OPUS,
        /** Explicit AAC passthrough wrapper for debug comparisons. */
        PASSTHROUGH_DEBUG
    }

    public enum AdjustmentPreset {
        AUTO, NONE, QUALITY, SPEED, PERFORMANCE
    }

    public enum BitrateMode {
        AUTO, NONE, QUALITY, SPEED, BALANCED, ROBUST_LESS, ESTIMATED
    }

    public enum NetworkAdoption {
        AUTO, QUALITY, SPEED, ESTIMATED
    }

    /**
     * Capture size. {@link #HD_720P} caps height only (width follows aspect — e.g. 1532×720).
     * {@link #FIXED_720P} fits inside 1280×720 box.
     */
    /** How the system screen-capture consent dialog behaves (sender only). */
    public enum CaptureMode {
        /** Whole display (default). */
        FULL_SCREEN,
        /** Android 14+: system picker can target a single app (better app audio capture). */
        USER_CHOICE,
        /** Generated TV calibration pattern (no MediaProjection). */
        CALIBRATION_TEST,
        /** Camera2 rear camera as video source. */
        CAMERA,
        /** Camera2 front camera as video source. */
        CAMERA_FRONT
    }

    /** UDP stream protection hint (negotiated with peer; stubs until implemented). */
    public enum StreamProtection {
        AUTO,
        FEC_3_4,
        FEC_REED_SOLOMON,
        NACK,
        FEC_NACK,
        /** No FEC/NACK (default). */
        NONE
    }

    /** How the receiver fits incoming video on its display. */
    public enum ReceiverDisplayResolution {
        AUTO_FIT,
        ADAPTIVE_EXPERIMENTAL
    }

    public enum Resolution {
        FULL(1.0f, 0, 0),
        THREE_QUARTERS(0.75f, 0, 0),
        HALF(0.5f, 0, 0),
        HD_720P(1.0f, 720, 0),
        FIXED_1080P(0f, 1080, 1920),
        FIXED_720P(0f, 720, 1280),
        ADAPTIVE(1.0f, 0, 0),
        /** Long-edge cap 480p (aspect preserved). */
        SD_480P(1.0f, 480, 0);

        public final float scale;
        public final int maxHeight;
        public final int maxWidth;

        Resolution(float scale, int maxHeight, int maxWidth) {
            this.scale = scale;
            this.maxHeight = maxHeight;
            this.maxWidth = maxWidth;
        }
    }

    /**
     * Maps legacy stored resolutions to the alpha sender presets
     * ({@link #FULL} / {@link #HD_720P} / {@link #SD_480P} / {@link #ADAPTIVE}).
     */
    public static Resolution forPresetUi(Resolution stored) {
        if (stored == null) {
            return Resolution.ADAPTIVE;
        }
        switch (stored) {
            case FIXED_720P:
                return Resolution.HD_720P;
            case FIXED_1080P:
                return Resolution.FULL;
            case THREE_QUARTERS:
            case HALF:
                return Resolution.ADAPTIVE;
            default:
                return stored;
        }
    }

    private String transport = CastConfig.DEFAULT_TRANSPORT;
    private Quality quality = Quality.MEDIUM;
    private Resolution resolution = Resolution.ADAPTIVE;
    private boolean audioEnabled;
    private VideoCodec videoCodec = VideoCodec.AUTO;
    private AudioCodec audioCodec = AudioCodec.AUTO;
    private AdjustmentPreset adjustmentPreset = AdjustmentPreset.AUTO;
    private BitrateMode bitrateMode = BitrateMode.AUTO;
    private NetworkAdoption networkAdoption = NetworkAdoption.AUTO;
    private CaptureMode captureMode = PlatformCastSupport.defaultCaptureMode();
    private StreamProtection streamProtection = StreamProtection.NONE;
    private ReceiverDisplayResolution receiverDisplayResolution = ReceiverDisplayResolution.AUTO_FIT;
    /** Filled after codec handshake (e.g. video/avc). */
    private transient String negotiatedVideoMime;
    /** Filled when AUTO (or fallback) resolves against peer audio caps. */
    private transient AudioCodec resolvedAudioCodec;

    public CastSettings() {
        PlatformCastSupport.applyDefaultAudioSetting(this);
    }

    public String getTransport() {
        return transport;
    }

    public void setTransport(String transport) {
        this.transport = transport;
    }

    public Quality getQuality() {
        return quality;
    }

    public void setQuality(Quality quality) {
        this.quality = quality;
    }

    public Resolution getResolution() {
        return resolution;
    }

    public void setResolution(Resolution resolution) {
        this.resolution = resolution;
    }

    public boolean isAudioEnabled() {
        return audioEnabled && PlatformCastSupport.isAudioCaptureSupported();
    }

    public void setAudioEnabled(boolean audioEnabled) {
        this.audioEnabled = audioEnabled;
    }

    public boolean isAudioRequested() {
        return audioEnabled;
    }

    public VideoCodec getVideoCodec() {
        return videoCodec;
    }

    public void setVideoCodec(VideoCodec videoCodec) {
        this.videoCodec = videoCodec;
    }

    public AudioCodec getAudioCodec() {
        return audioCodec;
    }

    public void setAudioCodec(AudioCodec audioCodec) {
        this.audioCodec = audioCodec != null ? audioCodec : AudioCodec.AUTO;
    }

    public AdjustmentPreset getAdjustmentPreset() {
        return adjustmentPreset;
    }

    public void setAdjustmentPreset(AdjustmentPreset adjustmentPreset) {
        this.adjustmentPreset = adjustmentPreset;
    }

    public BitrateMode getBitrateMode() {
        return bitrateMode;
    }

    public void setBitrateMode(BitrateMode bitrateMode) {
        this.bitrateMode = bitrateMode;
    }

    public NetworkAdoption getNetworkAdoption() {
        return networkAdoption;
    }

    public void setNetworkAdoption(NetworkAdoption networkAdoption) {
        this.networkAdoption = networkAdoption;
    }

    /** Legacy: raw quality bitrate. Prefer {@link CastTuningEngine#resolve}. */
    public int getVideoBitrate() {
        return quality.videoBitrate;
    }

    public int getVideoFps() {
        return quality.videoFps;
    }

    public float getResolutionScale() {
        return resolution.scale;
    }

    public int getMaxHeight() {
        return resolution.maxHeight;
    }

    public int getMaxWidth() {
        return resolution.maxWidth;
    }

    public boolean isPassthrough() {
        return videoCodec == VideoCodec.PASSTHROUGH;
    }

    public String getNegotiatedVideoMime() {
        return negotiatedVideoMime;
    }

    public void setNegotiatedVideoMime(String negotiatedVideoMime) {
        this.negotiatedVideoMime = negotiatedVideoMime;
    }

    public AudioCodec getResolvedAudioCodec() {
        return resolvedAudioCodec;
    }

    public void setResolvedAudioCodec(AudioCodec resolvedAudioCodec) {
        this.resolvedAudioCodec = resolvedAudioCodec;
    }

    public CaptureMode getCaptureMode() {
        return captureMode;
    }

    public void setCaptureMode(CaptureMode captureMode) {
        this.captureMode = captureMode != null ? captureMode : CaptureMode.FULL_SCREEN;
    }

    public StreamProtection getStreamProtection() {
        return streamProtection;
    }

    public void setStreamProtection(StreamProtection streamProtection) {
        this.streamProtection = streamProtection != null ? streamProtection : StreamProtection.NONE;
    }

    public ReceiverDisplayResolution getReceiverDisplayResolution() {
        return receiverDisplayResolution;
    }

    public void setReceiverDisplayResolution(ReceiverDisplayResolution receiverDisplayResolution) {
        this.receiverDisplayResolution = receiverDisplayResolution != null
                ? receiverDisplayResolution : ReceiverDisplayResolution.AUTO_FIT;
    }

    public boolean isCameraCapture() {
        return captureMode == CaptureMode.CAMERA || captureMode == CaptureMode.CAMERA_FRONT;
    }

    public boolean isFrontCamera() {
        return captureMode == CaptureMode.CAMERA_FRONT;
    }

    public void copyFrom(CastSettings other) {
        if (other == null) {
            return;
        }
        transport = other.transport;
        quality = other.quality;
        resolution = other.resolution;
        audioEnabled = other.audioEnabled;
        videoCodec = other.videoCodec;
        audioCodec = other.audioCodec;
        adjustmentPreset = other.adjustmentPreset;
        bitrateMode = other.bitrateMode;
        networkAdoption = other.networkAdoption;
        captureMode = other.captureMode;
        streamProtection = other.streamProtection;
        receiverDisplayResolution = other.receiverDisplayResolution;
        negotiatedVideoMime = other.negotiatedVideoMime;
        resolvedAudioCodec = other.resolvedAudioCodec;
    }
}
