package com.foxx.androidcast.media;

/*********************************************************************
 * H264SpsParser.java
 * Created at: Fri 15 May 2026 17:47:09 +0200
 * Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
 * Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
 * class H264SpsParser
 * Contributors:
 *    - Anton Afanasyeu <a.afanasieff@gmail.com> (2 commits, 177 lines)
 *    - Cursor Agent (project assistant)
 * Digest: SHA256	82619fa871bc520dcba789038ca5b368dc1fcfdd202f1ddd946a4f74ddc1c1dd
**********************************************************************/
import android.util.Log;

/** Extracts coded picture size from H.264 SPS (csd-0 / Annex-B). */
public final class H264SpsParser {
    private static final String TAG = "H264SpsParser";

    private H264SpsParser() {}

    public static final class Dimensions {
        public final int width;
        public final int height;

        public Dimensions(int width, int height) {
            this.width = width;
            this.height = height;
        }
    }

    /** Returns null if SPS cannot be parsed. */
    public static Dimensions parseDimensions(byte[] csd0) {
        if (csd0 == null || csd0.length < 8) {
            return null;
        }
        byte[] annexB = H264Bitstream.toAnnexB(csd0);
        int nalOffset = findNalOffset(annexB, 7); // SPS NAL type
        if (nalOffset < 0) {
            nalOffset = findNalOffset(annexB, 1); // fallback any SPS-like
        }
        if (nalOffset < 0) {
            return null;
        }
        try {
            BitReader br = new BitReader(annexB, nalOffset + 1);
            int profile = br.readBits(8);
            br.readBits(8); // constraint + reserved
            br.readBits(8); // level
            br.readUe(); // seq_parameter_set_id
            if (profile == 100 || profile == 110 || profile == 122 || profile == 244
                    || profile == 44 || profile == 83 || profile == 86 || profile == 118
                    || profile == 128 || profile == 138) {
                int chroma = br.readUe();
                if (chroma == 3) {
                    br.readUe(); // separate_colour_plane_flag offset
                }
                br.readUe(); // bit_depth_luma_minus8
                br.readUe(); // bit_depth_chroma_minus8
                br.readBits(1); // qpprime_y_zero_transform_bypass
                int seqScaling = br.readBits(1);
                if (seqScaling == 1) {
                    int count = (chroma != 3) ? 8 : 12;
                    int last = 8;
                    int next = 8;
                    for (int i = 0; i < count; i++) {
                        if (br.readBits(1) != 0) {
                            int delta = br.readSe();
                            next = (last + delta + 256) % 256;
                        }
                        last = next;
                    }
                }
            }
            br.readUe(); // log2_max_frame_num_minus4
            int picOrderCnt = br.readUe();
            if (picOrderCnt == 0) {
                br.readUe();
            } else if (picOrderCnt == 1) {
                br.readBits(1);
                br.readSe();
                br.readSe();
                int cycles = br.readUe();
                for (int i = 0; i < cycles; i++) {
                    br.readSe();
                }
            }
            br.readUe(); // max_num_ref_frames
            br.readBits(1); // gaps_in_frame_num_value_allowed_flag
            int picWidthMbs = br.readUe() + 1;
            int picHeightMbs = br.readUe() + 1;
            int frameMbsOnly = br.readBits(1);
            if (frameMbsOnly == 0) {
                br.readBits(1); // mb_adaptive_frame_field_flag
            }
            br.readBits(1); // direct_8x8_inference_flag
            int frameCrop = br.readBits(1);
            int cropLeft = 0;
            int cropRight = 0;
            int cropTop = 0;
            int cropBottom = 0;
            if (frameCrop == 1) {
                cropLeft = br.readUe();
                cropRight = br.readUe();
                cropTop = br.readUe();
                cropBottom = br.readUe();
            }
            int width = picWidthMbs * 16 - (cropLeft + cropRight) * 2;
            int height = (2 - frameMbsOnly) * picHeightMbs * 16 - (cropTop + cropBottom) * 2;
            if (width > 0 && height > 0) {
                return new Dimensions(width, height);
            }
        } catch (Exception e) {
            Log.w(TAG, "SPS parse failed", e);
        }
        return null;
    }

    private static int findNalOffset(byte[] data, int nalType) {
        for (int i = 0; i + 4 < data.length; i++) {
            if (data[i] == 0 && data[i + 1] == 0) {
                int start = -1;
                if (data[i + 2] == 1) {
                    start = i + 3;
                } else if (data[i + 2] == 0 && data[i + 3] == 1) {
                    start = i + 4;
                }
                if (start > 0 && start < data.length && (data[start] & 0x1F) == nalType) {
                    return start;
                }
            }
        }
        return -1;
    }

    private static final class BitReader {
        private final byte[] data;
        private int bitPos;

        BitReader(byte[] data, int byteOffset) {
            this.data = data;
            this.bitPos = byteOffset * 8;
        }

        int readBits(int n) {
            int val = 0;
            for (int i = 0; i < n; i++) {
                int byteIndex = bitPos / 8;
                if (byteIndex >= data.length) {
                    throw new IndexOutOfBoundsException();
                }
                int bitIndex = 7 - (bitPos % 8);
                int bit = (data[byteIndex] >> bitIndex) & 1;
                val = (val << 1) | bit;
                bitPos++;
            }
            return val;
        }

        int readUe() {
            int zeros = 0;
            while (readBits(1) == 0) {
                zeros++;
                if (zeros > 31) {
                    throw new IndexOutOfBoundsException();
                }
            }
            int val = 1;
            for (int i = 0; i < zeros; i++) {
                val = (val << 1) | readBits(1);
            }
            return val - 1;
        }

        int readSe() {
            int code = readUe();
            int sign = ((code & 1) << 1) - 1;
            return ((code + 1) >> 1) * sign;
        }
    }
}
