//! Adapter: AVC through a software encoder library loaded at run time from a path the
//! user names ([platform-encode] §5.4), whose distributor ships it as a separate binary.
//!
//! This is an adapter file: it names the library's real symbols and structures,
//! transcribed from its BSD-licensed interface headers for version 2.7, and checked
//! against them (the offsets below were printed by a C program compiled against those
//! headers). The library is never linked and never shipped: the user obtains it and
//! names its path, and the encode record states the SHA-256 of the file loaded. A
//! library of another major or minor version is refused, because its structures may
//! differ.
//!
//! What it asks of the library: one spatial and one temporal layer, one slice, one
//! reference picture, no periodic intra picture, no scene-change detection, no frame
//! skipping, constant parameter-set identifiers, CABAC, a target average bit rate, and an
//! IDR forced exactly where the caller marks one. The library returns Annex B NAL units.
//!
//! [platform-encode]: ../../../../extensions/platform-encode.md

use super::dl::{Lib, file_sha256};
use super::{Codec, Config, EncodeError, Identity, Opened, Options, Picture, Session};
use std::ffi::{c_int, c_void};

// Sizes and offsets, from the 2.7.0 headers (codec_app_def.h, codec_api.h).
const PARAM_EXT_SIZE: usize = 924;
const SOURCE_PICTURE_SIZE: usize = 80;
const FRAME_BS_INFO_SIZE: usize = 7192;
const LAYER_BS_INFO_SIZE: usize = 56;

mod at {
    // SEncParamExt
    pub const USAGE: usize = 0;
    pub const WIDTH: usize = 4;
    pub const HEIGHT: usize = 8;
    pub const TARGET_BITRATE: usize = 12;
    pub const RC_MODE: usize = 16;
    pub const MAX_FRAME_RATE: usize = 20;
    pub const TEMPORAL_LAYERS: usize = 24;
    pub const SPATIAL_LAYERS: usize = 28;
    pub const LAYER0: usize = 32;
    pub const INTRA_PERIOD: usize = 836;
    pub const NUM_REF: usize = 840;
    pub const SPS_PPS_STRATEGY: usize = 844;
    pub const PREFIX_NAL: usize = 848;
    pub const SSEI: usize = 849;
    pub const ENTROPY: usize = 856;
    pub const FRAME_SKIP: usize = 860;
    pub const MAX_BITRATE: usize = 864;
    pub const LTR: usize = 880;
    pub const THREADS: usize = 892;
    pub const DENOISE: usize = 908;
    pub const FRAME_CROPPING: usize = 911;
    pub const SCENE_CHANGE: usize = 912;
    // SSpatialLayerConfig, from the layer's start
    pub const L_WIDTH: usize = 0;
    pub const L_HEIGHT: usize = 4;
    pub const L_FRAME_RATE: usize = 8;
    pub const L_BITRATE: usize = 12;
    pub const L_MAX_BITRATE: usize = 16;
    pub const L_PROFILE: usize = 20;
    pub const L_LEVEL: usize = 24;
    pub const L_SLICE_MODE: usize = 32;
    pub const L_SLICE_NUM: usize = 36;
    pub const L_SIGNAL_TYPE: usize = 184;
    pub const L_VIDEO_FORMAT: usize = 185;
    pub const L_FULL_RANGE: usize = 186;
    pub const L_COLOUR_DESC: usize = 187;
    pub const L_PRIMARIES: usize = 188;
    pub const L_TRANSFER: usize = 189;
    pub const L_MATRIX: usize = 190;
    // SSourcePicture
    pub const P_FORMAT: usize = 0;
    pub const P_STRIDE: usize = 4;
    pub const P_DATA: usize = 24;
    pub const P_WIDTH: usize = 56;
    pub const P_HEIGHT: usize = 60;
    pub const P_TIME: usize = 64;
    // SFrameBSInfo and SLayerBSInfo
    pub const F_LAYERS: usize = 0;
    pub const F_LAYER_INFO: usize = 8;
    pub const F_FRAME_TYPE: usize = 7176;
    pub const LB_NAL_COUNT: usize = 16;
    pub const LB_NAL_LENGTHS: usize = 24;
    pub const LB_BUF: usize = 32;
}

const CAMERA_VIDEO_REAL_TIME: i32 = 0;
const RC_BITRATE_MODE: i32 = 1;
const PRO_HIGH: i32 = 100;
const LEVEL_UNKNOWN: i32 = 0;
const SM_SINGLE_SLICE: i32 = 0;
const CONSTANT_ID: i32 = 0;
const VIDEO_FORMAT_I420: i32 = 23;
const FRAME_TYPE_INVALID: i32 = 0;
const FRAME_TYPE_SKIP: i32 = 4;
const OPTION_TRACE_LEVEL: c_int = 25;
const LOG_QUIET: c_int = 0;
const LOG_ERROR_AND_WARNING: c_int = 3;
const VF_UNDEF: u8 = 5;
const BT709: u8 = 1;
const MAX_LAYERS: usize = 128;

