package com.foxx.androidcast.network.transport;

/*********************************************************************
 * NackProtectionEngine.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 NackProtectionEngine
 * Contributors:
 *    - Anton Afanasyeu <a.afanasieff@gmail.com> (3 commits, 105 lines)
 *    - Cursor Agent (project assistant)
 * Digest: SHA256	2143666c7b09b385a3f29fa6e162b0e1bdbc4014c53ca141e84d780ea63fa03a
**********************************************************************/
import com.foxx.androidcast.network.CastProtocol;

import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;

/** NACK requests + retransmit cache (sender stores, receiver requests). */
public final class NackProtectionEngine implements StreamProtectionEngine {
    private final TransportLossStats stats;
    private final RetransmitCache cache;
    private final NackRateLimiter rateLimiter = new NackRateLimiter();
    private NackSender nackSender;

    public interface NackSender {
        void sendNack(int messageId) throws IOException;
    }

    public NackProtectionEngine(TransportLossStats stats, RetransmitCache cache) {
        this.stats = stats;
        this.cache = cache != null ? cache : new RetransmitCache();
    }

    public void setNackSender(NackSender sender) {
        this.nackSender = sender;
    }

    public RetransmitCache getCache() {
        return cache;
    }

    @Override
    public StreamProtectionMode mode() {
        return StreamProtectionMode.NACK;
    }

    @Override
    public byte[] onOutboundPayload(byte type, byte[] payload) {
        return payload;
    }

    @Override
    public byte[] onInboundPayload(byte type, byte[] payload) {
        return payload;
    }

    @Override
    public void onNack(int messageId) {
        if (stats != null) {
            synchronized (stats) {
                stats.nackRequestsReceived++;
            }
        }
    }

    public void requestNack(int messageId) {
        if (!rateLimiter.allow(messageId)) {
            return;
        }
        if (stats != null) {
            synchronized (stats) {
                stats.nackRequestsSent++;
            }
        }
        if (nackSender != null) {
            try {
                nackSender.sendNack(messageId);
            } catch (IOException ignored) {
            }
        }
    }

    public static byte[] nackPayload(int messageId) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream(8);
        DataOutputStream dos = new DataOutputStream(bos);
        dos.writeInt(messageId);
        return bos.toByteArray();
    }

    public static int parseNackPayload(byte[] payload) {
        if (payload == null || payload.length < 4) {
            return -1;
        }
        return ((payload[0] & 0xFF) << 24) | ((payload[1] & 0xFF) << 16)
                | ((payload[2] & 0xFF) << 8) | (payload[3] & 0xFF);
    }

    private RetransmitHandler retransmitHandler;

    public void setRetransmitHandler(RetransmitHandler handler) {
        this.retransmitHandler = handler;
    }

    public interface RetransmitHandler {
        void retransmit(byte wireType, byte[] body);
    }
}
