package com.foxx.androidcast.media;

/*********************************************************************
 * CodecNegotiator.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 CodecNegotiator
 * Contributors:
 *    - Anton Afanasyeu <a.afanasieff@gmail.com> (7 commits, 125 lines)
 *    - Cursor Agent (project assistant)
 * Digest: SHA256	0fb27c49fb9d42566c0f347b0bb0d5a3a7a118ba38cee8486e48a5c4262260e9
**********************************************************************/
import android.media.MediaFormat;
import android.util.Log;

import com.foxx.androidcast.CastSettings;

import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;

/** Picks a video MIME type both sides support, honoring user preference when possible. */
public final class CodecNegotiator {
    private static final String TAG = "CodecNegotiator";

    private CodecNegotiator() {}

    public static String negotiate(List<String> senderEncoders, List<String> receiverDecoders,
            CastSettings.VideoCodec preference) {
        return negotiate(senderEncoders, receiverDecoders, preference, null, "negotiate");
    }

    public static String negotiate(List<String> senderEncoders, List<String> receiverDecoders,
            CastSettings.VideoCodec preference, CastSettings negotiationSettings) {
        return negotiate(senderEncoders, receiverDecoders, preference, negotiationSettings, "receiver");
    }

    public static String negotiate(List<String> senderEncoders, List<String> receiverDecoders,
            CastSettings.VideoCodec preference, CastSettings negotiationSettings, String logRole) {
        Set<String> enc = new HashSet<>(senderEncoders);
        Set<String> dec = new HashSet<>(receiverDecoders);
        CastSettings scoreSettings = negotiationSettings != null ? negotiationSettings : new CastSettings();

        if (preference == CastSettings.VideoCodec.H264) {
            String mime = pick(MediaFormat.MIMETYPE_VIDEO_AVC, enc, dec);
            CodecPriorityCatalog.logExplicitNegotiation(logRole, mime,
                    CastSettings.VideoCodec.H264, scoreSettings);
            return mime;
        }
        if (preference == CastSettings.VideoCodec.H265) {
            String mime = pick(MediaFormat.MIMETYPE_VIDEO_HEVC, enc, dec);
            CodecPriorityCatalog.logExplicitNegotiation(logRole, mime,
                    CastSettings.VideoCodec.H265, scoreSettings);
            return mime;
        }
        if (preference == CastSettings.VideoCodec.LIBVPX_VP8) {
            String mime = pick(MediaFormat.MIMETYPE_VIDEO_VP8, enc, dec);
            CodecPriorityCatalog.logExplicitNegotiation(logRole, mime,
                    CastSettings.VideoCodec.LIBVPX_VP8, scoreSettings);
            return mime;
        }
        if (preference == CastSettings.VideoCodec.LIBVPX_VP9) {
            String mime = pick(MediaFormat.MIMETYPE_VIDEO_VP9, enc, dec);
            CodecPriorityCatalog.logExplicitNegotiation(logRole, mime,
                    CastSettings.VideoCodec.LIBVPX_VP9, scoreSettings);
            return mime;
        }

        CodecPriorityCatalog.NegotiationDecision decision = CodecPriorityCatalog.decideBestVideoMime(
                senderEncoders, receiverDecoders, scoreSettings);
        if (decision != null) {
            CodecPriorityCatalog.logNegotiationDecision(logRole, decision);
            return decision.mime;
        }
        if (dec.contains(MediaFormat.MIMETYPE_VIDEO_AVC) && enc.contains(MediaFormat.MIMETYPE_VIDEO_AVC)) {
            String mime = MediaFormat.MIMETYPE_VIDEO_AVC;
            Log.w(TAG, logRole + " codec fallback H.264 (no scored candidate)");
            return mime;
        }
        throw new IllegalStateException("No common video codec (sender=" + enc + " receiver=" + dec + ")");
    }

    private static String pick(String mime, Set<String> enc, Set<String> dec) {
        if (enc.contains(mime) && dec.contains(mime)) {
            return mime;
        }
        throw new IllegalStateException("Preferred codec not available on both sides: " + mime);
    }

    public static String displayName(String mime) {
        if (mime == null) {
            return "?";
        }
        if (MediaFormat.MIMETYPE_VIDEO_AVC.equals(mime)) {
            return "H.264";
        }
        if (MediaFormat.MIMETYPE_VIDEO_HEVC.equals(mime)) {
            return "H.265";
        }
        if (MediaFormat.MIMETYPE_VIDEO_VP9.equals(mime)) {
            return "VP9";
        }
        if (MediaFormat.MIMETYPE_VIDEO_VP8.equals(mime)) {
            return "VP8";
        }
        int slash = mime.lastIndexOf('/');
        return slash >= 0 ? mime.substring(slash + 1).toUpperCase(Locale.US) : mime;
    }

    public static boolean isH264(String mime) {
        return MediaFormat.MIMETYPE_VIDEO_AVC.equals(mime);
    }

    public static boolean isHevc(String mime) {
        return MediaFormat.MIMETYPE_VIDEO_HEVC.equals(mime);
    }

    public static boolean isVpx(String mime) {
        return MediaFormat.MIMETYPE_VIDEO_VP8.equals(mime)
                || MediaFormat.MIMETYPE_VIDEO_VP9.equals(mime);
    }

    /** Score for diagnostics (lower = preferred). */
    public static int priorityScoreForNegotiatedMime(String mime, CastSettings settings) {
        return CodecPriorityCatalog.combinedVideoMimeScore(mime, settings);
    }
}
