package com.foxx.androidcast.network.diag;

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

/**
 * Diagnostic cast ping-pong wire format (optional; {@link com.foxx.androidcast.network.CastProtocol#MSG_DIAG_PING}).
 * <p>
 * v1 layout (big-endian):
 * magic(4) version(1) flags(1) direction(1) reserved(1)
 * timestamp_ms(8) seq(8) payload_size(4) [payload]
 * When {@link #FLAG_PONG} and direction {@link #DIR_REPLY_WITH}: reply_for_seq(8) reply_for_timestamp_ms(8)
 */
public final class CastDiagPing {
    public static final int MAGIC = 0x50494E47; // "PING"
    public static final byte VERSION = 1;

    /** Set on pong packets. */
    public static final byte FLAG_PONG = 0x01;

    /** Echo probe fields unchanged (dummy pong). */
    public static final byte DIR_MIRROR_AS_IS = 0;
    /** Pong uses local wall-clock timestamp; same seq as probe. */
    public static final byte DIR_MIRROR_SMART = 1;
    /** Pong includes reply-for fields plus local timestamp and seq. */
    public static final byte DIR_REPLY_WITH = 2;

    private CastDiagPing() {}

    public static final class Packet {
        public int magic;
        public byte version;
        public byte flags;
        public byte direction;
        public long timestampMs;
        public long seq;
        public byte[] payload = new byte[0];
        /** Valid when {@link #FLAG_PONG} and {@link #DIR_REPLY_WITH}. */
        public long replyForSeq;
        public long replyForTimestampMs;

        public boolean isPong() {
            return (flags & FLAG_PONG) != 0;
        }

        public boolean isValidMagic() {
            return magic == MAGIC && version == VERSION;
        }
    }

    public static byte[] encode(Packet p) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream(32);
        DataOutputStream out = new DataOutputStream(bos);
        out.writeInt(p.magic != 0 ? p.magic : MAGIC);
        out.writeByte(VERSION);
        out.writeByte(p.flags);
        out.writeByte(p.direction);
        out.writeByte(0);
        out.writeLong(p.timestampMs);
        out.writeLong(p.seq);
        byte[] body = p.payload != null ? p.payload : new byte[0];
        out.writeInt(body.length);
        if (body.length > 0) {
            out.write(body);
        }
        if (p.isPong() && p.direction == DIR_REPLY_WITH) {
            out.writeLong(p.replyForSeq);
            out.writeLong(p.replyForTimestampMs);
        }
        out.flush();
        return bos.toByteArray();
    }

    public static Packet decode(byte[] raw) throws IOException {
        if (raw == null || raw.length < 28) {
            throw new IOException("Diag ping too short");
        }
        DataInputStream in = new DataInputStream(new ByteArrayInputStream(raw));
        Packet p = new Packet();
        p.magic = in.readInt();
        p.version = in.readByte();
        p.flags = in.readByte();
        p.direction = in.readByte();
        in.readByte(); // reserved
        p.timestampMs = in.readLong();
        p.seq = in.readLong();
        int payloadSize = in.readInt();
        if (payloadSize < 0 || payloadSize > 64 * 1024) {
            throw new IOException("Invalid diag ping payload size: " + payloadSize);
        }
        if (payloadSize > 0) {
            p.payload = new byte[payloadSize];
            in.readFully(p.payload);
        }
        if (p.isPong() && p.direction == DIR_REPLY_WITH && in.available() >= 16) {
            p.replyForSeq = in.readLong();
            p.replyForTimestampMs = in.readLong();
        }
        return p;
    }
}
