// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: GPL-2.0-only
#[cfg(test)]
use chrony_candm::common::ChronyAddr;
use chrony_candm::common::ChronyFloat;
use chrony_candm::reply::Tracking;
#[cfg(not(test))]
use std::io;
#[cfg(not(test))]
use std::io::{Error, ErrorKind};
#[cfg(test)]
use std::net::IpAddr;
use std::time::SystemTime;

const NANOSEC_IN_SEC: u32 = 1_000_000_000;

/// Compute current root dispersion.
///
/// The root dispersion grows at the calculated error rate per second. Note that this bound may be
/// slightly inflated when compared with the one emitted by the local NTP daemon. The polling
/// of the local NTP daemon may occur slightly after its internal state has changed. In
/// particular:
///
/// - the result is inflated by the delay in getting the latest update at which the linear
///   growth is reset.
/// - the polled residual frequency is always slightly higher as the clock error correction
///   converges to the intended value.
///
/// The impact of the above is minimal, and can only make the daemon look slightly worse. This
/// more pessimistic bound is acceptable.
///
/// # Arguments
///
/// * `tracking` - The tracking information received from Chrony.
/// * `time` - The time to calculate the current root dispersion for
/// * `max_clock_error` - The assumed maximum frequency error that a system clock can gain between updates in ppm.
fn dispersion_at(tracking: Tracking, time: &SystemTime, max_clock_error: f64) -> f64 {
    let dur = time.duration_since(tracking.ref_time).unwrap();
    let dur = (dur.as_secs() as f64) + (dur.subsec_nanos() / NANOSEC_IN_SEC) as f64;
    // Calculate error rate using tracking information
    let error_rate =
        (max_clock_error + f64::from(tracking.skew_ppm) + f64::from(tracking.resid_freq_ppm))
            * 1e-6;
    f64::from(tracking.root_dispersion) + dur * error_rate
}

/// Update the root dispersion of the tracking data received from Chrony.
///
/// # Arguments
///
/// * `tracking_data` - The tracking information received from Chrony.
/// * `max_clock_error` - The assumed maximum frequency error that a system clock can gain between updates in ppm.
#[cfg(not(test))]
pub fn update_root_dispersion(
    tracking_data: Tracking,
    max_clock_error: f64,
) -> Result<Tracking, io::Error> {
    let mut tracking = tracking_data.clone();

    if tracking.ref_time > SystemTime::now() {
        return Err(Error::new(
            ErrorKind::InvalidData,
            format!(
                "Chrony's last update is in the future {:?}",
                tracking.ref_time
            ),
        ));
    };

    let now = SystemTime::now();
    tracking.root_dispersion = ChronyFloat::from(dispersion_at(tracking, &now, max_clock_error));
    Ok(tracking)
}

/// Create a mock tracking structure for testing
#[cfg(test)]
pub fn mock_tracking() -> Tracking {
    let d_chrony_float = ChronyFloat::from(f64::default());
    let d_chrony_addr = ChronyAddr::from(IpAddr::from([0; 4]));
    let d_u32 = u32::default();
    let d_u16 = u16::default();
    Tracking {
        ip_addr: d_chrony_addr,
        current_correction: ChronyFloat::from(0.000000001_f64),
        freq_ppm: d_chrony_float,
        last_offset: d_chrony_float,
        last_update_interval: d_chrony_float,
        leap_status: d_u16,
        ref_time: SystemTime::UNIX_EPOCH,
        ref_id: d_u32,
        resid_freq_ppm: d_chrony_float,
        rms_offset: d_chrony_float,
        root_delay: ChronyFloat::from(0.000000004_f64),
        root_dispersion: ChronyFloat::from(0.000000002_f64),
        skew_ppm: d_chrony_float,
        stratum: d_u16,
    }
}

#[cfg(test)]
mod tests {
    use crate::tracking::{dispersion_at, mock_tracking};
    use std::time::Duration;

    #[test]
    fn test_dispersion_at() {
        let tracking = mock_tracking();
        let max_clock_error: f64 = 1.0;
        let dur_secs: f64 = 5.0;
        // Validate the new root dispersion is correct for a 5 second duration
        let time = tracking.ref_time + Duration::from_secs(dur_secs as u64);
        let new_root_dispersion = dispersion_at(tracking, &time, max_clock_error);

        // Calculate error rate using tracking information
        let error_rate =
            (max_clock_error + f64::from(tracking.skew_ppm) + f64::from(tracking.resid_freq_ppm))
                * 1e-6;

        // Formula is root_dispersion + duration * error_rate
        let expected_root_dispersion = f64::from(tracking.root_dispersion) + dur_secs * error_rate;
        assert_eq!(new_root_dispersion, expected_root_dispersion);
    }
}


use criterion::{
    criterion_group, criterion_main,
    measurement::{Measurement, ValueFormatter},
    BenchmarkId, Criterion, SamplingMode, Throughput,
};
#[cfg(target_os = "linux")]
use std::env;
use std::time::{Duration, Instant};
use std::{
    alloc::{alloc, dealloc, Layout},
    arch::asm,
    ptr::write_bytes,
    slice::from_raw_parts_mut,
};

#[cfg(target_os = "linux")]
use perf_event::{events::Hardware, Builder, Counter};

use dcp::*;
use dcv_color_primitives as dcp;

const SRC_FORMATS: &[PixelFormat] = &[PixelFormat::Bgra, PixelFormat::Bgr];
const DST_FORMATS: &[PixelFormat] = &[
    PixelFormat::Nv12,
    PixelFormat::I420,
    PixelFormat::I444,
    PixelFormat::Rgb,
];

#[cfg(target_os = "linux")]
fn cycles_wanted() -> bool {
    if let Ok(value) = env::var("DCP_USE_CYCLES") {
        if let Ok(val) = value.parse::<i32>() {
            return val != 0;
        }
    }

    false
}

struct PerfEvent {
    #[cfg(target_os = "linux")]
    counter: Option<Counter>,
    started: Option<Instant>,
}

impl PerfEvent {
    fn clobber_memory() {
        // Set read/write memory barrier for the compiler
        unsafe {
            asm!("");
        }
    }

    #[cfg(target_os = "linux")]
    fn create_cycle_counter() -> Option<Counter> {
        Builder::new().kind(Hardware::CPU_CYCLES).build().ok()
    }

    fn new() -> PerfEvent {
        PerfEvent {
            #[cfg(target_os = "linux")]
            counter: if cycles_wanted() {
                Self::create_cycle_counter()
            } else {
                None
            },
            started: None,
        }
    }

    fn start(&mut self) {
        assert!(self.started.is_none());

        Self::clobber_memory();

        let instant = Instant::now();

        #[cfg(target_os = "linux")]
        if let Some(counter) = self.counter.as_mut() {
            if counter.reset().is_ok() && counter.enable().is_ok() {
                self.started = Some(instant);
            }

            return;
        }

        self.started = Some(instant);
    }

    fn end(&mut self) -> u64 {
        Self::clobber_memory();

        if let Some(instant) = self.started.take() {
            #[cfg(target_os = "linux")]
            if let Some(counter) = self.counter.as_mut() {
                if counter.disable().is_err() {
                    return 0;
                }

                return counter.read().unwrap_or_default();
            }

            return instant.elapsed().as_nanos() as u64;
        }

        0
    }
}

struct CycleFormatter;

impl CycleFormatter {
    fn cycles_per_element(&self, elems: f64, typical: f64, values: &mut [f64]) -> &'static str {
        let cycles_per_elem = typical / elems;
        let (denominator, unit) = if cycles_per_elem < 10f64.powi(3) {
            (1.0, " cy/px")
        } else if cycles_per_elem < 10f64.powi(6) {
            (10f64.powi(3), "Kcy/px")
        } else if cycles_per_elem < 10f64.powi(9) {
            (10f64.powi(6), "Mcy/px")
        } else {
            (10f64.powi(9), "Gcy/px")
        };

        for val in values {
            let cycles_per_elem = *val / elems;
            *val = cycles_per_elem / denominator;
        }

        unit
    }
}

impl ValueFormatter for CycleFormatter {
    fn scale_throughputs(
        &self,
        typical: f64,
        throughput: &Throughput,
        values: &mut [f64],
    ) -> &'static str {
        match *throughput {
            Throughput::Elements(elems) => self.cycles_per_element(elems as f64, typical, values),
            _ => unimplemented!(),
        }
    }

    fn scale_values(&self, ns: f64, values: &mut [f64]) -> &'static str {
        let (factor, unit) = if ns < 10f64.powi(3) {
            (10f64.powi(0), " cy   ")
        } else if ns < 10f64.powi(6) {
            (10f64.powi(-3), "Kcy   ")
        } else if ns < 10f64.powi(9) {
            (10f64.powi(-6), "Mcy   ")
        } else {
            (10f64.powi(-9), "Gcy   ")
        };

        for val in values {
            *val *= factor;
        }

        unit
    }

    fn scale_for_machines(&self, _values: &mut [f64]) -> &'static str {
        "cy"
    }
}

struct TimeFormatter;

impl TimeFormatter {
    fn elements_per_second(&self, elems: f64, typical: f64, values: &mut [f64]) -> &'static str {
        let elems_per_second = elems * (1e9 / typical);
        let (denominator, unit) = if elems_per_second < 10f64.powi(3) {
            (1.0, " px/s ")
        } else if elems_per_second < 10f64.powi(6) {
            (10f64.powi(3), "Kpx/s ")
        } else if elems_per_second < 10f64.powi(9) {
            (10f64.powi(6), "Mpx/s ")
        } else {
            (10f64.powi(9), "Gpx/s ")
        };

        for val in values {
            let elems_per_second = elems * (1e9 / *val);
            *val = elems_per_second / denominator;
        }

        unit
    }
}

impl ValueFormatter for TimeFormatter {
    fn scale_throughputs(
        &self,
        typical: f64,
        throughput: &Throughput,
        values: &mut [f64],
    ) -> &'static str {
        match *throughput {
            Throughput::Elements(elems) => self.elements_per_second(elems as f64, typical, values),
            _ => unimplemented!(),
        }
    }

    fn scale_values(&self, ns: f64, values: &mut [f64]) -> &'static str {
        let (factor, unit) = if ns < 10f64.powi(3) {
            (10f64.powi(0), "    ns")
        } else if ns < 10f64.powi(6) {
            (10f64.powi(-3), "    Âµs")
        } else if ns < 10f64.powi(9) {
            (10f64.powi(-6), "    ms")
        } else {
            (10f64.powi(-9), "    s")
        };

        for val in values {
            *val *= factor;
        }

        unit
    }

    fn scale_for_machines(&self, _values: &mut [f64]) -> &'static str {
        "ns"
    }
}

enum PerfUnit {
    #[cfg(target_os = "linux")]
    Cycle,
    Time,
}

struct Perf {
    unit: PerfUnit,
}

