/* nal_units.c
 *
 * Copyright (c) 2003-2026 HandBrake Team
 * Copyright (c) FFmpeg
 * This file is part of the HandBrake source code.
 * Homepage: <http://handbrake.fr/>.
 * It may be used under the terms of the GNU General Public License v2.
 * For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
 */

#include <stdint.h>
#include <string.h>

#include "handbrake/common.h"
#include "handbrake/nal_units.h"

static const uint8_t hb_annexb_startcode[] = { 0x00, 0x00, 0x00, 0x01, };

size_t hb_nal_unit_write_annexb(uint8_t *buf,
                                const uint8_t *nal_unit,
                                const size_t nal_unit_size)
{
    if (buf != NULL)
    {
        memcpy(buf, hb_annexb_startcode, sizeof(hb_annexb_startcode));
        memcpy(buf + sizeof(hb_annexb_startcode), nal_unit, nal_unit_size);
    }

    return sizeof(hb_annexb_startcode) + nal_unit_size;
}

size_t hb_nal_unit_write_isomp4(uint8_t *buf,
                                const uint8_t *nal_unit,
                                const size_t nal_unit_size)
{
    int i;
    uint8_t length[4]; // 4-byte length replaces Annex B start code prefix

    if (buf != NULL)
    {
        for (i = 0; i < sizeof(length); i++)
        {
            length[i] = (nal_unit_size >> (8 * (sizeof(length) - 1 - i))) & 0xff;
        }

        memcpy(buf, &length[0], sizeof(length));
        memcpy(buf + sizeof(length), nal_unit, nal_unit_size);
    }

    return sizeof(length) + nal_unit_size;
}

size_t hb_nal_unit_payload_write_isomp4(uint8_t *buf,
                                        const uint8_t *payload,
                                        size_t payload_size,
                                        const int nal_type,
                                        const uint8_t nal_length_size)
{
    int i;
    uint8_t length[4]; // 4-byte length replaces Annex B start code prefix
    uint8_t header[2]; // 2-byte NAL header

    size_t nal_unit_size = payload_size + 2;

    if (buf != NULL)
    {
        for (i = 0; i < nal_length_size; i++)
        {
            length[i] = (nal_unit_size >> (8 * (nal_length_size - 1 - i))) & 0xff;
        }

        header[0] = nal_type << 1;
        header[1] = 1;

        memcpy(buf, &length[0], nal_length_size);
        buf += nal_length_size;
        memcpy(buf, &header[0], sizeof(header));
        buf += sizeof(header);
        memcpy(buf, payload, payload_size);
    }

    return nal_length_size + nal_unit_size;
}

uint8_t* hb_annexb_find_next_nalu(const uint8_t *start, size_t *size)
{
    uint8_t *nal = NULL;
    uint8_t *buf = (uint8_t*)start;
    uint8_t *end = (uint8_t*)start + *size;

    /* Look for an Annex B start code prefix (3-byte sequence == 1) */
    while (end - buf > 3)
    {
        if (!buf[0] && !buf[1] && buf[2] == 1)
        {
            nal = (buf += 3); // NAL unit begins after start code
            break;
        }
        buf++;
    }

    if (nal == NULL)
    {
        *size = 0;
        return NULL;
    }

    /*
     * Start code prefix found, look for the next one to determine the size
     *
     * A 4-byte sequence == 1 is also a start code, so check for a 3-byte
     * sequence == 0 too (start code emulation prevention will prevent such a
     * sequence from occurring outside of a start code prefix)
     */
    while (end - buf > 3)
    {
        if (!buf[0] && !buf[1] && (!buf[2] || buf[2] == 1))
        {
            end = buf;
            break;
        }
        buf++;
    }

    *size = end - nal;
    return  nal;
}

