/*
 * Copyright (c) 2026-present, the Ladybird developers.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/BinarySearch.h>
#include <LibWeb/MediaSourceExtensions/TrackBufferDemuxer.h>

namespace Web::MediaSourceExtensions {

TrackBufferDemuxer::TrackBufferDemuxer(Media::Track const& track, Media::CodecID codec_id, ByteBuffer codec_initialization_data)
    : m_track(track)
    , m_codec_id(codec_id)
    , m_codec_initialization_data(move(codec_initialization_data))
{
}

TrackBufferDemuxer::~TrackBufferDemuxer() = default;

Media::TimeRanges TrackBufferDemuxer::track_buffer_ranges() const
{
    Sync::MutexLocker locker { m_mutex };
    // https://w3c.github.io/media-source/#track-buffer-ranges
    // NOTE: Implementations MAY coalesce adjacent ranges separated by a gap smaller than 2 times the
    //       maximum frame duration buffered so far in this track buffer.
    auto max_gap = maximum_time_range_gap();
    if (max_gap.is_zero())
        return m_track_buffer_ranges;
    return m_track_buffer_ranges.coalesced(max_gap);
}

void TrackBufferDemuxer::add_coded_frame(Media::CodedFrame frame)
{
    Sync::MutexLocker locker { m_mutex };
    auto start = frame.timestamp();
    auto end = frame.timestamp() + frame.duration();
    m_last_frame_duration = frame.duration();
    m_track_buffer_ranges.add_range(start, end);

    // Insert in sorted order by presentation timestamp using binary search.
    // Overlapping frames should have been removed by remove_coded_frames_and_dependants_in_range()
    // before this call.
    auto timestamp = frame.timestamp();
    size_t insert_index = 0;
    auto* existing_coded_frame = binary_search(m_coded_frames, timestamp, &insert_index, [](AK::Duration needle, Media::CodedFrame const& frame) {
        return needle <=> frame.timestamp();
    });
    VERIFY(!existing_coded_frame);
    if (insert_index < m_coded_frames.size() && m_coded_frames[insert_index].timestamp() < timestamp)
        insert_index++;

    m_total_bytes += frame.data().size();
    m_coded_frames.insert(insert_index, move(frame));

    if (insert_index <= m_read_position && (!m_last_returned_timestamp.has_value() || m_last_returned_timestamp.value() > timestamp))
        m_read_position++;

    m_data_changed.broadcast();
}

void TrackBufferDemuxer::remove_coded_frames_and_dependants_in_range(AK::Duration start, AK::Duration end)
{
    Sync::MutexLocker locker { m_mutex };

    // https://w3c.github.io/media-source/#sourcebuffer-coded-frame-processing
    // 1.13. Remove all coded frames from track buffer that have a presentation timestamp greater than
    //       or equal to presentation timestamp and less than frame end timestamp.
    // 1.14. Remove all possible decoding dependencies on the coded frames removed in the previous step
    //       by removing all coded frames from track buffer between those frames removed in the previous
    //       step and the next random access point after those removed frames.

    // Find the first frame at or after the start of the range.
    size_t remove_start = 0;
    while (remove_start < m_coded_frames.size() && m_coded_frames[remove_start].timestamp() < start)
        remove_start++;

    // Find all overlapping frames plus subsequent frames up to the next keyframe.
    size_t remove_end = remove_start;
    while (remove_end < m_coded_frames.size() && m_coded_frames[remove_end].timestamp() < end)
        remove_end++;
    if (remove_end <= remove_start)
        return;
    while (remove_end < m_coded_frames.size() && !m_coded_frames[remove_end].is_keyframe())
        remove_end++;

    for (size_t i = remove_start; i < remove_end; i++) {
        auto const& removed_frame = m_coded_frames[i];
        auto removed_start = removed_frame.timestamp();
        auto removed_end = (removed_frame.timestamp() + removed_frame.duration());
        m_track_buffer_ranges.remove_range(removed_start, removed_end);
        m_total_bytes -= removed_frame.data().size();
    }

    m_coded_frames.remove(remove_start, remove_end - remove_start);

    // Adjust read position if it was in or past the removed range.
    if (m_read_position >= remove_end) {
        m_read_position -= (remove_end - remove_start);
    } else if (m_read_position > remove_start) {
        m_read_position = remove_start > 0 ? remove_start - 1 : 0;

        while (m_read_position > 0 && !m_coded_frames[m_read_position].is_keyframe())
            m_read_position--;

        m_last_returned_timestamp.clear();
    }
}

size_t TrackBufferDemuxer::total_bytes() const
{
    Sync::MutexLocker locker { m_mutex };
    return m_total_bytes;
}

bool TrackBufferDemuxer::is_frame_evictable_while_locked(Media::CodedFrame const& frame, AK::Duration current_time) const
{
    auto time_range_start = current_time;
    auto time_range_end = current_time;
    if (m_last_returned_timestamp.has_value()) {
        time_range_start = min(time_range_start, m_last_returned_timestamp.value());
        time_range_end = max(time_range_end, m_last_returned_timestamp.value());
    }
    return frame.timestamp() < time_range_start || frame.timestamp() > time_range_end;
}

Optional<AK::Duration> TrackBufferDemuxer::earliest_evictable_frame_timestamp(AK::Duration current_time) const
{
    Sync::MutexLocker locker { m_mutex };
    if (m_coded_frames.is_empty())
        return {};
    auto const& frame = m_coded_frames[0];
    if (!is_frame_evictable_while_locked(frame, current_time))
        return {};
    return frame.timestamp();
}

size_t TrackBufferDemuxer::take_earliest_frame()
{
    Sync::MutexLocker locker { m_mutex };
    auto frame = m_coded_frames.take_first();
    m_track_buffer_ranges.remove_range(frame.timestamp(), frame.timestamp() + frame.duration());
    auto bytes = frame.data().size();
    m_total_bytes -= bytes;
    if (m_read_position > 0)
        m_read_position--;
    return bytes;
}

Optional<AK::Duration> TrackBufferDemuxer::latest_evictable_frame_timestamp(AK::Duration current_time) const
{
    Sync::MutexLocker locker { m_mutex };
    if (m_coded_frames.is_empty())
        return {};
    auto const& frame = m_coded_frames.last();
    if (!is_frame_evictable_while_locked(frame, current_time))
        return {};
    return frame.timestamp();
}

size_t TrackBufferDemuxer::take_latest_frame()
{
    Sync::MutexLocker locker { m_mutex };
    auto frame = m_coded_frames.take_last();
    m_track_buffer_ranges.remove_range(frame.timestamp(), frame.timestamp() + frame.duration());
    auto bytes = frame.data().size();
    m_total_bytes -= bytes;
    return bytes;
}

void TrackBufferDemuxer::set_reached_end_of_stream()
{
    Sync::MutexLocker locker { m_mutex };
    m_reached_end_of_stream = true;
    m_data_changed.broadcast();
}

void TrackBufferDemuxer::clear_reached_end_of_stream()
{
    Sync::MutexLocker locker { m_mutex };
    m_reached_end_of_stream = false;
}

Media::DecoderErrorOr<void> TrackBufferDemuxer::create_context_for_track(Media::Track const&)
{
    return {};
}

Media::DecoderErrorOr<Vector<Media::Track>> TrackBufferDemuxer::get_tracks_for_type(Media::TrackType type)
{
    if (m_track.type() == type)
        return Vector { m_track };
    return Vector<Media::Track> {};
}

Media::DecoderErrorOr<Optional<Media::Track>> TrackBufferDemuxer::get_preferred_track_for_type(Media::TrackType type)
{
    if (m_track.type() == type)
        return Optional<Media::Track> { m_track };
    return Optional<Media::Track> {};
}

AK::Duration TrackBufferDemuxer::maximum_time_range_gap() const
{
    return m_last_frame_duration + m_last_frame_duration;
}

bool TrackBufferDemuxer::next_frame_is_in_gap_while_locked() const
{
    auto max_gap = maximum_time_range_gap();
    if (!m_last_returned_timestamp.has_value() || max_gap.is_zero())
        return false;
    if (m_read_position >= m_coded_frames.size())
        return false;
    auto delta = m_coded_frames[m_read_position].timestamp() - m_last_returned_timestamp.value();
    return delta > max_gap;
}

Media::DecoderErrorOr<Media::CodedFrame> TrackBufferDemuxer::get_next_sample_for_track(Media::Track const&)
{
    Sync::MutexLocker locker { m_mutex };

    while (m_read_position >= m_coded_frames.size() || next_frame_is_in_gap_while_locked()) {
        if (m_aborted.load())
            return Media::DecoderError::with_description(Media::DecoderErrorCategory::Aborted, "Read aborted"sv);
        if (m_read_position >= m_coded_frames.size() && m_reached_end_of_stream)
            return Media::DecoderError::with_description(Media::DecoderErrorCategory::EndOfStream, "End of stream"sv);
        m_data_changed.wait();
    }

    m_last_returned_timestamp = m_coded_frames[m_read_position].timestamp();
    return m_coded_frames[m_read_position++];
}

Media::DecoderErrorOr<Media::CodecID> TrackBufferDemuxer::get_codec_id_for_track(Media::Track const&)
{
    return m_codec_id;
}

Media::DecoderErrorOr<ReadonlyBytes> TrackBufferDemuxer::get_codec_initialization_data_for_track(Media::Track const&)
{
    return m_codec_initialization_data.bytes();
}

AK::Duration TrackBufferDemuxer::select_fast_seek_target_for_track(Media::Track const&, AK::Duration target, Media::SeekMode mode)
{
    Sync::MutexLocker locker { m_mutex };
    if (m_coded_frames.is_empty())
        return target;

    size_t nearby_index = 0;
    binary_search(m_coded_frames, target, &nearby_index, [](AK::Duration needle, Media::CodedFrame const& frame) {
        return needle <=> frame.timestamp();
    });

    if (mode == Media::SeekMode::FastBefore) {
        if (m_coded_frames[nearby_index].timestamp() > target)
            return target;
        for (auto i = nearby_index; i-- > 0;) {
            if (m_coded_frames[i].is_keyframe())
                return m_coded_frames[i].timestamp();
        }
        return target;
    }

    VERIFY(mode == Media::SeekMode::FastAfter);
    auto start = nearby_index;
    if (m_coded_frames[start].timestamp() < target)
        start++;
    for (auto i = start; i < m_coded_frames.size(); i++) {
        if (m_coded_frames[i].is_keyframe())
            return m_coded_frames[i].timestamp();
    }
    return target;
}

Media::DecoderErrorOr<Media::DemuxerSeekResult> TrackBufferDemuxer::seek_to_most_recent_keyframe(Media::Track const&, AK::Duration timestamp, Media::DemuxerSeekOptions)
{
    Sync::MutexLocker locker { m_mutex };

    size_t best_position = 0;
    AK::Duration best_timestamp;

    while (true) {
        if (m_aborted.load())
            return Media::DecoderError::with_description(Media::DecoderErrorCategory::Aborted, "Seek aborted"sv);

        best_timestamp = AK::Duration::max();

        for (size_t i = 0; i < m_coded_frames.size(); i++) {
            auto const& frame = m_coded_frames[i];
            if (frame.timestamp() >= timestamp)
                break;
            if (frame.is_keyframe()) {
                best_position = i;
                best_timestamp = frame.timestamp();
            }
        }

        auto max_gap = maximum_time_range_gap();
        if (timestamp >= best_timestamp) {
            auto has_gap = [&] {
                auto prior_timestamp = m_coded_frames[best_position].timestamp();
                for (size_t i = best_position + 1; i < m_coded_frames.size(); i++) {
                    auto const& frame = m_coded_frames[i];
                    auto delta = frame.timestamp() - prior_timestamp;
                    if (delta > max_gap)
                        return true;
                    if (frame.timestamp() >= timestamp)
                        break;
                    prior_timestamp = frame.timestamp();
                }
                return timestamp - prior_timestamp > max_gap;
            }();
            if (!has_gap)
                break;
        } else if (best_position < m_coded_frames.size()) {
            auto& future_frame = m_coded_frames[best_position];
            if (future_frame.is_keyframe() && future_frame.timestamp() - timestamp <= max_gap) {
                best_timestamp = timestamp;
                break;
            }
        }

        m_data_changed.wait();
    }

    m_read_position = best_position;
    m_last_returned_timestamp = best_timestamp;
    m_data_changed.broadcast();
    return Media::DemuxerSeekResult::MovedPosition;
}

Media::DecoderErrorOr<AK::Duration> TrackBufferDemuxer::duration_of_track(Media::Track const&)
{
    return AK::Duration::zero();
}

Media::DecoderErrorOr<AK::Duration> TrackBufferDemuxer::total_duration()
{
    return AK::Duration::zero();
}

Media::TimeRanges TrackBufferDemuxer::buffered_time_ranges() const
{
    return track_buffer_ranges();
}

void TrackBufferDemuxer::set_blocking_reads_aborted_for_track(Media::Track const&)
{
    m_aborted.store(true);
    Sync::MutexLocker locker { m_mutex };
    m_data_changed.broadcast();
}

void TrackBufferDemuxer::reset_blocking_reads_aborted_for_track(Media::Track const&)
{
    m_aborted.store(false);
}

bool TrackBufferDemuxer::is_read_blocked_for_track(Media::Track const&)
{
    Sync::MutexLocker locker { m_mutex };
    if (m_aborted.load())
        return false;
    return m_read_position >= m_coded_frames.size() || next_frame_is_in_gap_while_locked();
}

}