impl Perf {
    fn new() -> Self {
        #[cfg(target_os = "linux")]
        {
            Perf {
                unit: if cycles_wanted() && PerfEvent::create_cycle_counter().is_some() {
                    PerfUnit::Cycle
                } else {
                    PerfUnit::Time
                },
            }
        }

        #[cfg(not(target_os = "linux"))]
        {
            Perf {
                unit: PerfUnit::Time,
            }
        }
    }
}

impl Measurement for Perf {
    type Intermediate = u64;
    type Value = u64;

    fn start(&self) -> Self::Intermediate {
        unimplemented!();
    }

    fn end(&self, _i: Self::Intermediate) -> Self::Value {
        unimplemented!();
    }

    fn add(&self, v1: &Self::Value, v2: &Self::Value) -> Self::Value {
        *v1 + *v2
    }

    fn zero(&self) -> Self::Value {
        0
    }

    fn to_f64(&self, val: &Self::Value) -> f64 {
        *val as f64
    }

    fn formatter(&self) -> &dyn ValueFormatter {
        match self.unit {
            #[cfg(target_os = "linux")]
            PerfUnit::Cycle => &CycleFormatter,
            _ => &TimeFormatter,
        }
    }
}

type RawBuffer = (Layout, *mut u8, usize);

fn alloc_buffer(size: usize, seed: Option<u8>) -> RawBuffer {
    unsafe {
        let layout = Layout::from_size_align_unchecked(size, 32);
        let ptr = alloc(layout);
        if let Some(seed) = seed {
            write_bytes::<u8>(ptr, seed, size);
        }

        (layout, ptr, size)
    }
}

fn dealloc_buffer(buffer: RawBuffer) {
    unsafe { dealloc(buffer.1, buffer.0) }
}

fn slice_from_buffer(buffer: RawBuffer, seed: Option<u8>) -> &'static [u8] {
    unsafe {
        let slice: &mut [u8] = from_raw_parts_mut(buffer.1, buffer.2);
        if let Some(s) = seed {
            slice.fill(s);
        }

        slice
    }
}

fn slice_from_buffer_mut(buffer: RawBuffer, seed: Option<u8>) -> &'static mut [u8] {
    unsafe {
        let slice: &mut [u8] = from_raw_parts_mut(buffer.1, buffer.2);
        if let Some(s) = seed {
            slice.fill(s);
        }

        slice
    }
}

fn make_seed(n: u64) -> u8 {
    // Returns a value in between [64,191]
    64_u8 + (n & 0x7F) as u8
}

fn image_dimensions(i: u32, format: PixelFormat) -> (u32, u32) {
    let components = match format {
        PixelFormat::Bgr => 3,
        _ /* Bgra */ => 4,
    };

    let payload = 1 << i;
    let pixels = (payload + (components - 1)) / components;

    let width = (16f32 * ((pixels as f32) / 144_f32).sqrt()).floor() as u32;
    let width = (width + 31) & !31;

    let height = (pixels + (width - 1)) / width;
    let height = (height + 1) & !1;

    (width, height)
}

fn convert_from(
    width: u32,
    height: u32,
    from_format: PixelFormat,
    to_format: PixelFormat,
    seed: u8,
    perf: &mut PerfEvent,
) -> Option<u64> {
    let pixels = (width as usize) * (height as usize);

    let src_channels = match from_format {
        PixelFormat::Bgr => 3,
        _ /* Bgra */ => 4,
    };

    let src = alloc_buffer(src_channels * pixels, None);
    let input_buffer = slice_from_buffer(src, Some(seed));
    let input_data = &[input_buffer];

    let shift = match to_format {
        PixelFormat::Rgb | PixelFormat::I444 => 0,
        _ => 1,
    };
    let num_planes = match to_format {
        PixelFormat::Rgb | PixelFormat::Nv12 => 1,
        _ => 3,
    };
    let color_space = match to_format {
        PixelFormat::Rgb => ColorSpace::Rgb,
        _ => ColorSpace::Bt601FR,
    };

    let dst = alloc_buffer((3 * pixels) >> shift, Some(seed));
    let output_buffer = slice_from_buffer_mut(dst, None);

    let mut output_data = Vec::with_capacity(3);
    match to_format {
        PixelFormat::Rgb | PixelFormat::Nv12 => output_data.push(&mut output_buffer[..]),
        _ => {
            let (y_data, uv_data) = output_buffer.split_at_mut(pixels);
            let (u_data, v_data) = uv_data.split_at_mut(pixels >> (2 * shift));

            output_data.push(&mut *y_data);
            output_data.push(&mut *u_data);
            output_data.push(&mut *v_data);
        }
    }

    let src_format = ImageFormat {
        pixel_format: from_format,
        color_space: ColorSpace::Rgb,
        num_planes: 1,
    };
    let dst_format = ImageFormat {
        pixel_format: to_format,
        color_space,
        num_planes,
    };

    perf.start();
    let result = convert_image(
        width,
        height,
        &src_format,
        None,
        input_data,
        &dst_format,
        None,
        &mut output_data,
    );

    let elapsed = perf.end();
    if result.is_ok() {
        assert!(!output_buffer.iter().all(|&x| x == 0));
    }

    dealloc_buffer(src);
    dealloc_buffer(dst);

    result.ok().map(|_x| elapsed)
}

fn convert_to(
    width: u32,
    height: u32,
    from_format: PixelFormat,
    seed: u8,
    perf: &mut PerfEvent,
) -> Option<u64> {
    let pixels = (width as usize) * (height as usize);

    let shift = match from_format {
        PixelFormat::Rgb | PixelFormat::I444 => 0,
        _ => 1,
    };
    let num_planes = match from_format {
        PixelFormat::Rgb | PixelFormat::Nv12 => 1,
        _ => 3,
    };
    let color_space = match from_format {
        PixelFormat::Rgb => ColorSpace::Rgb,
        _ => ColorSpace::Bt601FR,
    };

    let src = alloc_buffer((3 * pixels) >> shift, None);
    let input_buffer = slice_from_buffer(src, Some(seed));

    let mut input_data = Vec::with_capacity(3);
    match from_format {
        PixelFormat::Rgb | PixelFormat::Nv12 => input_data.push(input_buffer),
        _ => {
            let (y_data, uv_data) = input_buffer.split_at(pixels);
            let (u_data, v_data) = uv_data.split_at(pixels >> (2 * shift));

            input_data.push(y_data);
            input_data.push(u_data);
            input_data.push(v_data);
        }
    }

    let dst = alloc_buffer(4 * pixels, Some(seed));
    let output_buffer = slice_from_buffer_mut(dst, None);
    let output_data: &mut [&mut [u8]] = &mut [&mut output_buffer[..]];

    let src_format = ImageFormat {
        pixel_format: from_format,
        color_space,
        num_planes,
    };
    let dst_format = ImageFormat {
        pixel_format: PixelFormat::Bgra,
        color_space: ColorSpace::Rgb,
        num_planes: 1,
    };

    perf.start();
    let result = convert_image(
        width,
        height,
        &src_format,
        None,
        &input_data,
        &dst_format,
        None,
        output_data,
    );

    let elapsed = perf.end();
    if result.is_ok() {
        assert!(!output_buffer.iter().all(|&x| x == 0));
    }

    dealloc_buffer(src);
    dealloc_buffer(dst);

    result.ok().map(|_x| elapsed)
}

fn bench(c: &mut Criterion<Perf>) {
    initialize();

    let mut perf = PerfEvent::new();

    for src_format in SRC_FORMATS {
        let src_format_name = format!("{src_format}");
        let mut group = c.benchmark_group(format!("{src_format_name:<4}"));
        group.warm_up_time(Duration::from_secs(1));
        group.measurement_time(Duration::from_secs(8));
        group.sampling_mode(SamplingMode::Flat);

        for i in 18..=26 {
            let (width, height) = image_dimensions(i, *src_format);

            group.throughput(Throughput::Elements((width as u64) * (height as u64)));
            for dst_format in DST_FORMATS {
                let dst_format_name = format!("{dst_format}");
                group.bench_with_input(
                    BenchmarkId::new(format!("{dst_format_name:<4} {width:>4}x{height:<4}"), i),
                    &i,
                    |b, _i| {
                        b.iter_custom(|iters| {
                            let mut total = 0;
                            for j in 0..iters {
                                total += convert_from(
                                    width,
                                    height,
                                    *src_format,
                                    *dst_format,
                                    make_seed(j),
                                    &mut perf,
                                )
                                .expect("Benchmark iteration failed");
                            }

                            total
                        });
                    },
                );
            }
        }

        group.finish();
    }

    for src_format in DST_FORMATS {
        let src_format_name = format!("{src_format}");
        let mut group = c.benchmark_group(format!("{src_format_name:<4}"));
        group.warm_up_time(Duration::from_secs(1));
        group.measurement_time(Duration::from_secs(8));
        group.sampling_mode(SamplingMode::Flat);

        for i in 18..=26 {
            let dst_format = PixelFormat::Bgra;
            let dst_format_name = format!("{dst_format}");
            let (width, height) = image_dimensions(i, dst_format);

            group.throughput(Throughput::Elements((width as u64) * (height as u64)));
            group.bench_with_input(
                BenchmarkId::new(format!("{dst_format_name:<4} {width:>4}x{height:<4}"), i),
                &i,
                |b, _i| {
                    b.iter_custom(|iters| {
                        let mut total = 0;
                        for j in 0..iters {
                            total +=
                                convert_to(width, height, *src_format, make_seed(j), &mut perf)
                                    .expect("Benchmark iteration failed");
                        }

                        total
                    });
                },
            );
        }

        group.finish();
    }
}

criterion_group! {
    name = benches;
    config = Criterion::default().with_measurement(Perf::new());
    targets = bench
}

criterion_main!(benches);


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0

// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify,
// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

/// An enumeration of supported color models.
///
/// It includes:
/// * Colorimetry
/// * Gamma
/// * Range (headroom / footroom)
/// * Primaries
#[derive(Copy, Clone)]
#[repr(C)]
pub enum ColorSpace {
    /// Gamma-corrected R'G'B'.
    /// The gamma is the one defined in ITU-R Recommendation BT.709-6 page 3, item 1.2
    /// The relationship between gamma-corrected component (C') and its linear value (C)
    /// is the following one:
    /// C' = 4.5 * C                      if C < 0.018,
    ///      1.099 * pow(C, 0.45) - 0.099 otherwise.
    Rgb,
    /// YCbCr, ITU-R Recommendation BT.601 (standard video system)
    Bt601,
    /// YCbCr, ITU-R Recommendation BT.709 (CSC systems)
    Bt709,
    /// YCbCr, BT.601 (full range)
    Bt601FR,
    /// YCbCr, BT.709 (full range)
    Bt709FR,
}

impl ColorSpace {
    #[deprecated(since = "0.5.0", note = "Lrgb is the same as Rgb. Use ColorSpace::Rgb")]
    #[allow(non_upper_case_globals)]
    /// Deprecated. Same as `ColorSpace::Rgb`
    pub const Lrgb: ColorSpace = ColorSpace::Rgb;
}