uint8_t* hb_isomp4_find_next_nalu(const uint8_t *start, size_t *size, const uint8_t nal_length_size)
{
    uint8_t *nal = NULL;
    uint8_t *buf = (uint8_t*)start;
    uint8_t *end = (uint8_t*)start + *size;

    if (nal_length_size > 4)
    {
        return NULL;
    }

    while (end - buf > nal_length_size)
    {
        uint8_t length[4]; // 4-byte length replaces Annex B start code prefix
        size_t nal_unit_size = 0;

        memcpy(length, buf, nal_length_size);

        for (int i = 0; i < nal_length_size; i++)
        {
            nal_unit_size <<= 8;
            nal_unit_size |= length[i];
        }

        nal = buf;
        *size = nal_unit_size + nal_length_size;

        return nal;
    }

    return NULL;
}

hb_buffer_t* hb_nal_bitstream_annexb_to_mp4(const uint8_t *data,
                                            const size_t size)
{
    hb_buffer_t *out;
    uint8_t *buf, *end;
    size_t out_size, buf_size;

    out_size = 0;
    buf_size = size;
    buf      = (uint8_t*)data;
    end      = (uint8_t*)data + size;

    while ((buf = hb_annexb_find_next_nalu(buf, &buf_size)) != NULL)
    {
        out_size += hb_nal_unit_write_isomp4(NULL, buf, buf_size);
        buf_size  = end - buf;
    }

    out = hb_buffer_init(out_size);
    if (out == NULL)
    {
        hb_error("hb_nal_bitstream_annexb_to_mp4: hb_buffer_init failed");
        return NULL;
    }

    out_size = 0;
    buf_size = size;
    buf      = (uint8_t*)data;
    end      = (uint8_t*)data + size;

    while ((buf = hb_annexb_find_next_nalu(buf, &buf_size)) != NULL)
    {
        out_size += hb_nal_unit_write_isomp4(out->data + out_size, buf, buf_size);
        buf_size  = end - buf;
    }

    return out;
}

static size_t mp4_nal_unit_length(const uint8_t *data,
                                  const uint8_t nal_length_size,
                                  size_t *nal_unit_length)
{
    uint8_t i;

    /* In MP4, NAL units are preceded by a 2-4 byte length field */
    for (i = 0, *nal_unit_length = 0; i < nal_length_size; i++)
    {
        *nal_unit_length |= data[i] << (8 * (nal_length_size - 1 - i));
    }

    return nal_length_size;
}

hb_buffer_t* hb_nal_bitstream_mp4_to_annexb(const uint8_t *data,
                                            const size_t size,
                                            const uint8_t nal_length_size)
{
    hb_buffer_t *out;
    uint8_t *buf, *end;
    size_t out_size, nal_size;

    out_size = 0;
    buf      = (uint8_t*)data;
    end      = (uint8_t*)data + size;

    while (end - buf > nal_length_size)
    {
        buf += mp4_nal_unit_length(buf, nal_length_size, &nal_size);
        if (end - buf < nal_size)
        {
            hb_log("hb_nal_bitstream_mp4_to_annexb: truncated bitstream"
                   " (remaining: %lu, expected: %lu)", end - buf, nal_size);
            return NULL;
        }

        out_size += hb_nal_unit_write_annexb(NULL, buf, nal_size);
        buf      += nal_size;
    }

    out = hb_buffer_init(out_size);
    if (out == NULL)
    {
        hb_error("hb_nal_bitstream_mp4_to_annexb: hb_buffer_init failed");
        return NULL;
    }

    out_size = 0;
    buf      = (uint8_t*)data;
    end      = (uint8_t*)data + size;

    while (end - buf > nal_length_size)
    {
        buf      += mp4_nal_unit_length(buf, nal_length_size, &nal_size);
        out_size += hb_nal_unit_write_annexb(out->data + out_size, buf, nal_size);
        buf      += nal_size;
    }

    return out;
}

