use super::shader::YuvFrameData;
use crate::player::{self, FrameAction, Player, VideoContextError};
use std::sync::{
    Arc,
    atomic::{AtomicU64, Ordering},
};
use thiserror::Error;

static FRAME_COUNTER: AtomicU64 = AtomicU64::new(0);

pub(crate) struct YuvScratch {
    pub y: Vec<u8>,
    pub u: Vec<u8>,
    pub v: Vec<u8>,
}

#[derive(Debug, Error)]
#[error("iced video backend error")]
pub enum VideoBackendError {
    VideoContext(#[from] VideoContextError),
    Player(#[from] player::PlayerError),
}

pub(crate) struct FrameResult {
    pub(crate) frame_data: Option<Arc<YuvFrameData>>,
    pub(crate) clock: f64,
    pub(crate) action: FrameAction,
    pub(crate) buf_len: usize,
}

pub(crate) fn process_frame(
    player_ctx: &Player,
    width: u32,
    height: u32,
    scratch: &mut Option<YuvScratch>,
) -> Result<FrameResult, VideoBackendError> {
    let vctx = &player_ctx.video_ctx;

    // Fetch data from video ctx in one mutex-lock
    let (clock, action, decoded_frame, buf_len) =
        vctx.with_deque_mut(|mut deque| -> Result<_, VideoBackendError> {
            let pts = deque.front().map(|f| f.pts);
            let (clock, action) = player_ctx.next_frame_action(pts)?;
            let frame = match action {
                FrameAction::RenderFrame | FrameAction::DropFrame => deque.pop_front(),
                _ => None,
            };
            Ok((clock, action, frame, deque.len()))
        })??;

    let mut current_frame: Option<Arc<YuvFrameData>> = None;
    match action {
        FrameAction::RenderFrame => {
            if let Some(decoded) = decoded_frame {
                let w = width as usize;
                let h = height as usize;
                let cw = w.div_ceil(2);
                let ch = h.div_ceil(2);

                let buf = scratch.get_or_insert_with(|| YuvScratch {
                    y: Vec::with_capacity(w * h),
                    u: Vec::with_capacity(cw * ch),
                    v: Vec::with_capacity(cw * ch),
                });
                buf.y.clear();
                buf.u.clear();
                buf.v.clear();

                let fill = |dst: &mut Vec<u8>, plane: usize, pw: usize, ph: usize| {
                    let stride = decoded.frame.stride(plane);
                    let src = decoded.frame.data(plane);
                    for row in 0..ph {
                        let start = row * stride;
                        dst.extend_from_slice(&src[start..start + pw]);
                    }
                };
                fill(&mut buf.y, 0, w, h);
                fill(&mut buf.u, 1, cw, ch);
                fill(&mut buf.v, 2, cw, ch);

                current_frame = Some(Arc::new(YuvFrameData {
                    id: FRAME_COUNTER.fetch_add(1, Ordering::Relaxed),
                    y_plane: buf.y.clone(),
                    u_plane: buf.u.clone(),
                    v_plane: buf.v.clone(),
                    color_info: decoded.color,
                }));
            }
        }
        FrameAction::Nothing | FrameAction::DropFrame | FrameAction::Delay(_) => {}
    }

    let frame_res = FrameResult {
        frame_data: current_frame,
        clock,
        action,
        buf_len,
    };

    Ok(frame_res)
}