#[cfg(not(tarpaulin_include))]
impl std::fmt::Display for ColorSpace {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            ColorSpace::Rgb => write!(f, "rgb"),
            ColorSpace::Bt601 => write!(f, "bt-601"),
            ColorSpace::Bt709 => write!(f, "bt-709"),
            ColorSpace::Bt601FR => write!(f, "bt-601-fr"),
            ColorSpace::Bt709FR => write!(f, "bt-709-fr"),
        }
    }
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0

// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify,
// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#[cfg(target_arch = "x86")]
use core::arch::x86::__cpuid;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::__cpuid;

#[derive(Debug)]
pub enum CpuManufacturer {
    Unknown,
    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
    Intel,
    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
    Amd,
}

#[derive(Debug)]
pub enum InstructionSet {
    X86,
    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
    Sse2,
    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
    Avx2,
}

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
const fn four_cc(a: u8, b: u8, c: u8, d: u8) -> u32 {
    ((d as u32) << 24) | ((c as u32) << 16) | ((b as u32) << 8) | (a as u32)
}

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn cpuid(functionnumber: u32, output: &mut [u32; 4]) {
    unsafe {
        let result = __cpuid(functionnumber);
        output[0] = result.eax;
        output[1] = result.ebx;
        output[2] = result.ecx;
        output[3] = result.edx;
    }
}

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn compare_cpu_manufacturer(features: &[u32; 4], name: &[u8; 12]) -> u32 {
    features[1].wrapping_sub(four_cc(name[0], name[1], name[2], name[3]))
        | features[3].wrapping_sub(four_cc(name[4], name[5], name[6], name[7]))
        | features[2].wrapping_sub(four_cc(name[8], name[9], name[10], name[11]))
}

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub fn get() -> (CpuManufacturer, InstructionSet) {
    let mut manufacturer = CpuManufacturer::Unknown;
    let mut set = InstructionSet::X86;

    let features = &mut [0; 4];
    cpuid(0, features);

    if features[0] != 0 {
        if compare_cpu_manufacturer(features, b"GenuineIntel") == 0 {
            manufacturer = CpuManufacturer::Intel;
        } else if (compare_cpu_manufacturer(features, b"AuthenticAMD") == 0)
            | (compare_cpu_manufacturer(features, b"AMDisbetter!") == 0)
        {
            manufacturer = CpuManufacturer::Amd;
        }

        // This ensures we always use hardware intrinsics and we do not use software emulation
        cpuid(1, features);
        if (features[3] & (1 << 26)) != 0 {
            // On AMD cpus, all encode/decode using avx2 have worse performance than sse2 ones
            // For now, disable the avx2 path even if supported.
            // See https://en.wikipedia.org/wiki/CPUID for additional details
            match manufacturer {
                CpuManufacturer::Amd => features[1] = 0,
                _ => cpuid(7, features),
            }

            set = if (features[1] & (1 << 5)) == 0 {
                InstructionSet::Sse2
            } else {
                InstructionSet::Avx2
            };
        }
    }

    (manufacturer, set)
}

#[cfg(all(not(target_arch = "x86"), not(target_arch = "x86_64")))]
pub fn get() -> (CpuManufacturer, InstructionSet) {
    (CpuManufacturer::Unknown, InstructionSet::X86)
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0

// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify,
// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#![allow(clippy::crate_in_macro_def)]

use crate::color_space::ColorSpace;
use crate::pixel_format::PixelFormat;
use crate::static_assert;

#[doc(hidden)]
#[macro_export]
macro_rules! rgb_to_yuv_converter {
    ($src_pf:ident, $dst_pf:ident, $dst_cs:ident) => {
        paste::paste! {
            pub fn [<$src_pf:lower _ $dst_pf:lower _ $dst_cs:lower>](
                width: u32,
                height: u32,
                last_src_plane: u32,
                src_strides: &[usize],
                src_buffers: &[&[u8]],
                last_dst_plane: u32,
                dst_strides: &[usize],
                dst_buffers: &mut [&mut [u8]],
            ) -> bool {
                [<rgb _ $dst_pf:lower>]::<{ Sampler::$src_pf as usize }, { crate::pixel_format::PixelFormat::depth(crate::pixel_format::PixelFormat::$src_pf) }, { Colorimetry::$dst_cs as usize }>(
                    width,
                    height,
                    last_src_plane as usize,
                    src_strides,
                    src_buffers,
                    last_dst_plane as usize,
                    dst_strides,
                    dst_buffers,
                )
            }
        }
    };
}

#[doc(hidden)]
#[macro_export]
macro_rules! yuv_to_rgb_converter {
    ($src_pf:ident, $src_cs:ident, $dst_pf:ident) => {
        paste::paste! {
            pub fn [<$src_pf:lower _ $src_cs:lower _ $dst_pf:lower>] (
                width: u32,
                height: u32,
                last_src_plane: u32,
                src_strides: &[usize],
                src_buffers: &[&[u8]],
                last_dst_plane: u32,
                dst_strides: &[usize],
                dst_buffers: &mut [&mut [u8]],
            ) -> bool {
                [<$src_pf:lower _rgb>]::<{ Colorimetry::$src_cs as usize }, { crate::pixel_format::PixelFormat::depth(crate::pixel_format::PixelFormat::$dst_pf) }>(
                    width,
                    height,
                    last_src_plane as usize,
                    src_strides,
                    src_buffers,
                    last_dst_plane as usize,
                    dst_strides,
                    dst_buffers,
                )
            }
        }
    };
}

#[cfg(not(tarpaulin_include))]
const fn enum_count(lo: u32, hi: u32) -> u32 {
    hi - lo + 1
}

#[cfg(not(tarpaulin_include))]
const fn upper_power_of_two(x: u32) -> u32 {
    1 << (32 - (x - 1).leading_zeros())
}

const LO_RGB_PIXEL_FORMAT: u32 = PixelFormat::Argb as u32;
const HI_RGB_PIXEL_FORMAT: u32 = PixelFormat::Rgb as u32;
const LO_YUV_PIXEL_FORMAT: u32 = PixelFormat::I444 as u32;
const HI_YUV_PIXEL_FORMAT: u32 = PixelFormat::Nv12 as u32;
static_assert!(HI_RGB_PIXEL_FORMAT == LO_YUV_PIXEL_FORMAT - 1);

const LO_RGB_COLOR_SPACE: u32 = ColorSpace::Rgb as u32;
const HI_RGB_COLOR_SPACE: u32 = ColorSpace::Rgb as u32;
const LO_YUV_COLOR_SPACE: u32 = ColorSpace::Bt601 as u32;
const HI_YUV_COLOR_SPACE: u32 = ColorSpace::Bt709FR as u32;
static_assert!(HI_RGB_COLOR_SPACE == LO_YUV_COLOR_SPACE - 1);

const RGB_PIXEL_FORMAT_COUNT: u32 = enum_count(LO_RGB_PIXEL_FORMAT, HI_RGB_PIXEL_FORMAT);
const RGB_COLOR_SPACE_COUNT: u32 = enum_count(LO_RGB_COLOR_SPACE, HI_RGB_COLOR_SPACE);
const RGB_COUNT: u32 = RGB_PIXEL_FORMAT_COUNT * RGB_COLOR_SPACE_COUNT;
const YUV_PIXEL_FORMAT_COUNT: u32 = enum_count(LO_YUV_PIXEL_FORMAT, HI_YUV_PIXEL_FORMAT);
const YUV_COLOR_SPACE_COUNT: u32 = enum_count(LO_YUV_COLOR_SPACE, HI_YUV_COLOR_SPACE);

const ROWS: u32 = RGB_COUNT + (YUV_PIXEL_FORMAT_COUNT * YUV_COLOR_SPACE_COUNT);
const COLUMNS: u32 = upper_power_of_two(ROWS);

fn select_mode(a: u32, b: u32, cond: bool) -> u32 {
    let c = u32::from(cond);
    c * a + (1 - c) * b
}

pub const TABLE_SIZE: usize = (ROWS * COLUMNS) as usize;

pub fn get_pixel_format_mode(pixel_format: u32) -> bool {
    pixel_format <= HI_RGB_PIXEL_FORMAT
}

pub fn get_color_space_mode(color_space: u32) -> bool {
    static_assert!(HI_RGB_COLOR_SPACE == LO_RGB_COLOR_SPACE);
    color_space == HI_RGB_COLOR_SPACE
}

pub fn get_image_index(pixel_format: u32, color_space: u32, pixel_format_mode: bool) -> u32 {
    select_mode(
        // This never overflows
        RGB_PIXEL_FORMAT_COUNT * (color_space - LO_RGB_COLOR_SPACE)
            + (pixel_format - LO_RGB_PIXEL_FORMAT),
        // When pixel_format > HI_RGB_PIXEL_FORMAT, this is allowed to be garbage
        // because the value above will be selected
        RGB_COUNT
            .wrapping_add(
                YUV_PIXEL_FORMAT_COUNT.wrapping_mul(color_space.wrapping_sub(LO_YUV_COLOR_SPACE)),
            )
            .wrapping_add(pixel_format.wrapping_sub(LO_YUV_PIXEL_FORMAT)),
        pixel_format_mode,
    )
}

pub fn get_index(src_index: u32, dst_index: u32) -> usize {
    (src_index * COLUMNS + dst_index) as usize
}

#[cfg(not(feature = "test_instruction_sets"))]
#[cfg(not(tarpaulin_include))]
#[inline(never)]
pub fn is_pixel_format_valid(pixel_format: u32) -> bool {
    pixel_format.wrapping_sub(LO_RGB_PIXEL_FORMAT)
        <= HI_YUV_PIXEL_FORMAT.wrapping_sub(LO_RGB_PIXEL_FORMAT)
}

#[cfg(not(feature = "test_instruction_sets"))]
#[cfg(not(tarpaulin_include))]
#[inline(never)]
pub fn is_color_space_valid(color_space: u32) -> bool {
    color_space.wrapping_sub(LO_RGB_COLOR_SPACE)
        <= HI_YUV_COLOR_SPACE.wrapping_sub(LO_RGB_COLOR_SPACE)
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0

// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify,
// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#![warn(missing_docs)]
#![deny(trivial_casts)]
#![deny(trivial_numeric_casts)]
#![deny(unstable_features)]
#![deny(unused_import_braces)]
#![deny(
    clippy::complexity,
    clippy::correctness,
    clippy::perf,
    clippy::style,
    clippy::pedantic
)]
#![allow(
    clippy::too_many_arguments, // API design
    clippy::missing_safety_doc, // Until we add them...
    clippy::similar_names, // This requires effort to ensure
    // Due to vzeroupper use, compiler does not inline intrinsics
    // but rather creates a function for each one that wraps the operation followed
    // by vzeroupper().
    // This is detrimental to performance
    clippy::inline_always,
)]