/// The interface's vtable, in its order (codec_api.h, `ISVCEncoderVtbl`); each entry
/// takes the encoder object first. Every entry is declared so the table's layout is the
/// library's, though not every one is called.
#[repr(C)]
#[allow(dead_code)]
struct Vtbl {
    initialize: unsafe extern "C" fn(*mut Enc, *const c_void) -> c_int,
    initialize_ext: unsafe extern "C" fn(*mut Enc, *const u8) -> c_int,
    get_default_params: unsafe extern "C" fn(*mut Enc, *mut u8) -> c_int,
    uninitialize: unsafe extern "C" fn(*mut Enc) -> c_int,
    encode_frame: unsafe extern "C" fn(*mut Enc, *const u8, *mut u8) -> c_int,
    encode_parameter_sets: unsafe extern "C" fn(*mut Enc, *mut u8) -> c_int,
    // The library's own definition takes a layer id after the flag; -1 is every layer.
    force_intra_frame: unsafe extern "C" fn(*mut Enc, bool, c_int) -> c_int,
    set_option: unsafe extern "C" fn(*mut Enc, c_int, *mut c_void) -> c_int,
    get_option: unsafe extern "C" fn(*mut Enc, c_int, *mut c_void) -> c_int,
}

/// The encoder object: a pointer to its vtable.
#[repr(C)]
struct Enc {
    vtbl: *const Vtbl,
}

#[repr(C)]
#[derive(Default)]
struct Version {
    major: u32,
    minor: u32,
    revision: u32,
    reserved: u32,
}

/// A buffer of `N` bytes aligned for the structures' pointers and 64-bit members.
#[repr(C, align(8))]
struct Buf<const N: usize>([u8; N]);

impl<const N: usize> Buf<N> {
    fn zeroed() -> Box<Buf<N>> {
        Box::new(Buf([0u8; N]))
    }
    fn i32_at(&mut self, o: usize, v: i32) {
        self.0[o..o + 4].copy_from_slice(&v.to_ne_bytes());
    }
    fn u16_at(&mut self, o: usize, v: u16) {
        self.0[o..o + 2].copy_from_slice(&v.to_ne_bytes());
    }
    fn f32_at(&mut self, o: usize, v: f32) {
        self.0[o..o + 4].copy_from_slice(&v.to_ne_bytes());
    }
    fn u8_at(&mut self, o: usize, v: u8) {
        self.0[o] = v;
    }
    fn ptr_at(&mut self, o: usize, p: *const u8) {
        self.0[o..o + 8].copy_from_slice(&(p as usize as u64).to_ne_bytes());
    }
    fn read_i32(&self, o: usize) -> i32 {
        i32::from_ne_bytes(self.0[o..o + 4].try_into().expect("four bytes"))
    }
    fn read_ptr(&self, o: usize) -> *const u8 {
        u64::from_ne_bytes(self.0[o..o + 8].try_into().expect("eight bytes")) as usize as *const u8
    }
}

const _: () = assert!(std::mem::size_of::<usize>() == 8, "the offsets are those of a 64-bit target");
const _: () = assert!(at::F_LAYER_INFO + MAX_LAYERS * LAYER_BS_INFO_SIZE == at::F_FRAME_TYPE);

struct Api {
    create: unsafe extern "C" fn(*mut *mut Enc) -> c_int,
    destroy: unsafe extern "C" fn(*mut Enc),
}

struct SwSession {
    api: Api,
    enc: *mut Enc,
    cfg: Config,
    n: i64,
    info: Box<Buf<FRAME_BS_INFO_SIZE>>,
}

impl SwSession {
    fn vt(&self) -> &Vtbl {
        // SAFETY: the object the library created; its first word is its vtable.
        unsafe { &*(*self.enc).vtbl }
    }
}

