package com.foxx.androidcast.network.transport;

/*********************************************************************
 * StreamProtectionNegotiator.java
 * Created at: Sun 17 May 2026 22:49:49 +0200
 * Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
 * Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
 * class StreamProtectionNegotiator
 * Contributors:
 *    - Anton Afanasyeu <a.afanasieff@gmail.com> (4 commits, 249 lines)
 *    - Cursor Agent (project assistant)
 * Digest: SHA256	591201c333c776d898de736e339b1b74d3cbdba88e0230611e3aaf2f128ce08a
**********************************************************************/
import com.foxx.androidcast.CastSettings;
import com.foxx.androidcast.R;

import java.util.EnumSet;

/** Picks agreed stream protection from receiver capability vs sender preference. */
public final class StreamProtectionNegotiator {
    private StreamProtectionNegotiator() {}

    /** @deprecated Use {@link #negotiate(CastSettings.StreamProtection, CastSettings.StreamProtection, boolean)}. */
    @Deprecated
    public static CastSettings.StreamProtection resolve(CastSettings.StreamProtection local,
            CastSettings.StreamProtection remote) {
        return negotiate(local, remote, false).agreed;
    }

    /**
     * @param local sender preference when {@code localIsReceiver} is false; ignored on receiver
     *              (capability is auto-detected)
     * @param remote sender preference when {@code localIsReceiver}; receiver wire adv. when sender
     */
    public static StreamProtectionNegotiation negotiate(CastSettings.StreamProtection local,
            CastSettings.StreamProtection remote, boolean localIsReceiver) {
        EnumSet<CastSettings.StreamProtection> receiverCaps;
        CastSettings.StreamProtection senderPref;
        if (localIsReceiver) {
            receiverCaps = StreamProtectionCapability.receiverImplementations();
            senderPref = remote;
        } else {
            senderPref = local;
            receiverCaps = StreamProtectionCapability.resolveRemoteCapability(remote);
        }
        return negotiateWithReceiverCaps(receiverCaps, senderPref, local, remote, localIsReceiver);
    }

    private static StreamProtectionNegotiation negotiateWithReceiverCaps(
            EnumSet<CastSettings.StreamProtection> receiverCaps,
            CastSettings.StreamProtection senderPref,
            CastSettings.StreamProtection localRaw,
            CastSettings.StreamProtection remoteRaw,
            boolean localIsReceiver) {
        CastSettings.StreamProtection sender = normalize(senderPref);
        CastSettings.StreamProtection receiverLabel = localIsReceiver
                ? StreamProtectionCapability.handshakeAdvertisement()
                : pickRepresentativeCap(receiverCaps);

        if (sender == CastSettings.StreamProtection.NONE) {
            if (receiverCaps.stream().anyMatch(StreamProtectionNegotiator::isFec)) {
                StreamProtectionNegotiation.Prompt.Target target = localIsReceiver
                        ? StreamProtectionNegotiation.Prompt.Target.SENDER
                        : StreamProtectionNegotiation.Prompt.Target.RECEIVER;
                return new StreamProtectionNegotiation(
                        CastSettings.StreamProtection.NONE,
                        false,
                        new StreamProtectionNegotiation.Prompt(
                                target,
                                R.string.protection_negotiation_use_none,
                                receiverLabel,
                                senderPref));
            }
            return StreamProtectionNegotiation.none();
        }

        if (receiverCaps.contains(sender)) {
            return agreeExact(sender, receiverLabel, senderPref);
        }

        if (isFec34(sender) && normalize(remoteRaw) == CastSettings.StreamProtection.FEC_NACK) {
            return fec34WithoutNack(receiverLabel, senderPref, localIsReceiver);
        }

        if (isNackOnly(sender) && receiverCaps.stream().anyMatch(StreamProtectionNegotiator::isFec)) {
            CastSettings.StreamProtection fec = receiverCaps.contains(CastSettings.StreamProtection.FEC_NACK)
                    ? CastSettings.StreamProtection.FEC_NACK
                    : receiverCaps.contains(CastSettings.StreamProtection.FEC_3_4)
                    ? CastSettings.StreamProtection.FEC_3_4
                    : CastSettings.StreamProtection.FEC_REED_SOLOMON;
            return agreeFecWithNack(fec, receiverLabel, senderPref, localIsReceiver);
        }

        if (isFec(sender)) {
            if (isFec34(sender) && receiverCaps.contains(CastSettings.StreamProtection.FEC_REED_SOLOMON)) {
                return negotiateFecFamilyMismatch(
                        CastSettings.StreamProtection.FEC_REED_SOLOMON,
                        CastSettings.StreamProtection.FEC_3_4,
                        receiverLabel,
                        senderPref,
                        localIsReceiver);
            }
            if (sender == CastSettings.StreamProtection.FEC_REED_SOLOMON
                    && receiverCaps.contains(CastSettings.StreamProtection.FEC_3_4)) {
                return negotiateFecFamilyMismatch(
                        CastSettings.StreamProtection.FEC_REED_SOLOMON,
                        CastSettings.StreamProtection.FEC_3_4,
                        receiverLabel,
                        senderPref,
                        localIsReceiver);
            }
            StreamProtectionNegotiation.Prompt.Target target = localIsReceiver
                    ? StreamProtectionNegotiation.Prompt.Target.SENDER
                    : StreamProtectionNegotiation.Prompt.Target.RECEIVER;
            return new StreamProtectionNegotiation(
                    CastSettings.StreamProtection.NONE,
                    false,
                    new StreamProtectionNegotiation.Prompt(
                            target,
                            R.string.protection_negotiation_use_none,
                            receiverLabel,
                            senderPref));
        }

        StreamProtectionNegotiation.Prompt.Target target = localIsReceiver
                ? StreamProtectionNegotiation.Prompt.Target.SENDER
                : StreamProtectionNegotiation.Prompt.Target.RECEIVER;
        return new StreamProtectionNegotiation(
                CastSettings.StreamProtection.NONE,
                false,
                new StreamProtectionNegotiation.Prompt(
                        target,
                        R.string.protection_negotiation_mismatch,
                        receiverLabel,
                        senderPref));
    }