//! DCV color primitives is a library to perform image color model conversion.
//!
//! It is able to convert the following pixel formats:
//!
//! | Source pixel format  | Destination pixel formats  |
//! | -------------------- | -------------------------- |
//! | ARGB                 | I420, I444, NV12           |
//! | BGR                  | I420, I444, NV12, RGB      |
//! | BGRA                 | I420, I444, NV12, RGB      |
//! | I420                 | BGRA                       |
//! | I444                 | BGRA                       |
//! | NV12                 | BGRA, RGB                  |
//! | RGB                  | BGRA                       |
//!
//! The supported color models are:
//! * ycbcr, ITU-R Recommendation BT.601 (standard video system)
//! * ycbcr, ITU-R Recommendation BT.709 (CSC systems)
//!
//! Both standard range (0-235) and full range (0-255) are supported.
//!
//! # Examples
//!
//! Initialize the library:
//! ```
//! use dcv_color_primitives as dcp;
//! dcp::initialize();
//! ```
//!
//! Convert an image from bgra to nv12 (single plane) format, with Bt601 color space:
//! ```
//! use dcv_color_primitives as dcp;
//! use dcp::{convert_image, ColorSpace, ImageFormat, PixelFormat};
//!
//! fn convert() {
//!     dcp::initialize();
//!
//!     const WIDTH: u32 = 640;
//!     const HEIGHT: u32 = 480;
//!
//!     let src_data = Box::new([0u8; 4 * (WIDTH as usize) * (HEIGHT as usize)]);
//!     let mut dst_data = Box::new([0u8; 3 * (WIDTH as usize) * (HEIGHT as usize) / 2]);
//!
//!     let src_format = ImageFormat {
//!         pixel_format: PixelFormat::Bgra,
//!         color_space: ColorSpace::Rgb,
//!         num_planes: 1,
//!     };
//!
//!     let dst_format = ImageFormat {
//!         pixel_format: PixelFormat::Nv12,
//!         color_space: ColorSpace::Bt601,
//!         num_planes: 1,
//!     };
//!
//!     convert_image(
//!         WIDTH,
//!         HEIGHT,
//!         &src_format,
//!         None,
//!         &[&*src_data],
//!         &dst_format,
//!         None,
//!         &mut [&mut *dst_data],
//!     );
//! }
//! ```
//!
//! Handle conversion errors:
//! ```
//! use dcv_color_primitives as dcp;
//! use dcp::{convert_image, ColorSpace, ImageFormat, PixelFormat};
//! use std::error;
//!
//! fn convert() -> Result<(), Box<dyn error::Error>> {
//!     dcp::initialize();
//!
//!     const WIDTH: u32 = 640;
//!     const HEIGHT: u32 = 480;
//!
//!     let src_data = Box::new([0u8; 4 * (WIDTH as usize) * (HEIGHT as usize)]);
//!     let mut dst_data = Box::new([0u8; 3 * (WIDTH as usize) * (HEIGHT as usize) / 2]);
//!
//!     let src_format = ImageFormat {
//!         pixel_format: PixelFormat::Bgra,
//!         color_space: ColorSpace::Bt709,
//!         num_planes: 1,
//!     };
//!
//!     let dst_format = ImageFormat {
//!         pixel_format: PixelFormat::Nv12,
//!         color_space: ColorSpace::Bt601,
//!         num_planes: 1,
//!     };
//!
//!     convert_image(
//!         WIDTH,
//!         HEIGHT,
//!         &src_format,
//!         None,
//!         &[&*src_data],
//!         &dst_format,
//!         None,
//!         &mut [&mut *dst_data],
//!     )?;
//!
//!     Ok(())
//! }
//! ```
//!
//! Compute how many bytes are needed to store and image of a given format and size:
//! ```
//! use dcv_color_primitives as dcp;
//! use dcp::{get_buffers_size, ColorSpace, ImageFormat, PixelFormat};
//! use std::error;
//!
//! fn compute_size() -> Result<(), Box<dyn error::Error>> {
//!     dcp::initialize();
//!
//!     const WIDTH: u32 = 640;
//!     const HEIGHT: u32 = 480;
//!     const NUM_PLANES: u32 = 1;
//!
//!     let format = ImageFormat {
//!         pixel_format: PixelFormat::Bgra,
//!         color_space: ColorSpace::Rgb,
//!         num_planes: NUM_PLANES,
//!     };
//!
//!     let sizes: &mut [usize] = &mut [0usize; NUM_PLANES as usize];
//!     get_buffers_size(WIDTH, HEIGHT, &format, None, sizes)?;
//!
//!     let buffer: Vec<_> = vec![0u8; sizes[0]];
//!
//!     // Do something with buffer
//!     // --snip--
//!
//!     Ok(())
//! }
//! ```
//!
//! Provide image planes to handle data scattered in multiple buffers that are not
//! necessarily contiguous:
//! ```
//! use dcv_color_primitives as dcp;
//! use dcp::{convert_image, get_buffers_size, ColorSpace, ImageFormat, PixelFormat};
//! use std::error;
//!
//! fn convert() -> Result<(), Box<dyn error::Error>> {
//!     dcp::initialize();
//!
//!     const WIDTH: u32 = 640;
//!     const HEIGHT: u32 = 480;
//!     const NUM_SRC_PLANES: u32 = 2;
//!     const NUM_DST_PLANES: u32 = 1;
//!
//!     let src_format = ImageFormat {
//!         pixel_format: PixelFormat::Nv12,
//!         color_space: ColorSpace::Bt709,
//!         num_planes: NUM_SRC_PLANES,
//!     };
//!
//!     let src_sizes: &mut [usize] = &mut [0usize; NUM_SRC_PLANES as usize];
//!     get_buffers_size(WIDTH, HEIGHT, &src_format, None, src_sizes)?;
//!
//!     let src_y: Vec<_> = vec![0u8; src_sizes[0]];
//!     let src_uv: Vec<_> = vec![0u8; src_sizes[1]];
//!
//!     let dst_format = ImageFormat {
//!         pixel_format: PixelFormat::Bgra,
//!         color_space: ColorSpace::Rgb,
//!         num_planes: NUM_DST_PLANES,
//!     };
//!
//!     let dst_sizes: &mut [usize] = &mut [0usize; NUM_DST_PLANES as usize];
//!     get_buffers_size(WIDTH, HEIGHT, &dst_format, None, dst_sizes)?;
//!
//!     let mut dst_rgba: Vec<_> = vec![0u8; dst_sizes[0]];
//!
//!     convert_image(
//!         WIDTH,
//!         HEIGHT,
//!         &src_format,
//!         None,
//!         &[&src_y[..], &src_uv[..]],
//!         &dst_format,
//!         None,
//!         &mut [&mut dst_rgba[..]],
//!     )?;
//!
//!     Ok(())
//! }
//! ```
//!
//! Provide image strides to convert data which is not tightly packed:
//! ```
//! use dcv_color_primitives as dcp;
//! use dcp::{convert_image, get_buffers_size, ColorSpace, ImageFormat, PixelFormat};
//! use std::error;
//!
//! fn convert() -> Result<(), Box<dyn error::Error>> {
//!     dcp::initialize();
//!
//!     const WIDTH: u32 = 640;
//!     const HEIGHT: u32 = 480;
//!     const NUM_SRC_PLANES: u32 = 1;
//!     const NUM_DST_PLANES: u32 = 2;
//!     const RGB_STRIDE: usize = 4 * (((3 * (WIDTH as usize)) + 3) / 4);
//!
//!     let src_format = ImageFormat {
//!         pixel_format: PixelFormat::Bgr,
//!         color_space: ColorSpace::Rgb,
//!         num_planes: NUM_SRC_PLANES,
//!     };
//!
//!     let src_strides: &[usize] = &[RGB_STRIDE];
//!
//!     let src_sizes: &mut [usize] = &mut [0usize; NUM_SRC_PLANES as usize];
//!     get_buffers_size(WIDTH, HEIGHT, &src_format, Some(src_strides), src_sizes)?;
//!
//!     let src_rgba: Vec<_> = vec![0u8; src_sizes[0]];
//!
//!     let dst_format = ImageFormat {
//!         pixel_format: PixelFormat::Nv12,
//!         color_space: ColorSpace::Bt709,
//!         num_planes: NUM_DST_PLANES,
//!     };
//!
//!     let dst_sizes: &mut [usize] = &mut [0usize; NUM_DST_PLANES as usize];
//!     get_buffers_size(WIDTH, HEIGHT, &dst_format, None, dst_sizes)?;
//!
//!     let mut dst_y: Vec<_> = vec![0u8; dst_sizes[0]];
//!     let mut dst_uv: Vec<_> = vec![0u8; dst_sizes[1]];
//!
//!     convert_image(
//!         WIDTH,
//!         HEIGHT,
//!         &src_format,
//!         Some(src_strides),
//!         &[&src_rgba[..]],
//!         &dst_format,
//!         None,
//!         &mut [&mut dst_y[..], &mut dst_uv[..]],
//!     )?;
//!
//!     Ok(())
//! }
//! ```
mod color_space;
mod convert_image;
mod cpu_info;
mod dispatcher;
mod pixel_format;
mod static_assert;

use cpu_info::{CpuManufacturer, InstructionSet};
use paste::paste;
use std::error;
use std::fmt;

pub use color_space::ColorSpace;
pub use pixel_format::{PixelFormat, STRIDE_AUTO};

/// An enumeration of errors.
#[derive(Debug)]
#[repr(C)]
pub enum ErrorKind {
    /// [`initialize`] was never called
    ///
    /// [`initialize`]: ./fn.initialize.html
    NotInitialized,
    /// One or more parameters have invalid values for the called function
    InvalidValue,
    /// The combination of parameters is unsupported for the called function
    InvalidOperation,
    /// Not enough data was provided to the called function. Typically, provided
    /// arrays are not correctly sized
    NotEnoughData,
}

#[cfg(not(tarpaulin_include))]
impl fmt::Display for ErrorKind {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            ErrorKind::NotInitialized => {
                write!(f, "Library was not initialized by calling initialize()")
            }
            ErrorKind::InvalidValue => write!(
                f,
                "One or more parameters have not legal values for the command"
            ),
            ErrorKind::InvalidOperation => write!(
                f,
                "The combination of parameters is not legal for the command"
            ),
            ErrorKind::NotEnoughData => write!(f, "Not enough data provided"),
        }
    }
}

#[cfg(not(tarpaulin_include))]
impl error::Error for ErrorKind {
    fn cause(&self) -> Option<&dyn error::Error> {
        None
    }
}