static int is_post_hevc_sei_nal_type(int nal_type)
{
    return nal_type != HB_HEVC_NAL_UNIT_PREFIX_SEI &&
           nal_type != HB_HEVC_NAL_UNIT_SPS &&
           nal_type != HB_HEVC_NAL_UNIT_PPS &&
           nal_type != HB_HEVC_NAL_UNIT_ACCESS_UNIT_DELIMITER;
}

/**
 * Copies the data inserting emulation prevention bytes as needed.
 * Existing data in the destination can be taken into account by providing
 * dst with a dst_offset > 0.
 *
 * @return The number of bytes copied on success. On failure, the negative of
 *         the number of bytes needed to copy src is returned.
 */
static int copy_emulation_prev(const uint8_t *src,
                               size_t         src_size,
                               uint8_t       *dst,
                               ssize_t        dst_offset,
                               size_t         dst_size)
{
    int zeros = 0;
    int wrote_bytes = 0;
    uint8_t *dst_end = dst ? dst + dst_size : NULL;
    const uint8_t* src_end = src + src_size;
    int start_at = dst_offset > 2 ? dst_offset - 2 : 0;

    for (int i = start_at; i < dst_offset && i < dst_size; i++)
    {
        if (!dst[i])
        {
            zeros++;
        }
        else
        {
            zeros = 0;
        }
    }

    if (dst)
    {
        dst += dst_offset;
    }
    for (; src < src_end;)
    {
        if (zeros == 2)
        {
            int insert_ep3_byte = *src <= 3;
            if (insert_ep3_byte)
            {
                if (dst < dst_end)
                {
                    *dst = 3;
                }
                if (dst)
                {
                    dst++;
                }
                wrote_bytes++;
            }

            zeros = 0;
        }

        if (dst < dst_end)
        {
            *dst = *src;
        }

        if (!*src)
        {
            zeros++;
        }
        else
        {
            zeros = 0;
        }

        src++;
        if (dst)
        {
            dst++;
        }
        wrote_bytes++;
    }

    if (!dst)
    {
        return -wrote_bytes;
    }

    return wrote_bytes;
}

/**
 * Returns a sufficient number of bytes to contain the sei data.
 * It may be greater than the minimum required.
 */
static int get_sei_msg_bytes(const uint8_t *sei_data, const size_t sei_size, int type)
{
    int copied_size;
    if (sei_size == 0)
    {
        return 0;
    }

    copied_size = -copy_emulation_prev(sei_data,
                                       sei_size,
                                       NULL,
                                       0,
                                       0);

    if ((sei_size % 255) == 0) //may result in an extra byte
    {
        copied_size++;
    }

    return copied_size + sei_size / 255 + 1 + type / 255 + 1;
}

size_t hb_sei_unit_write_isomp4(const uint8_t *sei,
                                const size_t   sei_size,
                                int            sei_type,
                                uint8_t       *dst,
                                size_t         dst_size,
                                const uint8_t  nal_length_size)
{
    size_t remaining_sei_size = sei_size;
    size_t remaining_dst_size = dst_size;
    int sei_header_bytes;
    int bytes_written = 0;
    ssize_t offset;

    if (!remaining_dst_size)
    {
        return -1;
    }

    size_t sei_nalu_size = dst_size - nal_length_size;

    uint8_t length[4]; // up to 4-byte length replaces Annex B start code prefix
    uint8_t header[2]; // 2-byte NAL header

    for (int i = 0; i < nal_length_size; i++)
    {
        length[i] = (sei_nalu_size >> (8 * (nal_length_size - 1 - i))) & 0xff;
    }
    memcpy(dst, &length[0], nal_length_size);
    dst += nal_length_size;

    // NAL Header
    header[0] = HB_HEVC_NAL_UNIT_PREFIX_SEI << 1;
    header[1] = 1;

    memcpy(dst, &header[0], sizeof(header));
    dst += sizeof(header);

    remaining_dst_size -= nal_length_size + sizeof(header);

    uint8_t *sei_start = dst;

    while (sei_type && remaining_dst_size != 0)
    {
        int sei_byte = sei_type > 255 ? 255 : sei_type;
        *dst = sei_byte;

        sei_type -= sei_byte;
        dst++;
        remaining_dst_size--;
    }

    if (!dst_size)
    {
        return -1;
    }

    while (remaining_sei_size && remaining_dst_size != 0)
    {
        int size_byte = remaining_sei_size > 255 ? 255 : remaining_sei_size;
        *dst = size_byte;

        remaining_sei_size -= size_byte;
        dst++;
        remaining_dst_size--;
    }

    if (remaining_dst_size < sei_size)
    {
        return -1;
    }

    sei_header_bytes = dst - sei_start;

    offset = sei_header_bytes;
    bytes_written = copy_emulation_prev(sei,
                                        sei_size,
                                        sei_start,
                                        offset,
                                        dst_size - nal_length_size - sizeof(header) - 1);
    if (bytes_written < 0)
    {
        return -1;
    }

    remaining_dst_size -= bytes_written;

    if (remaining_dst_size < 1)
    {
        return -1;
    }

    // rbsp_stop_one_bit
    dst+= bytes_written;
    *dst = 0x80;

    return nal_length_size + sizeof(header) + sei_header_bytes + bytes_written + 1;
}