impl Session for SwSession {
    fn push(&mut self, pic: &Picture<'_>, idr: bool) -> Result<Vec<u8>, EncodeError> {
        let (w, h) = (self.cfg.width as i32, self.cfg.height as i32);
        let mut src = Buf::<SOURCE_PICTURE_SIZE>::zeroed();
        src.i32_at(at::P_FORMAT, VIDEO_FORMAT_I420);
        src.i32_at(at::P_STRIDE, w);
        src.i32_at(at::P_STRIDE + 4, w / 2);
        src.i32_at(at::P_STRIDE + 8, w / 2);
        src.ptr_at(at::P_DATA, pic.y.as_ptr());
        src.ptr_at(at::P_DATA + 8, pic.u.as_ptr());
        src.ptr_at(at::P_DATA + 16, pic.v.as_ptr());
        src.i32_at(at::P_WIDTH, w);
        src.i32_at(at::P_HEIGHT, h);
        let ms = self.n * 1000 * i64::from(self.cfg.fps_den) / i64::from(self.cfg.fps_num);
        src.0[at::P_TIME..at::P_TIME + 8].copy_from_slice(&ms.to_ne_bytes());
        self.n += 1;
        self.info.0.fill(0);
        let vt = self.vt();
        let (force, encode) = (vt.force_intra_frame, vt.encode_frame);
        // SAFETY: the library's own entry points on its object; the picture's planes
        // outlive the call, and the info buffer is the structure's size.
        unsafe {
            if idr && force(self.enc, true, -1) != 0 {
                return Err(EncodeError::new("encoder-failed", "the library refused to force an IDR"));
            }
            let rc = encode(self.enc, src.0.as_ptr(), self.info.0.as_mut_ptr());
            if rc != 0 {
                return Err(EncodeError::new("encoder-failed", format!("the library's encode returned {rc}")));
            }
        }
        let kind = self.info.read_i32(at::F_FRAME_TYPE);
        if kind == FRAME_TYPE_SKIP || kind == FRAME_TYPE_INVALID {
            return Err(EncodeError::new("encoder-failed", "the library skipped a picture"));
        }
        let layers = self.info.read_i32(at::F_LAYERS).clamp(0, MAX_LAYERS as i32) as usize;
        let mut out = Vec::new();
        for l in 0..layers {
            let base = at::F_LAYER_INFO + l * LAYER_BS_INFO_SIZE;
            let count = self.info.read_i32(base + at::LB_NAL_COUNT).max(0) as usize;
            let lens = self.info.read_ptr(base + at::LB_NAL_LENGTHS) as *const c_int;
            let buf = self.info.read_ptr(base + at::LB_BUF);
            if count == 0 || lens.is_null() || buf.is_null() {
                continue;
            }
            // SAFETY: the library's arrays of `count` lengths and their bytes, valid
            // until the next encode.
            unsafe {
                let total: usize = (0..count).map(|i| (*lens.add(i)).max(0) as usize).sum();
                out.extend_from_slice(std::slice::from_raw_parts(buf, total));
            }
        }
        Ok(out)
    }

    fn finish(&mut self) -> Result<Vec<u8>, EncodeError> {
        // The library returns each picture's access unit from its own encode call.
        Ok(Vec::new())
    }
}

impl Drop for SwSession {
    fn drop(&mut self) {
        let un = self.vt().uninitialize;
        // SAFETY: uninitialise, then destroy, the object the library created.
        unsafe {
            un(self.enc);
            (self.api.destroy)(self.enc);
        }
    }
}