/// Describes how the image data is laid out in memory and its color space.
///
/// # Note
/// Not all combinations of pixel format, color space and number of planes
/// describe a valid image format.
///
/// Each pixel format has one or more compatible color spaces:
///
/// pixel format        | color space
/// --------------------|--------------------------------------
/// `PixelFormat::Argb` | `ColorSpace::Rgb`
/// `PixelFormat::Bgra` | `ColorSpace::Rgb`
/// `PixelFormat::Bgr`  | `ColorSpace::Rgb`
/// `PixelFormat::Rgba` | `ColorSpace::Rgb`
/// `PixelFormat::Rgb`  | `ColorSpace::Rgb`
/// `PixelFormat::I444` | `ColorSpace::Bt601(FR)`, `ColorSpace::Bt709(FR)`
/// `PixelFormat::I422` | `ColorSpace::Bt601(FR)`, `ColorSpace::Bt709(FR)`
/// `PixelFormat::I420` | `ColorSpace::Bt601(FR)`, `ColorSpace::Bt709(FR)`
/// `PixelFormat::Nv12` | `ColorSpace::Bt601(FR)`, `ColorSpace::Bt709(FR)`
///
/// Some pixel formats might impose additional restrictions on the accepted number of
/// planes and the image size:
///
/// pixel format        | subsampling | w   | h   | #planes | #1     | #2     | #3
/// --------------------|:-----------:|:---:|:---:|:-------:|:------:|:------:|:-------:
/// `PixelFormat::Argb` | 4:4:4       |     |     | 1       | argb:4 |        |
/// `PixelFormat::Bgra` | 4:4:4       |     |     | 1       | bgra:4 |        |
/// `PixelFormat::Bgr`  | 4:4:4       |     |     | 1       | bgr:3  |        |
/// `PixelFormat::Rgba` | 4:4:4       |     |     | 1       | rgba:4 |        |
/// `PixelFormat::Rgb`  | 4:4:4       |     |     | 1       | rgb:3  |        |
/// `PixelFormat::I444` | 4:4:4       |     |     | 3       | y:1    | u:1    | v:1
/// `PixelFormat::I422` | 4:2:2       |  2  |     | 1, 3    | y:1    | u:1/2  | v:1/2
/// `PixelFormat::I420` | 4:2:0       |  2  |  2  | 3       | y:1    | u:1/4  | v:1/4
/// `PixelFormat::Nv12` | 4:2:0       |  2  |  2  | 1, 2    | y:1    | uv:1/2 |
///
/// The values reported in columns `w` and `h`, when specified, indicate that the described
/// image should have width and height that are multiples of the specified values
#[repr(C)]
pub struct ImageFormat {
    /// Pixel format
    pub pixel_format: PixelFormat,
    /// Color space
    pub color_space: ColorSpace,
    /// Number of planes
    pub num_planes: u32,
}

type ConvertDispatcher =
    fn(u32, u32, u32, &[usize], &[&[u8]], u32, &[usize], &mut [&mut [u8]]) -> bool;

macro_rules! rgb_to_yuv {
    ($conv:expr, $set:ident, $src_pf:ident, $dst_pf:ident, $dst_cs:ident) => {
        paste! {
            $conv[dispatcher::get_index(
                dispatcher::get_image_index(
                    PixelFormat::$src_pf as u32,
                    ColorSpace::Rgb as u32,
                    dispatcher::get_pixel_format_mode(PixelFormat::$src_pf as u32),
                ),
                dispatcher::get_image_index(
                    PixelFormat::$dst_pf as u32,
                    ColorSpace::$dst_cs as u32,
                    dispatcher::get_pixel_format_mode(PixelFormat::$dst_pf as u32),
                ),
            )] = Some(convert_image::$set::[<$src_pf:lower _ $dst_pf:lower _ $dst_cs:lower>])
        }
    };
}

macro_rules! yuv_to_rgb {
    ($conv:expr, $set:ident, $src_pf:ident, $src_cs:ident, $dst_pf:ident) => {
        paste! {
            $conv[dispatcher::get_index(
                dispatcher::get_image_index(
                    PixelFormat::$src_pf as u32,
                    ColorSpace::$src_cs as u32,
                    dispatcher::get_pixel_format_mode(PixelFormat::$src_pf as u32),
                ),
                dispatcher::get_image_index(
                    PixelFormat::$dst_pf as u32,
                    ColorSpace::Rgb as u32,
                    dispatcher::get_pixel_format_mode(PixelFormat::$dst_pf as u32),
                ),
            )] = Some(convert_image::$set::[<$src_pf:lower _ $src_cs:lower _ $dst_pf:lower>])
        }
    };
}

macro_rules! rgb_to_rgb {
    ($conv:expr, $set:ident, $src_pf:ident, $dst_pf:ident) => {
        paste! {
            $conv[dispatcher::get_index(
                dispatcher::get_image_index(
                    PixelFormat::$src_pf as u32,
                    ColorSpace::Rgb as u32,
                    dispatcher::get_pixel_format_mode(PixelFormat::$src_pf as u32),
                ),
                dispatcher::get_image_index(
                    PixelFormat::$dst_pf as u32,
                    ColorSpace::Rgb as u32,
                    dispatcher::get_pixel_format_mode(PixelFormat::$dst_pf as u32),
                ),
            )] = Some(convert_image::$set::[<$src_pf:lower _ $dst_pf:lower>])
        }
    };
}

macro_rules! set_dispatch_table {
    ($conv:expr, $set:ident) => {
        rgb_to_rgb!($conv, $set, Bgr, Rgb);
        rgb_to_rgb!($conv, $set, Bgra, Rgb);
        rgb_to_rgb!($conv, $set, Rgb, Bgra);
        rgb_to_yuv!($conv, $set, Argb, I420, Bt601);
        rgb_to_yuv!($conv, $set, Argb, I420, Bt601FR);
        rgb_to_yuv!($conv, $set, Argb, I420, Bt709);
        rgb_to_yuv!($conv, $set, Argb, I420, Bt709FR);
        rgb_to_yuv!($conv, $set, Argb, I444, Bt601);
        rgb_to_yuv!($conv, $set, Argb, I444, Bt601FR);
        rgb_to_yuv!($conv, $set, Argb, I444, Bt709);
        rgb_to_yuv!($conv, $set, Argb, I444, Bt709FR);
        rgb_to_yuv!($conv, $set, Argb, Nv12, Bt601);
        rgb_to_yuv!($conv, $set, Argb, Nv12, Bt601FR);
        rgb_to_yuv!($conv, $set, Argb, Nv12, Bt709);
        rgb_to_yuv!($conv, $set, Argb, Nv12, Bt709FR);
        rgb_to_yuv!($conv, $set, Bgr, I420, Bt601);
        rgb_to_yuv!($conv, $set, Bgr, I420, Bt601FR);
        rgb_to_yuv!($conv, $set, Bgr, I420, Bt709);
        rgb_to_yuv!($conv, $set, Bgr, I420, Bt709FR);
        rgb_to_yuv!($conv, $set, Bgr, I444, Bt601);
        rgb_to_yuv!($conv, $set, Bgr, I444, Bt601FR);
        rgb_to_yuv!($conv, $set, Bgr, I444, Bt709);
        rgb_to_yuv!($conv, $set, Bgr, I444, Bt709FR);
        rgb_to_yuv!($conv, $set, Bgr, Nv12, Bt601);
        rgb_to_yuv!($conv, $set, Bgr, Nv12, Bt601FR);
        rgb_to_yuv!($conv, $set, Bgr, Nv12, Bt709);
        rgb_to_yuv!($conv, $set, Bgr, Nv12, Bt709FR);
        rgb_to_yuv!($conv, $set, Bgra, I420, Bt601);
        rgb_to_yuv!($conv, $set, Bgra, I420, Bt601FR);
        rgb_to_yuv!($conv, $set, Bgra, I420, Bt709);
        rgb_to_yuv!($conv, $set, Bgra, I420, Bt709FR);
        rgb_to_yuv!($conv, $set, Bgra, I444, Bt601);
        rgb_to_yuv!($conv, $set, Bgra, I444, Bt601FR);
        rgb_to_yuv!($conv, $set, Bgra, I444, Bt709);
        rgb_to_yuv!($conv, $set, Bgra, I444, Bt709FR);
        rgb_to_yuv!($conv, $set, Bgra, Nv12, Bt601);
        rgb_to_yuv!($conv, $set, Bgra, Nv12, Bt601FR);
        rgb_to_yuv!($conv, $set, Bgra, Nv12, Bt709);
        rgb_to_yuv!($conv, $set, Bgra, Nv12, Bt709FR);
        yuv_to_rgb!($conv, $set, I420, Bt601, Bgra);
        yuv_to_rgb!($conv, $set, I420, Bt601FR, Bgra);
        yuv_to_rgb!($conv, $set, I420, Bt709, Bgra);
        yuv_to_rgb!($conv, $set, I420, Bt709FR, Bgra);
        yuv_to_rgb!($conv, $set, I444, Bt601, Bgra);
        yuv_to_rgb!($conv, $set, I444, Bt601FR, Bgra);
        yuv_to_rgb!($conv, $set, I444, Bt709, Bgra);
        yuv_to_rgb!($conv, $set, I444, Bt709FR, Bgra);
        yuv_to_rgb!($conv, $set, Nv12, Bt601, Bgra);
        yuv_to_rgb!($conv, $set, Nv12, Bt601FR, Bgra);
        yuv_to_rgb!($conv, $set, Nv12, Bt709, Bgra);
        yuv_to_rgb!($conv, $set, Nv12, Bt709FR, Bgra);
        yuv_to_rgb!($conv, $set, Nv12, Bt601, Rgb);
        yuv_to_rgb!($conv, $set, Nv12, Bt601FR, Rgb);
        yuv_to_rgb!($conv, $set, Nv12, Bt709, Rgb);
        yuv_to_rgb!($conv, $set, Nv12, Bt709FR, Rgb);
    };
}

struct GlobalState {
    init: bool,
    manufacturer: CpuManufacturer,
    set: InstructionSet,
    converters: [Option<ConvertDispatcher>; dispatcher::TABLE_SIZE],
}

static mut GLOBAL_STATE: GlobalState = GlobalState {
    init: false,
    manufacturer: CpuManufacturer::Unknown,
    set: InstructionSet::X86,
    converters: [None; dispatcher::TABLE_SIZE],
};

#[cfg(not(tarpaulin_include))]
fn initialize_global_state(manufacturer: CpuManufacturer, set: InstructionSet) {
    unsafe {
        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
        match set {
            InstructionSet::X86 => {
                set_dispatch_table!(GLOBAL_STATE.converters, x86);
            }
            InstructionSet::Sse2 => {
                set_dispatch_table!(GLOBAL_STATE.converters, sse2);
            }
            InstructionSet::Avx2 => {
                set_dispatch_table!(GLOBAL_STATE.converters, avx2);
            }
        }

        // This is the default for arm and wasm32 targets
        #[cfg(all(not(target_arch = "x86"), not(target_arch = "x86_64")))]
        {
            set_dispatch_table!(GLOBAL_STATE.converters, x86);
        }

        GLOBAL_STATE.manufacturer = manufacturer;
        GLOBAL_STATE.set = set;
        GLOBAL_STATE.init = true;
    }
}

/// Automatically initializes the library functions that are most appropriate for
/// the current processor type.
///
/// You should call this function before calling any other library function
///
/// # Safety
/// You can not use any other library function (also in other threads) while the initialization
/// is in progress. Failure to do so result in undefined behaviour
///
/// # Examples
/// ```
/// use dcv_color_primitives as dcp;
/// dcp::initialize();
/// ```
#[cfg(not(tarpaulin_include))]
pub fn initialize() {
    unsafe {
        if GLOBAL_STATE.init {
            return;
        }
    }

    let (manufacturer, set) = cpu_info::get();
    initialize_global_state(manufacturer, set);
}