    private static CastSettings.StreamProtection pickRepresentativeCap(
            EnumSet<CastSettings.StreamProtection> caps) {
        if (caps.contains(CastSettings.StreamProtection.FEC_REED_SOLOMON)) {
            return CastSettings.StreamProtection.FEC_REED_SOLOMON;
        }
        if (caps.contains(CastSettings.StreamProtection.FEC_3_4)) {
            return CastSettings.StreamProtection.FEC_3_4;
        }
        if (caps.contains(CastSettings.StreamProtection.FEC_NACK)) {
            return CastSettings.StreamProtection.FEC_NACK;
        }
        if (caps.contains(CastSettings.StreamProtection.NACK)) {
            return CastSettings.StreamProtection.NACK;
        }
        return CastSettings.StreamProtection.NONE;
    }

    private static StreamProtectionNegotiation negotiateFecFamilyMismatch(
            CastSettings.StreamProtection agreed,
            CastSettings.StreamProtection other,
            CastSettings.StreamProtection receiverLabel,
            CastSettings.StreamProtection senderPref,
            boolean localIsReceiver) {
        StreamProtectionNegotiation.Prompt.Target target = localIsReceiver
                ? StreamProtectionNegotiation.Prompt.Target.SENDER
                : StreamProtectionNegotiation.Prompt.Target.RECEIVER;
        boolean nack = wantsNack(other);
        if (isFecNack(other)) {
            nack = false;
        }
        return new StreamProtectionNegotiation(
                agreed,
                nack,
                new StreamProtectionNegotiation.Prompt(
                        target,
                        R.string.protection_negotiation_fec_family_rs,
                        receiverLabel,
                        senderPref));
    }

    private static StreamProtectionNegotiation fec34WithoutNack(
            CastSettings.StreamProtection receiverLabel,
            CastSettings.StreamProtection senderPref,
            boolean localIsReceiver) {
        StreamProtectionNegotiation.Prompt.Target target = localIsReceiver
                ? StreamProtectionNegotiation.Prompt.Target.SENDER
                : StreamProtectionNegotiation.Prompt.Target.RECEIVER;
        return new StreamProtectionNegotiation(
                CastSettings.StreamProtection.FEC_3_4,
                false,
                new StreamProtectionNegotiation.Prompt(
                        target,
                        R.string.protection_negotiation_fec34_no_nack,
                        receiverLabel,
                        senderPref));
    }

    private static StreamProtectionNegotiation agreeFecWithNack(
            CastSettings.StreamProtection fecMode,
            CastSettings.StreamProtection receiverLabel,
            CastSettings.StreamProtection senderPref,
            boolean localIsReceiver) {
        StreamProtectionNegotiation.Prompt.Target target = localIsReceiver
                ? StreamProtectionNegotiation.Prompt.Target.SENDER
                : StreamProtectionNegotiation.Prompt.Target.RECEIVER;
        return new StreamProtectionNegotiation(
                fecMode,
                true,
                new StreamProtectionNegotiation.Prompt(
                        target,
                        R.string.protection_negotiation_nack_to_fec,
                        receiverLabel,
                        senderPref));
    }

    private static StreamProtectionNegotiation agreeExact(
            CastSettings.StreamProtection mode,
            CastSettings.StreamProtection receiverLabel,
            CastSettings.StreamProtection senderPref) {
        boolean nack = wantsNack(mode);
        if (mode == CastSettings.StreamProtection.FEC_3_4) {
            nack = true;
        }
        return new StreamProtectionNegotiation(mode, nack, null);
    }

    public static CastSettings.StreamProtection normalize(CastSettings.StreamProtection mode) {
        if (mode == null || mode == CastSettings.StreamProtection.AUTO) {
            return CastSettings.StreamProtection.NONE;
        }
        return mode;
    }

    private static boolean wantsNack(CastSettings.StreamProtection mode) {
        return mode == CastSettings.StreamProtection.NACK
                || mode == CastSettings.StreamProtection.FEC_NACK;
    }

    private static boolean isNackOnly(CastSettings.StreamProtection mode) {
        return mode == CastSettings.StreamProtection.NACK;
    }

    private static boolean isFec(CastSettings.StreamProtection mode) {
        return mode == CastSettings.StreamProtection.FEC_3_4
                || mode == CastSettings.StreamProtection.FEC_REED_SOLOMON
                || mode == CastSettings.StreamProtection.FEC_NACK;
    }

    private static boolean isFec34(CastSettings.StreamProtection mode) {
        return mode == CastSettings.StreamProtection.FEC_3_4;
    }

    private static boolean isFecNack(CastSettings.StreamProtection mode) {
        return mode == CastSettings.StreamProtection.FEC_NACK;
    }
}