pub(crate) fn open(cfg: &Config, opts: &Options) -> Result<Opened, EncodeError> {
    if cfg.codec != Codec::Avc {
        return Err(EncodeError::new("encoder-unavailable", "the runtime-loaded software encoder codes AVC only"));
    }
    let path = opts.library.as_ref().ok_or_else(|| EncodeError::new("encoder-unavailable", "the software interface needs the path of the library to load (--library)"))?;
    let library_sha256 = file_sha256(path).map_err(|e| EncodeError::new("encoder-unavailable", e))?;
    let lib = Lib::open(&path.to_string_lossy()).map_err(|e| EncodeError::new("encoder-unavailable", e))?;
    // SAFETY: each symbol cast to the signature codec_api.h declares.
    let unavailable = |e: String| EncodeError::new("encoder-unavailable", e);
    let (api, get_version) = unsafe {
        let create = lib.func::<unsafe extern "C" fn(*mut *mut Enc) -> c_int>("WelsCreateSVCEncoder").map_err(unavailable)?;
        let destroy = lib.func::<unsafe extern "C" fn(*mut Enc)>("WelsDestroySVCEncoder").map_err(unavailable)?;
        let version = lib.func::<unsafe extern "C" fn(*mut Version)>("WelsGetCodecVersionEx").map_err(unavailable)?;
        (Api { create, destroy }, version)
    };
    let mut v = Version::default();
    // SAFETY: a version structure of the header's layout.
    unsafe { get_version(&mut v) };
    if (v.major, v.minor) != (2, 7) {
        return Err(EncodeError::new("encoder-unavailable", format!("the library is version {}.{}.{}; this adapter is written for the 2.7 interface", v.major, v.minor, v.revision)));
    }
    let mut enc: *mut Enc = std::ptr::null_mut();
    // SAFETY: the library writes its object's address.
    if unsafe { (api.create)(&mut enc) } != 0 || enc.is_null() {
        return Err(EncodeError::new("encoder-unavailable", "the library made no encoder"));
    }
    let s = SwSession { api, enc, cfg: cfg.clone(), n: 0, info: Buf::zeroed() };
    let mut p = Buf::<PARAM_EXT_SIZE>::zeroed();
    let vt = s.vt();
    // SAFETY: the library fills, then reads, a parameter structure of the header's size.
    unsafe {
        if (vt.get_default_params)(s.enc, p.0.as_mut_ptr()) != 0 {
            return Err(EncodeError::new("encoder-failed", "the library gave no default parameters"));
        }
    }
    let fps = cfg.fps_num as f32 / cfg.fps_den as f32;
    let bps = (u64::from(cfg.kbps) * 1000).min(i32::MAX as u64) as i32;
    p.i32_at(at::USAGE, CAMERA_VIDEO_REAL_TIME);
    p.i32_at(at::WIDTH, cfg.width as i32);
    p.i32_at(at::HEIGHT, cfg.height as i32);
    p.i32_at(at::TARGET_BITRATE, bps);
    p.i32_at(at::RC_MODE, RC_BITRATE_MODE);
    p.f32_at(at::MAX_FRAME_RATE, fps);
    p.i32_at(at::TEMPORAL_LAYERS, 1);
    p.i32_at(at::SPATIAL_LAYERS, 1);
    let l = at::LAYER0;
    p.i32_at(l + at::L_WIDTH, cfg.width as i32);
    p.i32_at(l + at::L_HEIGHT, cfg.height as i32);
    p.f32_at(l + at::L_FRAME_RATE, fps);
    p.i32_at(l + at::L_BITRATE, bps);
    p.i32_at(l + at::L_MAX_BITRATE, 0);
    p.i32_at(l + at::L_PROFILE, PRO_HIGH);
    p.i32_at(l + at::L_LEVEL, LEVEL_UNKNOWN);
    p.i32_at(l + at::L_SLICE_MODE, SM_SINGLE_SLICE);
    p.i32_at(l + at::L_SLICE_NUM, 1);
    p.u8_at(l + at::L_SIGNAL_TYPE, 1);
    p.u8_at(l + at::L_VIDEO_FORMAT, VF_UNDEF);
    p.u8_at(l + at::L_FULL_RANGE, u8::from(cfg.full_range));
    p.u8_at(l + at::L_COLOUR_DESC, 1);
    p.u8_at(l + at::L_PRIMARIES, BT709);
    p.u8_at(l + at::L_TRANSFER, BT709);
    p.u8_at(l + at::L_MATRIX, BT709);
    p.i32_at(at::INTRA_PERIOD, 0);
    p.i32_at(at::NUM_REF, 1);
    p.i32_at(at::SPS_PPS_STRATEGY, CONSTANT_ID);
    p.u8_at(at::PREFIX_NAL, 0);
    p.u8_at(at::SSEI, 0);
    p.i32_at(at::ENTROPY, 1);
    p.u8_at(at::FRAME_SKIP, 0);
    p.i32_at(at::MAX_BITRATE, 0);
    p.u8_at(at::LTR, 0);
    p.u16_at(at::THREADS, 1);
    p.u8_at(at::DENOISE, 0);
    p.u8_at(at::FRAME_CROPPING, 1);
    p.u8_at(at::SCENE_CHANGE, 0);
    // WAI_ENCODE_TRACE set: the library's errors and warnings on its own output.
    let mut quiet = if std::env::var_os("WAI_ENCODE_TRACE").is_some() { LOG_ERROR_AND_WARNING } else { LOG_QUIET };
    // SAFETY: as above.
    unsafe {
        (vt.set_option)(s.enc, OPTION_TRACE_LEVEL, (&mut quiet as *mut c_int).cast());
        let rc = (vt.initialize_ext)(s.enc, p.0.as_ptr());
        if rc != 0 {
            return Err(EncodeError::new("encoder-refused", format!("the library refused the parameters ({rc})")));
        }
        (vt.set_option)(s.enc, OPTION_TRACE_LEVEL, (&mut quiet as *mut c_int).cast());
    }
    let name = path.file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_default();
    let identity = Identity {
        api: "software",
        encoder: format!("{name} (the library's AVC encoder: High profile, CABAC, one slice, one reference)"),
        version: format!("{}.{}.{}", v.major, v.minor, v.revision),
        hardware: false,
        device: super::cpu_name(),
        driver: super::os_release(),
        library_sha256: Some(library_sha256),
    };
    Ok(Opened { identity, session: Box::new(s) })
}