/// This is for internal use only
#[cfg(feature = "test_instruction_sets")]
pub fn initialize_with_instruction_set(instruction_set: &str) {
    let (manufacturer, set) = cpu_info::get();

    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
    let set = match instruction_set {
        "x86" => InstructionSet::X86,
        "sse2" => match set {
            InstructionSet::Avx2 => InstructionSet::Sse2,
            _ => set,
        },
        _ => set,
    };

    initialize_global_state(manufacturer, set);
}

/// Returns a description of the algorithms that are best for the running cpu and
/// available instruction sets
///
/// # Errors
/// * [`NotInitialized`] if the library was not initialized before
///
/// # Examples
/// ```
/// use dcv_color_primitives as dcp;
/// dcp::initialize();
/// match dcp::describe_acceleration() {
///     Ok(description) => println!("{}", description),
///     Err(error) => println!("Unable to describe the acceleration: {}", error),
/// }
/// // => {cpu-manufacturer:Intel,instruction-set:Avx2}
/// ```
///
/// When [`initialize`] is not called:
/// ```
/// use dcv_color_primitives as dcp;
/// match dcp::describe_acceleration() {
///     Ok(description) => println!("{}", description),
///     Err(error) => println!("Unable to describe the acceleration: {}", error),
/// }
/// // => Unable to describe the acceleration: NotInitialized
/// ```
///
/// [`NotInitialized`]: ./enum.ErrorKind.html#variant.NotInitialized
/// [`initialize`]: ./fn.initialize.html
pub fn describe_acceleration() -> Result<String, ErrorKind> {
    unsafe {
        if GLOBAL_STATE.init {
            Ok(format!(
                "{{cpu-manufacturer:{:?},instruction-set:{:?}}}",
                GLOBAL_STATE.manufacturer, GLOBAL_STATE.set
            ))
        } else {
            Err(ErrorKind::NotInitialized)
        }
    }
}

/// Compute number of bytes required to store an image given its format, dimensions
/// and optionally its strides
///
/// # Arguments
/// * `width` - Width of the image in pixels
/// * `height` - Height of the image in pixels
/// * `format` - Image format
/// * `strides` - An array of distances in bytes between starts of consecutive lines
///               in each image planes
/// * `buffers_size` - An array describing the minimum number of bytes required in each
///                    image planes
///
/// # Examples
/// Compute how many bytes are needed to store and image of a given format and size
/// assuming *all planes contain data which is tightly packed*:
/// ```
/// use dcv_color_primitives as dcp;
/// use dcp::{get_buffers_size, ColorSpace, ImageFormat, PixelFormat};
/// use std::error;
///
/// fn compute_size_packed() -> Result<(), Box<dyn error::Error>> {
///     dcp::initialize();
///
///     const WIDTH: u32 = 640;
///     const HEIGHT: u32 = 480;
///     const NUM_PLANES: u32 = 2;
///
///     let format = ImageFormat {
///         pixel_format: PixelFormat::Nv12,
///         color_space: ColorSpace::Bt601,
///         num_planes: NUM_PLANES,
///     };
///
///     let sizes: &mut [usize] = &mut [0usize; NUM_PLANES as usize];
///     get_buffers_size(WIDTH, HEIGHT, &format, None, sizes)?;
///
///     Ok(())
/// }
/// ```
///
/// Compute how many bytes are needed to store and image of a given format and size
/// in which *all planes have custom strides*:
/// ```
/// use dcv_color_primitives as dcp;
/// use dcp::{get_buffers_size, ColorSpace, ImageFormat, PixelFormat};
/// use std::error;
///
/// fn compute_size_custom_strides() -> Result<(), Box<dyn error::Error>> {
///     dcp::initialize();
///
///     const WIDTH: u32 = 640;
///     const HEIGHT: u32 = 480;
///     const NUM_PLANES: u32 = 2;
///     const Y_STRIDE: usize = (WIDTH as usize) + 1;
///     const UV_STRIDE: usize = (WIDTH as usize) + 3;
///
///     let format = ImageFormat {
///         pixel_format: PixelFormat::Nv12,
///         color_space: ColorSpace::Bt601,
///         num_planes: NUM_PLANES,
///     };
///
///     let strides: &[usize] = &[ Y_STRIDE, UV_STRIDE, ];
///     let sizes: &mut [usize] = &mut [0usize; NUM_PLANES as usize];
///     get_buffers_size(WIDTH, HEIGHT, &format, Some(strides), sizes)?;
///
///     Ok(())
/// }
/// ```
///
/// Compute how many bytes are needed to store and image of a given format and size
/// in which *some planes have custom strides*, while *some other are assumed to
/// contain data which is tightly packed*:
/// ```
/// use dcv_color_primitives as dcp;
/// use dcp::{get_buffers_size, ColorSpace, ImageFormat, PixelFormat, STRIDE_AUTO};
/// use std::error;
///
/// fn compute_size_custom_strides() -> Result<(), Box<dyn error::Error>> {
///     dcp::initialize();
///
///     const WIDTH: u32 = 640;
///     const HEIGHT: u32 = 480;
///     const NUM_PLANES: u32 = 2;
///     const Y_STRIDE: usize = (WIDTH as usize) + 1;
///
///     let format = ImageFormat {
///         pixel_format: PixelFormat::Nv12,
///         color_space: ColorSpace::Bt601,
///         num_planes: NUM_PLANES,
///     };
///
///     let strides: &[usize] = &[ Y_STRIDE, STRIDE_AUTO, ];
///     let sizes: &mut [usize] = &mut [0usize; NUM_PLANES as usize];
///     get_buffers_size(WIDTH, HEIGHT, &format, Some(strides), sizes)?;
///
///     Ok(())
/// }
/// ```
///
/// Default strides (e.g. the one you would set for tightly packed data) can be set
/// using the constant [`STRIDE_AUTO`]
///
/// # Errors
///
/// * [`InvalidValue`] if `width` or `height` violate the [`size constraints`] that might by
///   imposed by the image pixel format
///
/// * [`InvalidValue`] if the image format has a number of planes which is not compatible
///   with its pixel format
///
/// * [`NotEnoughData`] if the strides array is not `None` and its length is less than the
///   image format number of planes
///
/// * [`NotEnoughData`] if the buffers size array is not `None` and its length is less than the
///   image format number of planes
///
/// [`InvalidValue`]: ./enum.ErrorKind.html#variant.NotInitialized
/// [`NotEnoughData`]: ./enum.ErrorKind.html#variant.NotEnoughData
/// [`size constraints`]: ./struct.ImageFormat.html#note
/// [`STRIDE_AUTO`]: ./constant.STRIDE_AUTO.html
pub fn get_buffers_size(
    width: u32,
    height: u32,
    format: &ImageFormat,
    strides: Option<&[usize]>,
    buffers_size: &mut [usize],
) -> Result<(), ErrorKind> {
    let pixel_format = format.pixel_format as u32;
    let last_plane = format.num_planes.wrapping_sub(1);
    if !pixel_format::is_compatible(pixel_format, width, height, last_plane) {
        return Err(ErrorKind::InvalidValue);
    }

    if pixel_format::get_buffers_size(
        pixel_format,
        width,
        height,
        last_plane,
        strides.unwrap_or(&pixel_format::DEFAULT_STRIDES),
        buffers_size,
    ) {
        Ok(())
    } else {
        Err(ErrorKind::NotEnoughData)
    }
}