hb_buffer_t * hb_isomp4_hevc_nal_bitstream_insert_payloads(const uint8_t *data,
                                                           const size_t size,
                                                                 hb_sei_t *seis,
                                                           const size_t sei_count,
                                                           const hb_nal_t *nals,
                                                           const size_t nal_count,
                                                           const uint8_t nal_length_size)
{
    hb_buffer_t *out;
    const uint8_t *buf, *end;
    uint8_t *out_data;
    size_t out_size = 0, buf_size;

    if ((seis == NULL || sei_count == 0) &&
        (nals == NULL || nal_count == 0))
    {
        return NULL;
    }

    if ((seis == NULL && sei_count > 0) ||
        (nals == NULL && nal_count > 0))
    {
        return NULL;
    }

    for (int i = 0; i < sei_count; i++)
    {
        hb_sei_t *sei = &seis[i];
        size_t msg_size = get_sei_msg_bytes(sei->payload, sei->payload_size, sei->type);
        sei->nalu_size = nal_length_size + 2 + msg_size + 1;
        sei->written = 0;

        out_size += sei->nalu_size;
    }

    for (int i = 0; i < nal_count; i++)
    {
        size_t nalu_size = nal_length_size + 2 + nals[i].payload_size;
        out_size += nalu_size;
    }

    out_size += size;

    out = hb_buffer_init(out_size);
    if (out == NULL)
    {
        hb_error("hb_nal_bitstream_insert_sei: hb_buffer_init failed");
        return NULL;
    }

    buf_size = size;
    buf      = data;
    end      = data + size;
    out_data = out->data;

    while ((buf = hb_isomp4_find_next_nalu(buf, &buf_size, nal_length_size)) != NULL)
    {
        uint8_t nal_type = buf[nal_length_size] >> 1;

        for (int i = 0; i < sei_count; i++)
        {
            hb_sei_t *sei = &seis[i];
            if (!sei->written && is_post_hevc_sei_nal_type(nal_type))
            {
                out_data += hb_sei_unit_write_isomp4(sei->payload, sei->payload_size, sei->type,
                                                     out_data, sei->nalu_size, nal_length_size);
                sei->written = 1;
            }
        }

        memcpy(out_data, buf, buf_size);
        out_data += buf_size;
        buf      += buf_size;
        buf_size  = end - buf;
    }

    for (int i = 0; i < nal_count; i++)
    {
        // Append the DOVI RPU payload at the end
        out_data += hb_nal_unit_payload_write_isomp4(out_data, nals[i].payload, nals[i].payload_size,
                                                     nals[i].type, nal_length_size);
    }

    return out;
}
