package com.usky.cdi.service.sip.ps;

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

/**
 * 海康风格 PES 封装（参考海康 PS 码流分析）。
 * <p>
 * SPS：{@code 8C 80 07 + PTS(5) + FF FC + Annex-B}<br>
 * PPS：{@code 8C 00 03 + FF FF FC + Annex-B}<br>
 * I 帧首片：{@code 8C 00 02 + FF FD + Annex-B 起始}<br>
 * I 帧续片：{@code 88 00 02 + FF FF + 裸 ES 续数据}<br>
 * P 帧：{@code 8C 80 07 + PTS(5) + FF FD + Annex-B}
 */
public final class HikvisionPesBuilder {

    /** 海康 PES packlen 典型值 0x13FA */
    private static final int PES_PACK_LEN = 5114;
    /** 首片/续片 PES 头（flags + stufflen + stuff）固定 5 字节 */
    private static final int CHUNK_PES_HDR = 5;
    /** 8C 80 类 PES 头固定 10 字节（flags + stufflen + 7 字节 stuff） */
    private static final int STD_PES_HDR = 10;

    private HikvisionPesBuilder() {
    }

    public static void writeSpsPes(ByteArrayOutputStream out, byte[] annexBNal, long pts90k) throws IOException {
        out.write(buildStdPes(annexBNal, pts90k, new byte[]{(byte) 0xFF, (byte) 0xFC}));
    }

    public static void writePpsPes(ByteArrayOutputStream out, byte[] annexBNal) throws IOException {
        out.write(buildPpsPes(annexBNal));
    }

    public static void writePFramePesChunks(ByteArrayOutputStream out, byte[] annexB, long pts90k)
            throws IOException {
        writeStdPesChunks(out, annexB, pts90k, new byte[]{(byte) 0xFF, (byte) 0xFD});
    }

    public static void writeIdrPesChunks(ByteArrayOutputStream out, byte[] annexB, long pts90k)
            throws IOException {
        if (annexB.length <= maxFirstChunkEs()) {
            out.write(buildFirstIdrPes(annexB, 0, annexB.length, pts90k));
            return;
        }
        int offset = 0;
        boolean first = true;
        while (offset < annexB.length) {
            int chunk = Math.min(first ? maxFirstChunkEs() : maxContinueEs(), annexB.length - offset);
            if (first) {
                out.write(buildFirstIdrPes(annexB, offset, chunk, pts90k));
            } else {
                out.write(buildContinueIdrPes(annexB, offset, chunk));
            }
            offset += chunk;
            first = false;
        }
    }

    private static void writeStdPesChunks(ByteArrayOutputStream out, byte[] annexB, long pts90k,
                                          byte[] suffix) throws IOException {
        int maxEs = PES_PACK_LEN - STD_PES_HDR;
        int offset = 0;
        boolean first = true;
        while (offset < annexB.length) {
            int chunk = Math.min(first ? maxEs : maxContinueEs(), annexB.length - offset);
            if (first) {
                byte[] payload = slice(annexB, offset, chunk);
                out.write(buildStdPes(payload, pts90k, suffix));
            } else {
                out.write(buildContinueIdrPes(annexB, offset, chunk));
            }
            offset += chunk;
            first = false;
        }
    }

    private static int maxFirstChunkEs() {
        return PES_PACK_LEN - CHUNK_PES_HDR;
    }

    private static int maxContinueEs() {
        return PES_PACK_LEN - CHUNK_PES_HDR;
    }

    private static byte[] buildStdPes(byte[] payload, long pts90k, byte[] suffix) {
        byte[] pts5 = buildPts5(pts90k);
        byte[] stuff = new byte[pts5.length + suffix.length];
        System.arraycopy(pts5, 0, stuff, 0, pts5.length);
        System.arraycopy(suffix, 0, stuff, pts5.length, suffix.length);
        return buildPes((byte) 0x8C, (byte) 0x80, stuff, payload);
    }

    private static byte[] buildPpsPes(byte[] payload) {
        return buildPes((byte) 0x8C, (byte) 0x00, new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFC}, payload);
    }

    private static byte[] buildFirstIdrPes(byte[] payload, int offset, int length, long pts90k) {
        byte[] stuff = buildIdrFirstStuff(pts90k);
        byte[] slice = slice(payload, offset, length);
        return buildPes((byte) 0x8C, (byte) 0x00, stuff, slice);
    }

    private static byte[] buildContinueIdrPes(byte[] payload, int offset, int length) {
        int bodyLen = CHUNK_PES_HDR + length;
        byte[] pes = new byte[6 + bodyLen];
        pes[0] = 0x00;
        pes[1] = 0x00;
        pes[2] = 0x01;
        pes[3] = (byte) 0xE0;
        pes[4] = (byte) ((bodyLen >> 8) & 0xFF);
        pes[5] = (byte) (bodyLen & 0xFF);
        pes[6] = (byte) 0x88;
        pes[7] = (byte) 0x00;
        pes[8] = 0x02;
        pes[9] = (byte) 0xFF;
        pes[10] = (byte) 0xFF;
        System.arraycopy(payload, offset, pes, 11, length);
        return pes;
    }

    private static byte[] buildPes(byte flag1, byte flag2, byte[] stuff, byte[] payload) {
        int bodyLen = 2 + 1 + stuff.length + payload.length;
        byte[] pes = new byte[6 + bodyLen];
        pes[0] = 0x00;
        pes[1] = 0x00;
        pes[2] = 0x01;
        pes[3] = (byte) 0xE0;
        pes[4] = (byte) ((bodyLen >> 8) & 0xFF);
        pes[5] = (byte) (bodyLen & 0xFF);
        pes[6] = flag1;
        pes[7] = flag2;
        pes[8] = (byte) stuff.length;
        System.arraycopy(stuff, 0, pes, 9, stuff.length);
        System.arraycopy(payload, 0, pes, 9 + stuff.length, payload.length);
        return pes;
    }

    private static byte[] buildIdrFirstStuff(long pts90k) {
        if (pts90k == 0) {
            return new byte[]{(byte) 0xFF, (byte) 0xFD};
        }
        long pts = pts90k / 100;
        return new byte[]{(byte) ((pts >> 8) & 0xFF), (byte) (pts & 0xFF)};
    }

    private static byte[] buildPts5(long pts90k) {
        long pts = pts90k / 100;
        byte[] buf = new byte[5];
        buf[0] = (byte) (0x21 | ((pts >> 29) & 0x0E));
        buf[1] = (byte) (pts >> 22);
        buf[2] = (byte) (0x01 | ((pts >> 14) & 0xFE));
        buf[3] = (byte) (pts >> 7);
        buf[4] = (byte) (0x01 | ((pts & 0x7F) << 1));
        return buf;
    }

    private static byte[] slice(byte[] data, int offset, int length) {
        byte[] out = new byte[length];
        System.arraycopy(data, offset, out, 0, length);
        return out;
    }
}