/// Converts from a color space to another one, applying downsampling/upsampling
/// to match destination image format.
///
/// # Arguments
/// * `width` - Width of the image to convert in pixels
/// * `height` - Height of the image to convert in pixels
/// * `src_format` - Source image format
/// * `src_strides` - An array of distances in bytes between starts of consecutive lines
///                   in each source image planes
/// * `src_buffers` - An array of image buffers in each source color plane
/// * `dst_format` - Destination image format
/// * `dst_strides` - An array of distances in bytes between starts of consecutive lines
///                   in each destination image planes
/// * `dst_buffers` - An array of image buffers in each destination color plane
///
/// # Errors
///
/// * [`NotInitialized`] if the library was not initialized before
///
/// * [`InvalidValue`] if `width` or `height` violate the [`size constraints`]
///   that might by imposed by the source and destination image pixel formats
///
/// * [`InvalidValue`] if source or destination image formats have a number of planes
///   which is not compatible with their pixel formats
///
/// * [`InvalidOperation`] if there is no available method to convert the image with the
///   source pixel format to the image with the destination pixel format.
///
///   The list of available conversions is specified here:
///
///   Source image pixel format       | Supported destination image pixel formats
///   --------------------------------|------------------------------------------
///   `PixelFormat::Argb`             | `PixelFormat::I420` [`1`]
///   `PixelFormat::Argb`             | `PixelFormat::I444` [`1`]
///   `PixelFormat::Argb`             | `PixelFormat::Nv12` [`1`]
///   `PixelFormat::Bgra`             | `PixelFormat::I420` [`1`]
///   `PixelFormat::Bgra`             | `PixelFormat::I444` [`1`]
///   `PixelFormat::Bgra`             | `PixelFormat::Nv12` [`1`]
///   `PixelFormat::Bgra`             | `PixelFormat::Rgb`  [`4`]
///   `PixelFormat::Bgr`              | `PixelFormat::I420` [`1`]
///   `PixelFormat::Bgr`              | `PixelFormat::I444` [`1`]
///   `PixelFormat::Bgr`              | `PixelFormat::Nv12` [`1`]
///   `PixelFormat::Bgr`              | `PixelFormat::Rgb`  [`5`]
///   `PixelFormat::I420`             | `PixelFormat::Bgra` [`2`]
///   `PixelFormat::I444`             | `PixelFormat::Bgra` [`2`]
///   `PixelFormat::Nv12`             | `PixelFormat::Bgra`, `PixelFormat::Rgb` [`2`]
///   `PixelFormat::Rgb`              | `PixelFormat::Bgra` [`3`]
///
/// * [`NotEnoughData`] if the source stride array is not `None` and its length is less than the
///   source image format number of planes
///
/// * [`NotEnoughData`] if the destination stride array is not `None` and its length is less than the
///   destination image format number of planes
///
/// * [`NotEnoughData`] if one or more source/destination buffers does not provide enough data.
///
///   The minimum number of bytes to provide for each buffer depends from the image format, dimensions,
///   and strides (if they are not `None`).
///
///   You can compute the buffers' size using [`get_buffers_size`]
///
/// # Algorithm 1
/// Conversion from linear RGB model to ycbcr color model, with 4:2:0 downsampling
///
/// If the destination image color space is Bt601, the following formula is applied:
/// ```text
/// y  =  0.257 * r + 0.504 * g + 0.098 * b + 16
/// cb = -0.148 * r - 0.291 * g + 0.439 * b + 128
/// cr =  0.439 * r - 0.368 * g - 0.071 * b + 128
/// ```
///
/// If the destination image color space is Bt709, the following formula is applied:
/// ```text
/// y  =  0.213 * r + 0.715 * g + 0.072 * b + 16
/// cb = -0.117 * r - 0.394 * g + 0.511 * b + 128
/// cr =  0.511 * r - 0.464 * g - 0.047 * b + 128
///
/// If the destination image color space is Bt601FR, the following formula is applied:
/// ```text
/// y  =  0.299 * r + 0.587 * g + 0.114 * b
/// cb = -0.169 * r - 0.331 * g + 0.500 * b + 128
/// cr =  0.500 * r - 0.419 * g - 0.081 * b + 128
///
/// If the destination image color space is Bt709FR, the following formula is applied:
/// ```text
/// y  =  0.213 * r + 0.715 * g + 0.072 * b
/// cb = -0.115 * r - 0.385 * g + 0.500 * b + 128
/// cr =  0.500 * r - 0.454 * g - 0.046 * b + 128
/// ```
///
/// # Algorithm 2
/// Conversion from ycbcr model to linear RGB model, with 4:4:4 upsampling
///
/// If the destination image contains an alpha channel, each component will be set to 255
///
/// If the source image color space is Bt601, the following formula is applied:
/// ```text
/// r = 1.164 * (y - 16) + 1.596 * (cr - 128)
/// g = 1.164 * (y - 16) - 0.813 * (cr - 128) - 0.392 * (cb - 128)
/// b = 1.164 * (y - 16) + 2.017 * (cb - 128)
/// ```
///
/// If the source image color space is Bt709, the following formula is applied:
/// ```text
/// r = 1.164 * (y - 16) + 1.793 * (cr - 128)
/// g = 1.164 * (y - 16) - 0.534 * (cr - 128) - 0.213 * (cb - 128)
/// b = 1.164 * (y - 16) + 2.115 * (cb - 128)
///
/// If the source image color space is Bt601FR, the following formula is applied:
/// ```text
/// r = y + 1.402 * (cr - 128)
/// g = y - 0.714 * (cr - 128) - 0.344 * (cb - 128)
/// b = y + 1.772 * (cb - 128)
///
/// If the source image color space is Bt709FR, the following formula is applied:
/// ```text
/// r = y + 1.575 * (cr - 128)
/// g = y - 0.468 * (cr - 128) - 0.187 * (cb - 128)
/// b = y + 1.856 * (cb - 128)
/// ```
///
/// # Algorithm 3
/// Conversion from RGB to BGRA
///
/// # Algorithm 4
/// Conversion from BGRA to RGB
///
/// # Algorithm 5
/// Conversion from BGR to RGB
///
/// [`NotInitialized`]: ./enum.ErrorKind.html#variant.NotInitialized
/// [`InvalidValue`]: ./enum.ErrorKind.html#variant.InvalidValue
/// [`InvalidOperation`]: ./enum.ErrorKind.html#variant.InvalidOperation
/// [`NotEnoughData`]: ./enum.ErrorKind.html#variant.NotEnoughData
/// [`size constraints`]: ./struct.ImageFormat.html#note
/// [`get_buffers_size`]: ./fn.get_buffers_size.html
/// [`1`]: ./fn.convert_image.html#algorithm-1
/// [`2`]: ./fn.convert_image.html#algorithm-2
/// [`3`]: ./fn.convert_image.html#algorithm-3
pub fn convert_image(
    width: u32,
    height: u32,
    src_format: &ImageFormat,
    src_strides: Option<&[usize]>,
    src_buffers: &[&[u8]],
    dst_format: &ImageFormat,
    dst_strides: Option<&[usize]>,
    dst_buffers: &mut [&mut [u8]],
) -> Result<(), ErrorKind> {
    unsafe {
        if !GLOBAL_STATE.init {
            return Err(ErrorKind::NotInitialized);
        }
    }

    let src_pixel_format = src_format.pixel_format as u32;
    let dst_pixel_format = dst_format.pixel_format as u32;
    let src_color_space = src_format.color_space as u32;
    let dst_color_space = dst_format.color_space as u32;

    // Cross-correlate pixel format with color space. Predicate handles Table 1
    let src_pf_mode = dispatcher::get_pixel_format_mode(src_pixel_format);
    let src_cs_mode = dispatcher::get_color_space_mode(src_color_space);
    let dst_pf_mode = dispatcher::get_pixel_format_mode(dst_pixel_format);
    let dst_cs_mode = dispatcher::get_color_space_mode(dst_color_space);
    let src_pf_cs_mismatch = src_pf_mode ^ src_cs_mode;
    let dst_pf_cs_mismatch = dst_pf_mode ^ dst_cs_mode;
    if src_pf_cs_mismatch | dst_pf_cs_mismatch {
        return Err(ErrorKind::InvalidValue);
    }

    // Cross-correlate pixel format with planes and alignment.
    // wrapping_sub is wanted. If num_planes is 0, this turns in a very big number that
    // still represents an invalid number of planes.
    let last_src_plane = src_format.num_planes.wrapping_sub(1);
    if !pixel_format::is_compatible(src_pixel_format, width, height, last_src_plane) {
        return Err(ErrorKind::InvalidValue);
    }

    let last_dst_plane = dst_format.num_planes.wrapping_sub(1);
    if !pixel_format::is_compatible(dst_pixel_format, width, height, last_dst_plane) {
        return Err(ErrorKind::InvalidValue);
    }

    // Cross-correlate modes.
    let src_index = dispatcher::get_image_index(src_pixel_format, src_color_space, src_pf_mode);
    let dst_index = dispatcher::get_image_index(dst_pixel_format, dst_color_space, dst_pf_mode);
    let index = dispatcher::get_index(src_index, dst_index);
    let converters = { unsafe { &GLOBAL_STATE.converters } };
    if index >= converters.len() {
        return Err(ErrorKind::InvalidOperation);
    }

    let converter = converters[index];
    match converter {
        None => Err(ErrorKind::InvalidOperation),
        Some(image_converter) => {
            if image_converter(
                width,
                height,
                last_src_plane,
                src_strides.unwrap_or(&pixel_format::DEFAULT_STRIDES),
                src_buffers,
                last_dst_plane,
                dst_strides.unwrap_or(&pixel_format::DEFAULT_STRIDES),
                dst_buffers,
            ) {
                Ok(())
            } else {
                Err(ErrorKind::NotEnoughData)
            }
        }
    }
}

#[doc(hidden)]
#[cfg(not(tarpaulin_include))]
#[cfg(not(feature = "test_instruction_sets"))]
pub mod c_api {
    #![allow(clippy::wildcard_imports)]
    use super::*; // We are importing everything
    use pixel_format::{are_planes_compatible, MAX_NUMBER_OF_PLANES};
    use std::cmp;
    use std::ffi::CString;
    use std::mem::{transmute, MaybeUninit};
    use std::os::raw::c_char;
    use std::ptr;
    use std::slice;

    const UNBOUNDED_C_ARRAY: usize = std::isize::MAX as usize;

    #[repr(C)]
    pub enum Result {
        Ok,
        Err,
    }

    unsafe fn set_error(error: *mut ErrorKind, value: ErrorKind) -> self::Result {
        if !error.is_null() {
            *error = value;
        }

        self::Result::Err
    }

    #[no_mangle]
    pub extern "C" fn dcp_initialize() {
        initialize();
    }

    #[no_mangle]
    pub extern "C" fn dcp_describe_acceleration() -> *mut c_char {
        if let Ok(acc) = describe_acceleration() {
            if let Ok(s) = CString::new(acc) {
                s.into_raw()
            } else {
                let p: *const c_char = ptr::null();
                p as *mut c_char
            }
        } else {
            let p: *const c_char = ptr::null();
            p as *mut c_char
        }
    }

    #[no_mangle]
    pub unsafe extern "C" fn dcp_unref_string(string: *mut c_char) {
        if !string.is_null() {
            let _unused = CString::from_raw(string);
        }
    }

    #[no_mangle]
    pub unsafe extern "C" fn dcp_get_buffers_size(
        width: u32,
        height: u32,
        format: *const ImageFormat,
        strides: *const usize,
        buffers_size: *mut usize,
        error: *mut ErrorKind,
    ) -> self::Result {
        // Protect from C null pointers
        if format.is_null() || buffers_size.is_null() {
            return set_error(error, ErrorKind::InvalidValue);
        }

        // C enums are untrusted in the sense you can cast any value to an enum type
        let format = &*format;
        let pixel_format = format.pixel_format as u32;
        if !dispatcher::is_pixel_format_valid(pixel_format) {
            return set_error(error, ErrorKind::InvalidValue);
        }

        // We assume there is enough data in the buffers
        // If the assumption will not hold undefined behaviour occurs (like in C)
        let num_planes = format.num_planes as usize;
        if !are_planes_compatible(pixel_format, format.num_planes) {
            return set_error(error, ErrorKind::InvalidValue);
        }

        // Convert nullable type to Option
        let strides = if strides.is_null() {
            None
        } else {
            Some(slice::from_raw_parts(strides, num_planes))
        };

        let buffers_size = slice::from_raw_parts_mut(buffers_size, num_planes);
        match get_buffers_size(width, height, format, strides, buffers_size) {
            Ok(_) => self::Result::Ok,
            Err(error_kind) => set_error(error, error_kind),
        }
    }

