#include "VideoPlayer.h"

#include <iostream>
#include <chrono>

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}

VideoPlayer::VideoPlayer() {}

VideoPlayer::~VideoPlayer() {
    if (m_texture) SDL_DestroyTexture(m_texture);
    if (m_rgbBuffer) av_free(m_rgbBuffer);
    if (m_frame) av_frame_free(&m_frame);
    if (m_sws) sws_freeContext(m_sws);
    if (m_dec) avcodec_free_context(&m_dec);
    if (m_fmt) avformat_close_input(&m_fmt);
}

bool VideoPlayer::open(const std::string& path, SDL_Renderer* renderer) {
    m_path = path;
    avformat_network_init();
    if (avformat_open_input(&m_fmt, path.c_str(), nullptr, nullptr) != 0) {
        std::cerr << "VideoPlayer: failed to open " << path << "\n";
        return false;
    }
    if (avformat_find_stream_info(m_fmt, nullptr) < 0) {
        std::cerr << "VideoPlayer: failed to find stream info\n";
        return false;
    }
    // Find video stream
    m_videoStream = -1;
    for (unsigned i = 0; i < m_fmt->nb_streams; ++i) {
        if (m_fmt->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { m_videoStream = (int)i; break; }
    }
    if (m_videoStream < 0) { std::cerr << "VideoPlayer: no video stream\n"; return false; }

    AVCodecParameters* codecpar = m_fmt->streams[m_videoStream]->codecpar;
    const AVCodec* codec = avcodec_find_decoder(codecpar->codec_id);
    if (!codec) { std::cerr << "VideoPlayer: decoder not found\n"; return false; }
    m_dec = avcodec_alloc_context3(codec);
    if (!m_dec) { std::cerr << "VideoPlayer: failed to alloc codec ctx\n"; return false; }
    if (avcodec_parameters_to_context(m_dec, codecpar) < 0) { std::cerr << "VideoPlayer: param to ctx failed\n"; return false; }
    if (avcodec_open2(m_dec, codec, nullptr) < 0) { std::cerr << "VideoPlayer: open codec failed\n"; return false; }

    m_width = m_dec->width;
    m_height = m_dec->height;
    m_frame = av_frame_alloc();
    m_sws = sws_getContext(m_width, m_height, m_dec->pix_fmt, m_width, m_height, AV_PIX_FMT_RGBA, SWS_BILINEAR, nullptr, nullptr, nullptr);
    m_rgbBufferSize = av_image_get_buffer_size(AV_PIX_FMT_RGBA, m_width, m_height, 1);
    m_rgbBuffer = (uint8_t*)av_malloc(m_rgbBufferSize);

    if (renderer) {
        m_texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, m_width, m_height);
        if (!m_texture) { std::cerr << "VideoPlayer: failed create texture\n"; }
    }

    m_finished = false;
    m_textureReady = false;
    m_started = false;
    m_frameAccumulatorMs = 0.0;

    // Estimate frame interval.
    m_frameIntervalMs = 33.333;
    if (m_fmt && m_videoStream >= 0) {
        AVRational fr = m_fmt->streams[m_videoStream]->avg_frame_rate;
        if (fr.num > 0 && fr.den > 0) {
            const double fps = av_q2d(fr);
            if (fps > 1.0) {
                m_frameIntervalMs = 1000.0 / fps;
            }
        }
    }

    // Seek to start
    av_seek_frame(m_fmt, m_videoStream, 0, AVSEEK_FLAG_BACKWARD);
    if (m_dec) avcodec_flush_buffers(m_dec);
    return true;
}

bool VideoPlayer::decodeOneFrame() {
    if (m_finished || !m_fmt) return false;

    AVPacket* pkt = av_packet_alloc();
    if (!pkt) {
        m_finished = true;
        return false;
    }

    int ret = 0;
    while (av_read_frame(m_fmt, pkt) >= 0) {
        if (pkt->stream_index == m_videoStream) {
            ret = avcodec_send_packet(m_dec, pkt);
            if (ret < 0) {
                av_packet_unref(pkt);
                continue;
            }

            while (ret >= 0) {
                ret = avcodec_receive_frame(m_dec, m_frame);
                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) break;
                if (ret < 0) break;

                uint8_t* dstData[4] = { m_rgbBuffer, nullptr, nullptr, nullptr };
                int dstLinesize[4] = { m_width * 4, 0, 0, 0 };
                sws_scale(m_sws, m_frame->data, m_frame->linesize, 0, m_height, dstData, dstLinesize);
                m_textureReady = true;
                if (m_texture) {
                    SDL_UpdateTexture(m_texture, nullptr, m_rgbBuffer, dstLinesize[0]);
                }
                av_frame_unref(m_frame);

                av_packet_unref(pkt);
                av_packet_free(&pkt);
                return true;
            }
        }
        av_packet_unref(pkt);
    }

    av_packet_free(&pkt);
    m_finished = true;
    return false;
}

bool VideoPlayer::decodeFirstFrame() {
    if (!m_fmt || m_finished) return false;
    if (m_textureReady) return true;
    // Ensure we are at the beginning.
    av_seek_frame(m_fmt, m_videoStream, 0, AVSEEK_FLAG_BACKWARD);
    if (m_dec) avcodec_flush_buffers(m_dec);
    return decodeOneFrame();
}

void VideoPlayer::start() {
    m_started = true;
}

bool VideoPlayer::update(double deltaMs) {
    if (m_finished || !m_fmt) return false;
    if (!m_started) return true;

    m_frameAccumulatorMs += deltaMs;

    // Decode at most a small burst per frame to avoid spiral-of-death.
    int framesDecoded = 0;
    const int maxFramesPerTick = 4;
    while (m_frameAccumulatorMs >= m_frameIntervalMs && framesDecoded < maxFramesPerTick) {
        m_frameAccumulatorMs -= m_frameIntervalMs;
        if (!decodeOneFrame()) {
            return false;
        }
        ++framesDecoded;
    }
    return !m_finished;
}

bool VideoPlayer::update() {
    // Legacy behavior: decode exactly one frame.
    return decodeOneFrame();
}

void VideoPlayer::render(SDL_Renderer* renderer, int winW, int winH) {
    if (!m_textureReady || !m_texture || !renderer) return;
    if (winW <= 0 || winH <= 0) return;
    SDL_FRect dst = { 0.0f, 0.0f, (float)winW, (float)winH };
    SDL_RenderTexture(renderer, m_texture, nullptr, &dst);
}