    #[no_mangle]
    pub unsafe extern "C" fn dcp_convert_image(
        width: u32,
        height: u32,
        src_format: *const ImageFormat,
        src_strides: *const usize,
        src_buffers: *const *const u8,
        dst_format: *const ImageFormat,
        dst_strides: *const usize,
        dst_buffers: *const *mut u8,
        error: *mut ErrorKind,
    ) -> self::Result {
        // Protect from C null pointers
        if src_format.is_null()
            || dst_format.is_null()
            || src_buffers.is_null()
            || dst_buffers.is_null()
        {
            return set_error(error, ErrorKind::InvalidValue);
        }

        // C enums are untrusted in the sense you can cast any value to an enum type
        let src_format: &ImageFormat = &*src_format;
        let dst_format: &ImageFormat = &*dst_format;
        let src_pixel_format = src_format.pixel_format as u32;
        let dst_pixel_format = dst_format.pixel_format as u32;
        if !dispatcher::is_pixel_format_valid(src_pixel_format)
            || !dispatcher::is_pixel_format_valid(dst_pixel_format)
            || !dispatcher::is_color_space_valid(src_format.color_space as u32)
            || !dispatcher::is_color_space_valid(dst_format.color_space as u32)
        {
            return set_error(error, ErrorKind::InvalidValue);
        }

        // We assume there is enough data in the buffers
        // If the assumption will not hold undefined behaviour occurs (like in C)
        if !are_planes_compatible(src_pixel_format, src_format.num_planes)
            || !are_planes_compatible(dst_pixel_format, dst_format.num_planes)
        {
            return set_error(error, ErrorKind::InvalidValue);
        }

        let src_buffers = {
            let src_num_planes = src_format.num_planes as usize;
            let num_planes = cmp::min(src_num_planes, MAX_NUMBER_OF_PLANES);
            let mut src_buf: [MaybeUninit<&[u8]>; MAX_NUMBER_OF_PLANES] =
                [MaybeUninit::uninit().assume_init(); MAX_NUMBER_OF_PLANES];

            for (plane_index, item) in src_buf.iter_mut().enumerate().take(num_planes) {
                let ptr = *src_buffers.add(plane_index);
                if ptr.is_null() {
                    return set_error(error, ErrorKind::InvalidValue);
                }

                *item = MaybeUninit::new(slice::from_raw_parts(ptr, UNBOUNDED_C_ARRAY));
            }

            transmute::<_, [&[u8]; MAX_NUMBER_OF_PLANES]>(src_buf)
        };

        let mut dst_buffers = {
            let dst_num_planes = dst_format.num_planes as usize;
            let num_planes = cmp::min(dst_num_planes, MAX_NUMBER_OF_PLANES);
            let mut dst_buf: [MaybeUninit<&[u8]>; MAX_NUMBER_OF_PLANES] =
                [MaybeUninit::uninit().assume_init(); MAX_NUMBER_OF_PLANES];

            for (plane_index, item) in dst_buf.iter_mut().enumerate().take(num_planes) {
                let ptr = *dst_buffers.add(plane_index);
                if ptr.is_null() {
                    return set_error(error, ErrorKind::InvalidValue);
                }

                *item = MaybeUninit::new(slice::from_raw_parts_mut(ptr, UNBOUNDED_C_ARRAY));
            }

            transmute::<_, [&mut [u8]; MAX_NUMBER_OF_PLANES]>(dst_buf)
        };

        // Convert nullable type to Option
        let src_strides = if src_strides.is_null() {
            None
        } else {
            Some(slice::from_raw_parts(src_strides, UNBOUNDED_C_ARRAY))
        };

        let dst_strides = if dst_strides.is_null() {
            None
        } else {
            Some(slice::from_raw_parts(dst_strides, UNBOUNDED_C_ARRAY))
        };

        match convert_image(
            width,
            height,
            src_format,
            src_strides,
            &src_buffers[..],
            dst_format,
            dst_strides,
            &mut dst_buffers[..],
        ) {
            Ok(_) => self::Result::Ok,
            Err(error_kind) => set_error(error, error_kind),
        }
    }
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0

// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify,
// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
pub const MAX_NUMBER_OF_PLANES: usize = 4;

/// An enumeration of supported pixel formats.
#[derive(Copy, Clone)]
#[repr(C)]
pub enum PixelFormat {
    /// RGB with alpha channel first.
    ///
    /// 32 bits per pixel
    Argb,
    /// Reverse RGB with alpha channel last.
    ///
    /// 32 bits per pixel
    Bgra,
    /// Reverse RGB packed into 24 bits without padding.
    ///
    /// 24 bits per pixel
    Bgr,
    /// RGB with alpha channel last.
    ///
    /// 32 bits per pixel
    Rgba,
    /// RGB packed into 24 bits without padding.
    ///
    /// 24 bits per pixel
    Rgb,
    /// YUV with one luma plane Y then 2 chroma planes U and V.
    /// Chroma planes are not sub-sampled.
    ///
    /// 24 bits per pixel
    I444,
    /// YUV with one luma plane Y then 2 chroma planes U, V.
    /// Chroma planes are sub-sampled in the horizontal dimension, by a factor of 2.
    ///
    /// 16 bits per pixel
    I422,
    /// YUV with one luma plane Y then U chroma plane and last the V chroma plane.
    /// The two chroma planes are sub-sampled in both the horizontal and vertical dimensions by a factor of 2.
    ///
    /// 12 bits per pixel
    I420,
    /// YUV with one luma plane Y then one plane with U and V values interleaved.
    /// Chroma planes are subsampled in both the horizontal and vertical dimensions by a factor of 2.
    ///
    /// 12 bits per pixel
    Nv12,
}

impl PixelFormat {
    #[cfg(not(tarpaulin_include))]
    pub(crate) const fn depth(pixel_format: PixelFormat) -> usize {
        match pixel_format {
            PixelFormat::Argb | PixelFormat::Bgra | PixelFormat::Rgba => 4,
            PixelFormat::Bgr | PixelFormat::Rgb => 3,
            _ => 0,
        }
    }
}

#[cfg(not(tarpaulin_include))]
impl std::fmt::Display for PixelFormat {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            PixelFormat::Argb => write!(f, "argb"),
            PixelFormat::Bgra => write!(f, "bgra"),
            PixelFormat::Bgr => write!(f, "bgr"),
            PixelFormat::Rgba => write!(f, "rgba"),
            PixelFormat::Rgb => write!(f, "rgb"),
            PixelFormat::I444 => write!(f, "i444"),
            PixelFormat::I422 => write!(f, "i422"),
            PixelFormat::I420 => write!(f, "i420"),
            PixelFormat::Nv12 => write!(f, "nv12"),
        }
    }
}

/// If a plane stride is assigned to this constant, the plane will be assumed to contain packed data
pub const STRIDE_AUTO: usize = 0;

pub const DEFAULT_STRIDES: [usize; MAX_NUMBER_OF_PLANES] = [STRIDE_AUTO; MAX_NUMBER_OF_PLANES];

#[cfg(not(tarpaulin_include))]
const fn make_pf_spec(planes: u32, width: u32, height: u32) -> u32 {
    (height << 3) | (width << 2) | planes
}

#[cfg(not(tarpaulin_include))]
const fn make_plane_spec(plane0: u32, plane1: u32, plane2: u32, plane3: u32) -> u32 {
    (plane3 << 18) | (plane2 << 12) | (plane1 << 6) | plane0
}

const INVALID_PLANE: u32 = 32;

const PF_SPECS: [u32; 9] = [
    make_pf_spec(0, 0, 0),
    make_pf_spec(0, 0, 0),
    make_pf_spec(0, 0, 0),
    make_pf_spec(0, 0, 0),
    make_pf_spec(0, 0, 0),
    make_pf_spec(2, 0, 0),
    make_pf_spec(2, 1, 0),
    make_pf_spec(2, 1, 1),
    make_pf_spec(1, 1, 1),
];

const STRIDE_SPECS: [u32; 9] = [
    make_plane_spec(0, 0, 0, 0),
    make_plane_spec(0, 0, 0, 0),
    make_plane_spec(0, 0, 0, INVALID_PLANE),
    make_plane_spec(0, 0, 0, 0),
    make_plane_spec(0, 0, 0, INVALID_PLANE),
    make_plane_spec(0, 0, 0, INVALID_PLANE),
    make_plane_spec(0, 1, 1, INVALID_PLANE),
    make_plane_spec(0, 1, 1, INVALID_PLANE),
    make_plane_spec(0, 0, INVALID_PLANE, INVALID_PLANE),
];

const HEIGHT_SPECS: [u32; 9] = [
    make_plane_spec(0, 0, 0, 0),
    make_plane_spec(0, 0, 0, 0),
    make_plane_spec(0, 0, 0, INVALID_PLANE),
    make_plane_spec(0, 0, 0, 0),
    make_plane_spec(0, 0, 0, INVALID_PLANE),
    make_plane_spec(0, 0, 0, INVALID_PLANE),
    make_plane_spec(0, 0, 0, INVALID_PLANE),
    make_plane_spec(0, 1, 1, INVALID_PLANE),
    make_plane_spec(0, 1, INVALID_PLANE, INVALID_PLANE),
];

fn get_pf_width(pf: u32) -> u32 {
    (pf >> 2) & 1
}

fn get_pf_height(pf: u32) -> u32 {
    pf >> 3
}

fn get_pf_planes(pf: u32) -> u32 {
    pf & 3
}

fn get_plane_value(bpp: u32, plane: usize) -> u32 {
    (bpp >> (6 * plane)) & 0x3F
}

fn get_plane_mask(bpp: u32, plane: usize) -> usize {
    usize::from(INVALID_PLANE != get_plane_value(bpp, plane))
}

fn get_plane_spec(dimension: u32, bpp: u32, plane: usize) -> usize {
    (dimension.wrapping_shr(get_plane_value(bpp, plane))) as usize
}

pub fn is_compatible(pixel_format: u32, width: u32, height: u32, last_plane: u32) -> bool {
    let spec = PF_SPECS[pixel_format as usize];
    let planes = get_pf_planes(spec);
    let matches_exact = last_plane.wrapping_sub(planes);
    let last_plane = if pixel_format == (PixelFormat::Nv12 as u32) {
        last_plane
    } else {
        1
    };

    ((width & get_pf_width(spec))
        | (height & get_pf_height(spec))
        | (last_plane.wrapping_mul(matches_exact)))
        == 0
}

pub fn get_buffers_size(
    pixel_format: u32,
    width: u32,
    height: u32,
    last_plane: u32,
    strides: &[usize],
    buffers_size: &mut [usize],
) -> bool {
    let last_plane = last_plane as usize;
    if last_plane >= MAX_NUMBER_OF_PLANES
        || last_plane >= strides.len()
        || last_plane >= buffers_size.len()
    {
        return false;
    }

    let stride = &mut [0_usize; MAX_NUMBER_OF_PLANES];

    let pixel_format = pixel_format as usize;
    let stride_spec = STRIDE_SPECS[pixel_format];
    for i in 0..MAX_NUMBER_OF_PLANES {
        stride[i] = if i >= strides.len() || strides[i] == STRIDE_AUTO {
            get_plane_mask(stride_spec, i) * get_plane_spec(width, stride_spec, i)
        } else {
            strides[i]
        };
    }

    let height_spec = HEIGHT_SPECS[pixel_format];
    if last_plane == 0 {
        buffers_size[0] = ((stride[0] * get_plane_spec(height, height_spec, 0))
            + (stride[1] * get_plane_spec(height, height_spec, 1)))
            + ((stride[2] * get_plane_spec(height, height_spec, 2))
                + (stride[3] * get_plane_spec(height, height_spec, 3)));
    } else {
        let buffer_array = &mut buffers_size[..=last_plane];
        let stride_array = &stride[..=last_plane];

        for (buffer_size, (i, stride)) in
            buffer_array.iter_mut().zip(stride_array.iter().enumerate())
        {
            *buffer_size = *stride * get_plane_spec(height, height_spec, i);
        }
    }

    true
}

#[cfg(not(feature = "test_instruction_sets"))]
pub fn are_planes_compatible(pixel_format: u32, num_planes: u32) -> bool {
    let last_plane = num_planes.wrapping_sub(1);
    let spec = PF_SPECS[pixel_format as usize];
    let matches_exact = last_plane.wrapping_sub(get_pf_planes(spec));
    let last_plane = if pixel_format == (PixelFormat::Nv12 as u32) {
        last_plane
    } else {
        1
    };

    last_plane.wrapping_mul(matches_exact) == 0
}


// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT-0

// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify,
// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#[doc(hidden)]
#[macro_export]
macro_rules! static_assert {
    ($cond:expr) => {
        const _: usize = 0 - !$cond as usize;
    };
}