#![warn(unused)]
#![deny(trivial_casts)]
#![deny(trivial_numeric_casts)]
#![deny(unsafe_code)]
#![deny(unstable_features)]
#![deny(unused_import_braces)]
#![deny(
    clippy::complexity,
    clippy::correctness,
    clippy::perf,
    clippy::style,
    clippy::pedantic
)]
#![allow(clippy::too_many_lines)] // This requires effort to handle

#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::wasm_bindgen_test as test;

#[cfg(target_arch = "x86_64")]
use std::alloc::{alloc, alloc_zeroed, dealloc, Layout};
#[cfg(target_arch = "x86_64")]
use std::ptr::write_bytes;
#[cfg(target_arch = "x86_64")]
use std::slice::{from_raw_parts, from_raw_parts_mut};

use dcp::{
    convert_image, describe_acceleration, get_buffers_size, ColorSpace, ErrorKind, ImageFormat,
    PixelFormat, STRIDE_AUTO,
};

#[cfg(not(feature = "test_instruction_sets"))]
use dcp::initialize;
#[cfg(feature = "test_instruction_sets")]
use dcp::initialize_with_instruction_set;

use dcv_color_primitives as dcp;
use itertools::iproduct;
use rand::Rng;

#[cfg(feature = "test_instruction_sets")]
const SETS: [&str; 3] = ["x86", "sse2", "avx2"];
#[cfg(not(feature = "test_instruction_sets"))]
const SETS: [&str; 1] = [""];

const MAX_NUMBER_OF_PLANES: u32 = 3;

const PIXEL_FORMATS: &[PixelFormat; 9] = &[
    PixelFormat::Argb,
    PixelFormat::Bgra,
    PixelFormat::Bgr,
    PixelFormat::Rgba,
    PixelFormat::Rgb,
    PixelFormat::I444,
    PixelFormat::I422,
    PixelFormat::I420,
    PixelFormat::Nv12,
];

const COLOR_SPACES: &[ColorSpace; 5] = &[
    ColorSpace::Rgb,
    ColorSpace::Bt601,
    ColorSpace::Bt709,
    ColorSpace::Bt601FR,
    ColorSpace::Bt709FR,
];

const PIXEL_FORMAT_I444: u32 = PixelFormat::I444 as u32;
const PIXEL_FORMAT_I422: u32 = PixelFormat::I422 as u32;
const PIXEL_FORMAT_I420: u32 = PixelFormat::I420 as u32;
const COLOR_SPACE_RGB: u32 = ColorSpace::Rgb as u32;
const PIXEL_FORMAT_ARGB: u32 = PixelFormat::Argb as u32;
const PIXEL_FORMAT_BGRA: u32 = PixelFormat::Bgra as u32;
const PIXEL_FORMAT_BGR: u32 = PixelFormat::Bgr as u32;
const PIXEL_FORMAT_NV12: u32 = PixelFormat::Nv12 as u32;
const PIXEL_FORMAT_RGB: u32 = PixelFormat::Rgb as u32;
const RGB_SRC: [[[u8; 4]; 8]; 8] = [
    [
        [161, 24, 44, 58],
        [35, 95, 51, 205],
        [177, 30, 252, 158],
        [248, 94, 62, 28],
        [247, 51, 135, 38],
        [98, 147, 200, 127],
        [68, 103, 20, 124],
        [233, 227, 165, 0],
    ],
    [
        [251, 19, 32, 170],
        [235, 183, 25, 77],
        [146, 81, 218, 161],
        [25, 124, 96, 56],
        [22, 127, 167, 179],
        [247, 34, 40, 53],
        [164, 193, 159, 24],
        [96, 158, 17, 223],
    ],
    [
        [240, 123, 14, 108],
        [0, 105, 52, 116],
        [194, 219, 244, 47],
        [216, 254, 153, 84],
        [116, 77, 133, 68],
        [190, 96, 190, 133],
        [118, 4, 170, 115],
        [218, 145, 23, 50],
    ],
    [
        [202, 120, 126, 231],
        [42, 28, 137, 40],
        [136, 227, 210, 177],
        [254, 140, 238, 88],
        [90, 195, 170, 67],
        [125, 242, 148, 88],
        [1, 91, 190, 245],
        [31, 100, 190, 225],
    ],
    [
        [207, 49, 249, 131],
        [48, 120, 34, 82],
        [43, 145, 253, 141],
        [83, 205, 105, 44],
        [16, 9, 157, 22],
        [253, 131, 178, 148],
        [142, 236, 98, 6],
        [246, 190, 15, 213],
    ],
    [
        [72, 207, 6, 168],
        [220, 39, 6, 219],
        [244, 14, 252, 45],
        [159, 106, 17, 184],
        [222, 72, 230, 39],
        [6, 185, 30, 35],
        [101, 223, 30, 14],
        [40, 71, 16, 244],
    ],
    [
        [124, 121, 46, 190],
        [244, 206, 61, 169],
        [43, 130, 87, 247],
        [170, 10, 238, 229],
        [12, 168, 14, 220],
        [96, 60, 226, 235],
        [206, 93, 122, 117],
        [126, 168, 203, 39],
    ],
    [
        [181, 88, 248, 45],
        [65, 24, 208, 166],
        [24, 21, 151, 85],
        [60, 86, 9, 153],
        [225, 80, 156, 159],
        [210, 181, 6, 214],
        [17, 142, 255, 163],
        [189, 137, 72, 87],
    ],
];

const Y_BT601_REF: &[&[u8]] = &[
    &[74, 78, 101, 133, 118, 135, 87, 206],
    &[93, 171, 116, 94, 102, 100, 171, 122],
    &[141, 74, 200, 214, 98, 132, 65, 147],
    &[141, 54, 186, 175, 154, 185, 81, 93],
    &[118, 92, 125, 151, 40, 164, 181, 176],
    &[139, 93, 110, 112, 132, 114, 157, 64],
    &[113, 188, 101, 88, 105, 93, 128, 153],
    &[131, 65, 48, 76, 129, 162, 117, 141],
];

const CB_BT601_REF: &[&[u8]] = &[
    &[116, 118, 204, 91, 136, 159, 97, 100],
    &[99, 51, 179, 130, 161, 99, 117, 75],
    &[63, 120, 143, 89, 147, 155, 184, 64],
    &[118, 174, 134, 154, 133, 104, 185, 178],
    &[192, 101, 191, 102, 192, 131, 81, 43],
    &[60, 87, 198, 81, 175, 86, 61, 108],
    &[95, 59, 122, 204, 83, 196, 124, 150],
    &[184, 203, 185, 98, 140, 47, 196, 92],
];

const CR_BT601_REF: &[&[u8]] = &[
    &[187, 105, 177, 198, 208, 103, 119, 135],
    &[229, 162, 147, 87, 79, 221, 118, 111],
    &[187, 86, 115, 119, 141, 163, 166, 169],
    &[164, 126, 89, 171, 84, 83, 81, 91],
    &[183, 103, 75, 82, 121, 178, 97, 165],
    &[83, 210, 212, 158, 183, 60, 88, 118],
    &[135, 155, 93, 182, 70, 132, 176, 107],
    &[157, 133, 120, 122, 186, 153, 65, 155],
];

const CB2_BT601_REF: &[&[u8]] = &[
    &[96, 151, 139, 97],
    &[119, 130, 135, 153],
    &[110, 143, 146, 73],
    &[135, 152, 116, 140],
];

const CR2_BT601_REF: &[&[u8]] = &[
    &[171, 152, 153, 121],
    &[141, 124, 118, 127],
    &[145, 132, 135, 117],
    &[145, 129, 135, 126],
];

const Y_BT709_REF: &[&[u8]] = &[
    &[63, 84, 82, 123, 101, 137, 93, 208],
    &[75, 173, 106, 103, 108, 84, 174, 132],
    &[136, 84, 201, 221, 93, 121, 51, 146],
    &[134, 49, 193, 163, 163, 197, 84, 95],
    &[99, 101, 129, 164, 34, 154, 193, 179],
    &[157, 80, 85, 111, 115, 133, 173, 68],
    &[116, 191, 109, 68, 122, 84, 118, 155],
    &[118, 56, 43, 80, 116, 166, 122, 139],
];

const CB_BT709_REF: &[&[u8]] = &[
    &[123, 115, 211, 98, 145, 156, 95, 100],
    &[110, 53, 182, 126, 156, 109, 116, 72],
    &[68, 115, 141, 87, 149, 160, 189, 67],
    &[122, 174, 130, 160, 128, 98, 181, 174],
    &[200, 97, 186, 96, 192, 136, 77, 45],
    &[53, 95, 209, 84, 182, 78, 56, 107],
    &[95, 60, 118, 212, 76, 197, 129, 148],
    &[189, 205, 185, 97, 147, 48, 190, 94],
];

const CR_BT709_REF: &[&[u8]] = &[
    &[187, 103, 184, 197, 211, 104, 116, 133],
    &[229, 157, 151, 86, 80, 221, 117, 106],
    &[184, 84, 116, 115, 143, 166, 171, 165],
    &[164, 130, 89, 174, 83, 80, 84, 94],
    &[189, 100, 79, 78, 125, 180, 92, 160],
    &[77, 209, 219, 155, 188, 56, 82, 117],
    &[132, 151, 92, 189, 66, 137, 176, 108],
    &[162, 139, 124, 120, 189, 148, 69, 153],
];

const CB2_BT709_REF: &[&[u8]] = &[
    &[100, 154, 142, 96],
    &[120, 130, 134, 153],
    &[112, 144, 147, 71],
    &[137, 153, 117, 140],
];

const CR2_BT709_REF: &[&[u8]] = &[
    &[169, 154, 154, 118],
    &[140, 124, 118, 129],
    &[144, 133, 137, 113],
    &[146, 131, 135, 127],
];

const Y_BT601FR_REF: &[&[u8]] = &[
    &[67, 72, 99, 136, 119, 138, 83, 222],
    &[90, 181, 116, 91, 100, 98, 180, 123],
    &[146, 68, 214, 231, 95, 135, 57, 153],
    &[145, 45, 198, 185, 161, 196, 75, 90],
    &[119, 89, 127, 157, 28, 173, 192, 187],
    &[144, 89, 110, 112, 135, 114, 165, 55],
    &[113, 201, 99, 84, 104, 90, 130, 159],
    &[134, 57, 37, 69, 132, 170, 118, 145],
];

const CB_BT601FR_REF: &[&[u8]] = &[
    &[115, 116, 214, 86, 137, 163, 92, 96],
    &[95, 40, 186, 131, 166, 95, 116, 68],
    &[54, 119, 145, 84, 149, 159, 192, 55],
    &[117, 180, 135, 158, 133, 101, 193, 185],
    &[201, 97, 199, 99, 201, 131, 75, 31],
    &[50, 81, 208, 75, 182, 81, 52, 106],
    &[90, 49, 121, 215, 77, 205, 123, 153],
    &[192, 213, 192, 94, 142, 36, 206, 87],
];

const CR_BT601FR_REF: &[&[u8]] = &[
    &[195, 102, 183, 208, 219, 99, 117, 136],
    &[243, 167, 149, 81, 72, 234, 116, 108],
    &[195, 80, 113, 117, 143, 167, 172, 174],
    &[169, 126, 84, 177, 78, 77, 75, 86],
    &[191, 99, 68, 75, 119, 185, 92, 170],
    &[77, 221, 224, 162, 190, 51, 83, 117],
    &[136, 159, 88, 189, 63, 133, 182, 104],
    &[161, 134, 119, 121, 194, 157, 56, 159],
];

const CB2_BT601FR_REF: &[&[u8]] = &[
    &[92, 154, 140, 93],
    &[118, 130, 136, 156],
    &[107, 145, 149, 66],
    &[136, 156, 115, 142],
];

const CR2_BT601FR_REF: &[&[u8]] = &[
    &[177, 155, 156, 120],
    &[142, 123, 116, 127],
    &[147, 132, 136, 116],
    &[147, 129, 137, 125],
];

const Y_BT709FR_REF: &[&[u8]] = &[
    &[55, 79, 77, 124, 99, 140, 90, 224],
    &[69, 183, 105, 101, 108, 80, 184, 135],
    &[140, 79, 215, 239, 89, 123, 40, 152],
    &[138, 39, 206, 171, 171, 210, 79, 92],
    &[97, 98, 131, 172, 21, 160, 206, 189],
    &[164, 75, 80, 111, 115, 136, 183, 60],
    &[116, 204, 108, 60, 124, 80, 119, 162],
    &[119, 46, 31, 75, 116, 175, 124, 143],
];

const CB_BT709FR_REF: &[&[u8]] = &[
    &[122, 113, 222, 94, 148, 160, 91, 96],
    &[108, 43, 189, 125, 160, 107, 114, 65],
    &[60, 114, 143, 82, 152, 164, 198, 59],
    &[122, 181, 130, 164, 128, 94, 188, 181],
    &[210, 93, 194, 92, 201, 138, 70, 34],
    &[43, 91, 221, 77, 190, 71, 45, 104],
    &[90, 51, 116, 224, 69, 207, 130, 150],
    &[197, 215, 193, 92, 149, 37, 199, 90],
];

const CR_BT709FR_REF: &[&[u8]] = &[
    &[196, 100, 191, 206, 222, 101, 114, 134],
    &[243, 161, 154, 80, 74, 234, 115, 103],
    &[191, 78, 114, 114, 145, 171, 177, 170],
    &[169, 130, 83, 181, 77, 74, 78, 89],
    &[198, 96, 72, 72, 125, 187, 87, 164],
    &[70, 220, 232, 159, 196, 46, 76, 115],
    &[133, 154, 86, 198, 57, 138, 183, 105],
    &[167, 140, 124, 119, 197, 151, 60, 157],
];

const CB2_BT709FR_REF: &[&[u8]] = &[
    &[97, 158, 144, 91],
    &[119, 130, 134, 156],
    &[109, 146, 150, 63],
    &[138, 156, 116, 142],
];

const CR2_BT709FR_REF: &[&[u8]] = &[
    &[175, 158, 158, 117],
    &[142, 123, 117, 129],
    &[146, 134, 138, 111],
    &[148, 132, 136, 126],
];

// Largest group that uses neither avx2 nor sse2 is 62x64.
// We can arrange the image as blocks:
// y0 y0 | y1 y1 | ...
// y0 y0 | y1 y1 | ...
// u0 v0 | u1 v1 | ...
//
// Min width to represent 8 colors below: 2 * 8 = 16x16
//
// Color table (bt601):
//             r    g    b
// black    (  0,   0,   0):    16, 128, 128
// red      (255,   0,   0):    82,  90, 240
// green    (  0, 255,   0):   145,  54,  34
// yellow   (255, 255,   0):   210,  16, 146
// blue     (  0,   0, 255):    41, 240, 110
// magenta  (255,   0, 255):   107, 202, 222
// cyan     (  0, 255, 255):   169, 166,  16
// white    (255, 255, 255):   235, 128, 128
//
// Color table (bt709):
//             r    g    b
// black    (  0,   0,   0):    16, 128, 128
// red      (255,   0,   0):    63, 102, 240
// green    (  0, 255,   0):   173,  42,  26
// yellow   (255, 255,   0):   219,  16, 138
// blue     (  0,   0, 255):    32, 240, 118
// magenta  (255,   0, 255):    78, 214, 230
// cyan     (  0, 255, 255):   188, 154,  16
// white    (255, 255, 255):   235, 128, 128
//
// Color table (bt601 full range):
//             r    g    b
// black    (  0,   0,   0):     0, 128, 128
// red      (255,   0,   0):    76,  84, 255
// green    (  0, 255,   0):   149,  43,  21
// yellow   (255, 255,   0):   225,   0, 148
// blue     (  0,   0, 255):    29, 255, 107
// magenta  (255,   0, 255):   105, 212, 234
// cyan     (  0, 255, 255):   178, 171,   0
// white    (255, 255, 255):   255, 128, 128
//
// Color table (bt709 full range):
//             r    g    b
// black    (  0,   0,   0):     0, 128, 128
// red      (255,   0,   0):    54,  98, 255
// green    (  0, 255,   0):   182,  29,  12
// yellow   (255, 255,   0):   237,   0, 139
// blue     (  0,   0, 255):    18, 255, 116
// magenta  (255,   0, 255):    73, 226, 243
// cyan     (  0, 255, 255):   201, 157,   0
// white    (255, 255, 255):   255, 128, 128
const Y_SRC: [[u8; 8]; 4] = [
    [16, 82, 145, 210, 41, 107, 169, 235],
    [16, 63, 173, 219, 32, 78, 188, 235],
    [0, 76, 149, 225, 29, 105, 178, 255],
    [0, 54, 182, 237, 18, 73, 201, 255],
];

const U_SRC: [[u8; 8]; 4] = [
    [128, 90, 54, 16, 240, 202, 166, 128],
    [128, 102, 42, 16, 240, 214, 154, 128],
    [128, 84, 43, 0, 255, 212, 171, 128],
    [128, 98, 29, 0, 255, 226, 157, 128],
];

const V_SRC: [[u8; 8]; 4] = [
    [128, 240, 34, 146, 110, 222, 16, 128],
    [128, 240, 26, 138, 118, 230, 16, 128],
    [128, 255, 21, 148, 107, 234, 0, 128],
    [128, 255, 12, 139, 116, 243, 0, 128],
];

const NUM_LOG2_DEN: [[usize; 2]; 9] = [
    [4, 0],
    [4, 0],
    [3, 0],
    [4, 0],
    [3, 0],
    [3, 0],
    [2, 0],
    [3, 1],
    [3, 1],
];

const NUM_LOG2_DEN_PER_PLANE: [[usize; (2 * MAX_NUMBER_OF_PLANES) as usize]; 9] = [
    [4, 0, 0, 0, 0, 0],
    [4, 0, 0, 0, 0, 0],
    [3, 0, 0, 0, 0, 0],
    [4, 0, 0, 0, 0, 0],
    [3, 0, 0, 0, 0, 0],
    [1, 0, 1, 0, 1, 0],
    [1, 0, 1, 1, 1, 1],
    [1, 0, 1, 2, 1, 2],
    [1, 0, 1, 1, 0, 0],
];

type PlaneRef<'a> = &'a [&'a [u8]];

macro_rules! set_expected {
    ($var:ident, $pred:expr, $status:path) => {
        if $var.is_ok() && $pred {
            $var = Err($status);
        }
    };
}

fn is_valid_format(format: &ImageFormat, width: u32, height: u32) -> bool {
    match format.pixel_format {
        PixelFormat::I444 => format.num_planes != 3,
        PixelFormat::I422 | PixelFormat::I420 => {
            format.num_planes != 3 || (width & 1) == 1 || (height & 1) == 1
        }
        PixelFormat::Nv12 => {
            (format.num_planes < 1 || format.num_planes > 2)
                || (width & 1) == 1
                || (height & 1) == 1
        }
        _ => format.num_planes != 1,
    }
}

fn get_depth(pixel_format: PixelFormat) -> usize {
    match pixel_format {
        PixelFormat::Bgra | PixelFormat::Argb => 4,
        _ => 3,
    }
}

fn check_err(result: ErrorKind, err: ErrorKind) {
    assert_eq!(result as u32, err as u32);
}

fn check_bounds(
    width: u32,
    height: u32,
    src_format: &ImageFormat,
    src_buffers: &[&[u8]],
    dst_format: &ImageFormat,
    dst_buffers: &mut [&mut [u8]],
) {
    if width == 0 || height == 0 {
        return;
    }

    check_err(
        convert_image(
            width,
            height,
            src_format,
            None,
            &src_buffers[..src_format.num_planes as usize - 1],
            dst_format,
            None,
            dst_buffers,
        )
        .unwrap_err(),
        ErrorKind::NotEnoughData,
    );
    check_err(
        convert_image(
            width,
            height,
            src_format,
            None,
            src_buffers,
            dst_format,
            None,
            &mut dst_buffers[..dst_format.num_planes as usize - 1],
        )
        .unwrap_err(),
        ErrorKind::NotEnoughData,
    );

    // Overflow for source plane data
    for i in 0..src_format.num_planes as usize {
        let mut src_vec = Vec::new();

        for (c, v) in src_buffers.iter().enumerate() {
            if c == i {
                src_vec.push(&v[..0]);
            } else {
                src_vec.push(&v[..]);
            }
        }

        check_err(
            convert_image(
                width,
                height,
                src_format,
                None,
                &src_vec,
                dst_format,
                None,
                dst_buffers,
            )
            .unwrap_err(),
            ErrorKind::NotEnoughData,
        );
    }

    // Overflow for destination plane data
    for i in 0..dst_format.num_planes as usize {
        let mut dst_vec = Vec::new();

        for (c, v) in dst_buffers.iter_mut().enumerate() {
            if c == i {
                dst_vec.push(&mut v[..0]);
            } else {
                dst_vec.push(&mut v[..]);
            }
        }

        check_err(
            convert_image(
                width,
                height,
                src_format,
                None,
                src_buffers,
                dst_format,
                None,
                &mut dst_vec[..],
            )
            .unwrap_err(),
            ErrorKind::NotEnoughData,
        );
    }
}

fn check_plane(plane: &[u8], reference: PlaneRef, width: usize, height: usize, stride: usize) {
    for (row, exp) in plane.chunks(stride).zip(reference.iter().take(height)) {
        let (payload, pad) = row.split_at(width);
        assert!(payload
            .iter()
            .zip(exp.iter().take(width))
            .all(|(&x, &y)| x == y));
        assert!(pad.iter().all(|&x| x == 0));
    }
}

fn bootstrap(set: &str) {
    #[cfg(not(feature = "test_instruction_sets"))]
    {
        let _ = set;
        let desc = describe_acceleration();
        if desc.is_ok() {
            return;
        }

        initialize();
    }

    #[cfg(feature = "test_instruction_sets")]
    {
        let desc = describe_acceleration();
        match desc {
            Err(ErrorKind::NotInitialized) => {
                const WIDTH: u32 = 640;
                const HEIGHT: u32 = 480;
                const PIXELS: usize = (WIDTH as usize) * (HEIGHT as usize);

                let src_buffers: &[&[u8]] = &[&[0_u8; 4 * PIXELS]];
                let dst_buffers: &mut [&mut [u8]] = &mut [&mut [0_u8; 3 * PIXELS]];
                let src_format = ImageFormat {
                    pixel_format: PixelFormat::Bgra,
                    color_space: ColorSpace::Rgb,
                    num_planes: 1,
                };
                let dst_format = ImageFormat {
                    pixel_format: PixelFormat::Rgb,
                    color_space: ColorSpace::Rgb,
                    num_planes: 1,
                };
                let sizes = &mut [0_usize; 1];

                assert!(get_buffers_size(WIDTH, HEIGHT, &src_format, None, sizes).is_ok());
                check_err(
                    convert_image(
                        WIDTH,
                        HEIGHT,
                        &src_format,
                        None,
                        src_buffers,
                        &dst_format,
                        None,
                        dst_buffers,
                    )
                    .unwrap_err(),
                    ErrorKind::NotInitialized,
                );
            }
            Err(_) => panic!(),
            Ok(_) => (),
        }

        initialize_with_instruction_set(set);
        assert!(describe_acceleration().is_ok());
    }
}

fn rgb_to_yuv_size_mode_pad(
    image_size: (u32, u32),
    src_format: &ImageFormat,
    dst_format: &ImageFormat,
    pad: (usize, usize, usize, usize),
    plane_ref: (PlaneRef, PlaneRef, PlaneRef),
) {
    let (src_pad, y_pad, u_pad, v_pad) = pad;
    let w = image_size.0 as usize;
    let h = image_size.1 as usize;
    let cw = w / 2;
    let ch = h / 2;

    let y_stride = w + y_pad;
    let u_stride = match dst_format.pixel_format {
        PixelFormat::I444 | PixelFormat::Nv12 => w + u_pad,
        PixelFormat::I420 => cw + u_pad,
        _ => unreachable!(),
    };
    let v_stride = match dst_format.pixel_format {
        PixelFormat::Nv12 => u_stride,
        PixelFormat::I420 => cw + v_pad,
        PixelFormat::I444 => w + v_pad,
        _ => unreachable!(),
    };
    let src_depth = get_depth(src_format.pixel_format);

    let src_stride = w * src_depth + src_pad;
    let src_size = src_stride * h;
    let dst_size = match dst_format.pixel_format {
        PixelFormat::Nv12 => y_stride * h + u_stride * ch,
        PixelFormat::I420 => y_stride * h + (u_stride + v_stride) * ch,
        PixelFormat::I444 => (y_stride + u_stride + v_stride) * h,
        _ => unreachable!(),
    };

    // Allocate and initialize input
    let mut src_image = vec![0_u8; src_size].into_boxed_slice();
    for (y, line) in RGB_SRC.iter().enumerate().take(h) {
        for (x, pixel) in line.iter().enumerate().take(w) {
            let p = y * src_stride + x * src_depth;
            match src_format.pixel_format {
                PixelFormat::Argb => {
                    src_image[p] = pixel[3];
                    src_image[p + 1] = pixel[0];
                    src_image[p + 2] = pixel[1];
                    src_image[p + 3] = pixel[2];
                }
                PixelFormat::Bgra => {
                    src_image[p] = pixel[2];
                    src_image[p + 1] = pixel[1];
                    src_image[p + 2] = pixel[0];
                    src_image[p + 3] = pixel[3];
                }
                _ => {
                    src_image[p] = pixel[2];
                    src_image[p + 1] = pixel[1];
                    src_image[p + 2] = pixel[0];
                }
            }
        }
    }

    // Allocate output
    let mut dst_image = vec![0_u8; dst_size].into_boxed_slice();

    // Compute strides
    let src_stride = if src_pad == 0 {
        STRIDE_AUTO
    } else {
        src_stride
    };

    let mut dst_strides = Vec::with_capacity(3);
    let mut dst_buffers: Vec<&mut [u8]> = Vec::with_capacity(3);
    dst_strides.push(if y_pad == 0 { STRIDE_AUTO } else { y_stride });
    match dst_format.pixel_format {
        PixelFormat::Nv12 => {
            if dst_format.num_planes == 1 {
                dst_buffers.push(&mut dst_image);
            } else if dst_format.num_planes == 2 {
                dst_strides.push(if u_pad == 0 { STRIDE_AUTO } else { u_stride });

                let (first, last) = dst_image.split_at_mut(y_stride * h);
                dst_buffers.push(first);
                dst_buffers.push(last);
            }
        }
        PixelFormat::I420 => {
            let (y_plane, chroma_planes) = dst_image.split_at_mut(y_stride * h);
            let (u_plane, v_plane) = chroma_planes.split_at_mut(ch * u_stride);

            dst_buffers.push(y_plane);
            dst_buffers.push(u_plane);
            dst_buffers.push(v_plane);
            dst_strides.push(if u_pad == 0 { STRIDE_AUTO } else { u_stride });
            dst_strides.push(if v_pad == 0 { STRIDE_AUTO } else { v_stride });
        }
        PixelFormat::I444 => {
            let (y_plane, chroma_planes) = dst_image.split_at_mut(y_stride * h);
            let (u_plane, v_plane) = chroma_planes.split_at_mut(u_stride * h);

            dst_buffers.push(y_plane);
            dst_buffers.push(u_plane);
            dst_buffers.push(v_plane);
            dst_strides.push(if u_pad == 0 { STRIDE_AUTO } else { u_stride });
            dst_strides.push(if v_pad == 0 { STRIDE_AUTO } else { v_stride });
        }
        _ => unreachable!(),
    }

    assert!(convert_image(
        image_size.0,
        image_size.1,
        src_format,
        Some(&[src_stride]),
        &[&src_image[..]],
        dst_format,
        Some(&dst_strides[..]),
        &mut dst_buffers[..],
    )
    .is_ok());

    if w == 0 || h == 0 {
        return;
    }

    check_plane(dst_buffers[0], plane_ref.0, w, h, y_stride);
    match dst_format.pixel_format {
        PixelFormat::Nv12 => {
            for (uv_row, (u_exp, v_exp)) in dst_image[y_stride * h..]
                .chunks(u_stride)
                .zip(plane_ref.1.iter().zip(plane_ref.2).take(ch))
            {
                let (payload, pad) = uv_row.split_at(w);
                assert!(payload
                    .chunks(2)
                    .zip(u_exp.iter().zip(v_exp.iter().take(cw)))
                    .all(|(uv, (&u, &v))| uv[0] == u && uv[1] == v));
                assert!(pad.iter().all(|&x| x == 0));
            }
        }
        PixelFormat::I420 => {
            check_plane(dst_buffers[1], plane_ref.1, cw, ch, u_stride);
            check_plane(dst_buffers[2], plane_ref.2, cw, ch, v_stride);
        }
        PixelFormat::I444 => {
            check_plane(dst_buffers[1], plane_ref.1, w, h, u_stride);
            check_plane(dst_buffers[2], plane_ref.2, w, h, v_stride);
        }
        _ => unreachable!(),
    }
}

fn rgb_to_yuv_size_mode(
    image_size: (u32, u32),
    src_format: &ImageFormat,
    dst_format: &ImageFormat,
    plane_ref: (PlaneRef, PlaneRef, PlaneRef),
) {
    const MAX_PAD: usize = 2;

    if dst_format.num_planes == 1 {
        for (src_pad, y_pad) in iproduct!(0..MAX_PAD, 0..MAX_PAD) {
            rgb_to_yuv_size_mode_pad(
                image_size,
                src_format,
                dst_format,
                (src_pad, y_pad, y_pad, y_pad),
                plane_ref,
            );
        }
    } else if dst_format.num_planes == 2 {
        for (src_pad, y_pad, uv_pad) in iproduct!(0..MAX_PAD, 0..MAX_PAD, 0..MAX_PAD) {
            rgb_to_yuv_size_mode_pad(
                image_size,
                src_format,
                dst_format,
                (src_pad, y_pad, uv_pad, uv_pad),
                plane_ref,
            );
        }
    } else if dst_format.num_planes == 3 {
        for pad in iproduct!(0..MAX_PAD, 0..MAX_PAD, 0..MAX_PAD, 0..MAX_PAD) {
            rgb_to_yuv_size_mode_pad(image_size, src_format, dst_format, pad, plane_ref);
        }
    }
}

fn rgb_to_yuv_size(
    image_size: (u32, u32),
    dst_image_format: &ImageFormat,
    plane_ref: (PlaneRef, PlaneRef, PlaneRef),
) {
    const SUPPORTED_PIXEL_FORMATS: &[PixelFormat] =
        &[PixelFormat::Argb, PixelFormat::Bgra, PixelFormat::Bgr];

    for pixel_format in SUPPORTED_PIXEL_FORMATS {
        rgb_to_yuv_size_mode(
            image_size,
            &ImageFormat {
                pixel_format: *pixel_format,
                color_space: ColorSpace::Rgb,
                num_planes: 1,
            },
            dst_image_format,
            plane_ref,
        );
    }
}

fn rgb_to_yuv_ok(pixel_format: PixelFormat, num_planes: u32) {
    const SUPPORTED_COLOR_SPACES: &[ColorSpace] = &[
        ColorSpace::Bt601,
        ColorSpace::Bt709,
        ColorSpace::Bt601FR,
        ColorSpace::Bt709FR,
    ];
    const MAX_WIDTH: u32 = 8;
    const MAX_HEIGHT: u32 = 8;

    let step = match pixel_format {
        PixelFormat::I444 => 1,
        _ => 2,
    };

    for color_space in SUPPORTED_COLOR_SPACES {
        let format = ImageFormat {
            pixel_format,
            color_space: *color_space,
            num_planes,
        };

        let plane_ref = if let PixelFormat::I444 = pixel_format {
            match color_space {
                ColorSpace::Bt601 => (Y_BT601_REF, CB_BT601_REF, CR_BT601_REF),
                ColorSpace::Bt709 => (Y_BT709_REF, CB_BT709_REF, CR_BT709_REF),
                ColorSpace::Bt601FR => (Y_BT601FR_REF, CB_BT601FR_REF, CR_BT601FR_REF),
                _ => (Y_BT709FR_REF, CB_BT709FR_REF, CR_BT709FR_REF),
            }
        } else {
            match color_space {
                ColorSpace::Bt601 => (Y_BT601_REF, CB2_BT601_REF, CR2_BT601_REF),
                ColorSpace::Bt709 => (Y_BT709_REF, CB2_BT709_REF, CR2_BT709_REF),
                ColorSpace::Bt601FR => (Y_BT601FR_REF, CB2_BT601FR_REF, CR2_BT601FR_REF),
                _ => (Y_BT709FR_REF, CB2_BT709FR_REF, CR2_BT709FR_REF),
            }
        };

        for width in (0..=MAX_WIDTH).step_by(step) {
            for height in (0..=MAX_HEIGHT).step_by(step) {
                rgb_to_yuv_size((width, height), &format, plane_ref);
            }
        }
    }
}

fn yuv_to_bgra_size_format_mode_stride(
    image_size: (u32, u32),
    src_format: &ImageFormat,
    dst_format: &ImageFormat,
    pad: (usize, usize, usize, usize),
) {
    let (y_pad, u_pad, v_pad, dst_pad) = pad;
    let w = image_size.0 as usize;
    let h = image_size.1 as usize;

    let y_stride = w + y_pad;
    let cw = w / 2;
    let ch = match src_format.pixel_format {
        PixelFormat::Nv12 | PixelFormat::I420 => h / 2,
        PixelFormat::I444 => h,
        _ => unreachable!(),
    };
    let u_stride = match src_format.pixel_format {
        PixelFormat::Nv12 | PixelFormat::I444 => w + u_pad,
        PixelFormat::I420 => cw + u_pad,
        _ => unreachable!(),
    };
    let v_stride = match src_format.pixel_format {
        PixelFormat::Nv12 => u_stride,
        PixelFormat::I420 => cw + v_pad,
        PixelFormat::I444 => w + v_pad,
        _ => unreachable!(),
    };
    let src_size = match src_format.pixel_format {
        PixelFormat::Nv12 => y_stride * h + u_stride * ch,
        PixelFormat::I444 | PixelFormat::I420 => y_stride * h + (u_stride + v_stride) * ch,
        _ => unreachable!(),
    };

    let dst_stride = w * 4 + dst_pad;
    let dst_size = dst_stride * h;
    let color_space_index = match src_format.color_space {
        ColorSpace::Bt601 => 0,
        ColorSpace::Bt709 => 1,
        ColorSpace::Bt601FR => 2,
        _ => 3,
    };

    // Allocate and initialize input
    let mut src_image = vec![0_u8; src_size].into_boxed_slice();
    let mut i = 0;
    while i < y_stride * h {
        for x in (0..w).step_by(2) {
            let index = (x >> 1) & 7;
            let luma = Y_SRC[color_space_index][index];

            src_image[i + x] = luma;
            src_image[i + x + 1] = luma;
        }

        i += y_stride;
    }

    match src_format.pixel_format {
        PixelFormat::Nv12 => {
            i = y_stride * h;
            while i < src_size {
                for x in (0..w).step_by(2) {
                    let index = (x >> 1) & 0x7;
                    src_image[i + x] = U_SRC[color_space_index][index];
                    src_image[i + x + 1] = V_SRC[color_space_index][index];
                }

                i += u_stride;
            }
        }
        PixelFormat::I420 => {
            let p0 = y_stride * h;
            let p1 = p0 + u_stride * ch;

            i = p0;
            while i < p1 {
                for x in (0..w).step_by(2) {
                    let pos = x >> 1;
                    let index = pos & 0x7;
                    src_image[i + pos] = U_SRC[color_space_index][index];
                }

                i += u_stride;
            }

            i = p1;
            while i < src_size {
                for x in (0..w).step_by(2) {
                    let pos = x >> 1;
                    let index = pos & 0x7;
                    src_image[i + pos] = V_SRC[color_space_index][index];
                }

                i += v_stride;
            }
        }
        PixelFormat::I444 => {
            let p0 = y_stride * h;
            let p1 = p0 + u_stride * ch;

            i = p0;
            while i < p1 {
                for x in 0..w {
                    let index = (x >> 1) & 0x7;
                    src_image[i + x] = U_SRC[color_space_index][index];
                }

                i += u_stride;
            }

            i = p1;
            while i < src_size {
                for x in 0..w {
                    let index = (x >> 1) & 0x7;
                    src_image[i + x] = V_SRC[color_space_index][index];
                }

                i += v_stride;
            }
        }
        _ => unreachable!(),
    }

    // Allocate output
    let mut dst_image = vec![0_u8; dst_size].into_boxed_slice();

    // Compute strides
    let dst_stride = if dst_pad == 0 {
        STRIDE_AUTO
    } else {
        dst_stride
    };

    let mut src_buffers: Vec<&[u8]> = Vec::with_capacity(3);
    let mut src_strides = Vec::with_capacity(3);
    src_strides.push(if y_pad == 0 { STRIDE_AUTO } else { y_stride });
    match src_format.pixel_format {
        PixelFormat::Nv12 => {
            if src_format.num_planes == 1 {
                src_buffers.push(&src_image);
            } else if src_format.num_planes == 2 {
                src_strides.push(if u_pad == 0 { STRIDE_AUTO } else { u_stride });

                let (first, last) = src_image.split_at(y_stride * h);
                src_buffers.push(first);
                src_buffers.push(last);
            }
        }
        PixelFormat::I420 | PixelFormat::I444 => {
            if src_format.num_planes == 3 {
                let y_size = y_stride * h;
                let u_size = u_stride * ch;

                src_strides.push(if u_pad == 0 { STRIDE_AUTO } else { u_stride });
                src_strides.push(if v_pad == 0 { STRIDE_AUTO } else { v_stride });
                src_buffers.push(&src_image[0..y_size]);
                src_buffers.push(&src_image[y_size..y_size + u_size]);
                src_buffers.push(&src_image[y_size + u_size..]);
            }
        }
        _ => unreachable!(),
    }

    let mut expected_row = vec![0_i32; 3 * w].into_boxed_slice();
    for (x, pixel) in expected_row.chunks_exact_mut(3).enumerate() {
        let index = (x >> 1) & 7;

        // Expected blue
        pixel[0] = if ((index >> 2) & 1) == 0 { 0 } else { 255 };
        // Expected green
        pixel[1] = if ((index >> 1) & 1) == 0 { 0 } else { 255 };
        // Expected red
        pixel[2] = if (index & 1) == 0 { 0 } else { 255 };
    }

    assert!(convert_image(
        image_size.0,
        image_size.1,
        src_format,
        Some(&src_strides[..]),
        &src_buffers[..],
        dst_format,
        Some(&[dst_stride]),
        &mut [&mut dst_image[..]],
    )
    .is_ok());

    i = 0;
    while i < dst_size {
        let pack_stride = 4 * w;

        for col in 0..w {
            let src = 4 * col;
            let dst = 3 * col;
            assert!(
                (i32::from(dst_image[i + src]) - expected_row[dst]).abs() <= 2
                    && (i32::from(dst_image[i + src + 1]) - expected_row[dst + 1]).abs() <= 2
                    && (i32::from(dst_image[i + src + 2]) - expected_row[dst + 2]).abs() <= 2
                    && dst_image[i + src + 3] == 255
            );
        }

        for col in 0..dst_pad {
            assert_eq!(dst_image[i + pack_stride + col], 0);
        }

        i += pack_stride + dst_pad;
    }
}

fn yuv_to_bgra_size_format_mode(
    image_size: (u32, u32),
    src_format: &ImageFormat,
    dst_format: &ImageFormat,
) {
    const MAX_PAD: usize = 4;

    if src_format.num_planes == 1 {
        for (y_pad, dst_pad) in iproduct!(0..MAX_PAD, 0..MAX_PAD) {
            yuv_to_bgra_size_format_mode_stride(
                image_size,
                src_format,
                dst_format,
                (y_pad, y_pad, y_pad, dst_pad),
            );
        }
    } else if src_format.num_planes == 2 {
        for (y_pad, uv_pad, dst_pad) in iproduct!(0..MAX_PAD, 0..MAX_PAD, 0..MAX_PAD) {
            yuv_to_bgra_size_format_mode_stride(
                image_size,
                src_format,
                dst_format,
                (y_pad, uv_pad, uv_pad, dst_pad),
            );
        }
    } else if src_format.num_planes == 3 {
        for pad in iproduct!(0..MAX_PAD, 0..MAX_PAD, 0..MAX_PAD, 0..MAX_PAD) {
            yuv_to_bgra_size_format_mode_stride(image_size, src_format, dst_format, pad);
        }
    }
}

fn yuv_to_bgra_ok(pixel_format: PixelFormat, num_planes: u32) {
    const SUPPORTED_COLOR_SPACES: &[ColorSpace] = &[
        ColorSpace::Bt601,
        ColorSpace::Bt709,
        ColorSpace::Bt601FR,
        ColorSpace::Bt709FR,
    ];
    const MAX_WIDTH: u32 = 34;
    const MAX_HEIGHT: u32 = 4;

    let step = match pixel_format {
        PixelFormat::I444 => 1,
        _ => 2,
    };
    let dst_format = ImageFormat {
        pixel_format: PixelFormat::Bgra,
        color_space: ColorSpace::Rgb,
        num_planes: 1,
    };

    for color_space in SUPPORTED_COLOR_SPACES {
        let src_format = ImageFormat {
            pixel_format,
            color_space: *color_space,
            num_planes,
        };

        for width in (0..=MAX_WIDTH).step_by(step) {
            for height in (0..=MAX_HEIGHT).step_by(step) {
                yuv_to_bgra_size_format_mode((width, height), &src_format, &dst_format);
            }
        }
    }
}

fn yuv_to_rgb_size_format_mode_stride(
    image_size: (u32, u32),
    src_format: &ImageFormat,
    dst_format: &ImageFormat,
    pad: (usize, usize, usize),
) {
    let (y_pad, u_pad, dst_pad) = pad;
    let w = image_size.0 as usize;
    let h = image_size.1 as usize;

    let y_stride = w + y_pad;
    let u_stride = w + u_pad;
    let ch = h / 2;
    let src_size = y_stride * h + u_stride * ch;

    let dst_stride = w * 3 + dst_pad;
    let dst_size = dst_stride * h;
    let color_space_index = match src_format.color_space {
        ColorSpace::Bt601 => 0,
        ColorSpace::Bt709 => 1,
        ColorSpace::Bt601FR => 2,
        _ => 3,
    };

    let mut src_image = vec![0_u8; src_size].into_boxed_slice();
    let mut i = 0;
    while i < y_stride * h {
        for x in (0..w).step_by(2) {
            let index = (x >> 1) & 7;
            let luma = Y_SRC[color_space_index][index];

            src_image[i + x] = luma;
            src_image[i + x + 1] = luma;
        }

        i += y_stride;
    }

    while i < src_size {
        for x in (0..w).step_by(2) {
            let index = (x >> 1) & 0x7;
            src_image[i + x] = U_SRC[color_space_index][index];
            src_image[i + x + 1] = V_SRC[color_space_index][index];
        }

        i += u_stride;
    }

    let mut dst_image = vec![0_u8; dst_size].into_boxed_slice();
    let dst_stride = if dst_pad == 0 {
        STRIDE_AUTO
    } else {
        dst_stride
    };

    let mut src_buffers: Vec<&[u8]> = Vec::with_capacity(2);
    let mut src_strides = Vec::with_capacity(2);
    src_strides.push(if y_pad == 0 { STRIDE_AUTO } else { y_stride });
    if src_format.num_planes == 1 {
        src_buffers.push(&src_image);
    } else if src_format.num_planes == 2 {
        src_strides.push(if u_pad == 0 { STRIDE_AUTO } else { u_stride });

        let (first, last) = src_image.split_at(y_stride * h);
        src_buffers.push(first);
        src_buffers.push(last);
    }

    let mut expected_row = vec![0_i32; 3 * w].into_boxed_slice();
    for (x, pixel) in expected_row.chunks_exact_mut(3).enumerate() {
        let index = (x >> 1) & 7;

        pixel[0] = if (index & 1) == 0 { 0 } else { 255 };
        pixel[1] = if ((index >> 1) & 1) == 0 { 0 } else { 255 };
        pixel[2] = if ((index >> 2) & 1) == 0 { 0 } else { 255 };
    }

    assert!(convert_image(
        image_size.0,
        image_size.1,
        src_format,
        Some(&src_strides[..]),
        &src_buffers[..],
        dst_format,
        Some(&[dst_stride]),
        &mut [&mut dst_image[..]],
    )
    .is_ok());

    let pack_stride = 3 * w;
    i = 0;
    while i < dst_size {
        for col in 0..w {
            let off = 3 * col;
            assert!(
                (i32::from(dst_image[i + off]) - expected_row[off]).abs() <= 2
                    && (i32::from(dst_image[i + off + 1]) - expected_row[off + 1]).abs() <= 2
                    && (i32::from(dst_image[i + off + 2]) - expected_row[off + 2]).abs() <= 2
            );
        }

        for col in 0..dst_pad {
            assert_eq!(dst_image[i + pack_stride + col], 0);
        }

        i += pack_stride + dst_pad;
    }
}

fn yuv_to_rgb_size_format_mode(
    image_size: (u32, u32),
    src_format: &ImageFormat,
    dst_format: &ImageFormat,
) {
    const MAX_PAD: usize = 4;

    if src_format.num_planes == 1 {
        for (y_pad, dst_pad) in iproduct!(0..MAX_PAD, 0..MAX_PAD) {
            yuv_to_rgb_size_format_mode_stride(
                image_size,
                src_format,
                dst_format,
                (y_pad, y_pad, dst_pad),
            );
        }
    } else {
        for (y_pad, uv_pad, dst_pad) in iproduct!(0..MAX_PAD, 0..MAX_PAD, 0..MAX_PAD) {
            yuv_to_rgb_size_format_mode_stride(
                image_size,
                src_format,
                dst_format,
                (y_pad, uv_pad, dst_pad),
            );
        }
    }
}

fn yuv_to_rgb_ok(num_planes: u32) {
    const SUPPORTED_COLOR_SPACES: &[ColorSpace] = &[
        ColorSpace::Bt601,
        ColorSpace::Bt709,
        ColorSpace::Bt601FR,
        ColorSpace::Bt709FR,
    ];
    const MAX_WIDTH: u32 = 34;
    const MAX_HEIGHT: u32 = 4;

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

    for color_space in SUPPORTED_COLOR_SPACES {
        let src_format = ImageFormat {
            pixel_format: PixelFormat::Nv12,
            color_space: *color_space,
            num_planes,
        };

        for width in (0..=MAX_WIDTH).step_by(2) {
            for height in (0..=MAX_HEIGHT).step_by(2) {
                yuv_to_rgb_size_format_mode((width, height), &src_format, &dst_format);
            }
        }
    }
}

fn rgb_conversion_errors(src_pixel_format: PixelFormat, dst_pixel_format: PixelFormat) {
    const WIDTH: u32 = 33;
    const HEIGHT: u32 = 1;

    let src_depth = get_depth(src_pixel_format);
    let dst_depth = get_depth(dst_pixel_format);
    let src_stride: usize = (WIDTH as usize) * src_depth;
    let dst_stride: usize = (WIDTH as usize) * dst_depth;
    let src_size: usize = src_stride * (HEIGHT as usize);
    let dst_size: usize = dst_stride * (HEIGHT as usize);

    let src_image = vec![0_u8; src_size].into_boxed_slice();
    let mut dst_image = vec![0_u8; dst_size].into_boxed_slice();

    for num_planes in 0..4 {
        let src_format = ImageFormat {
            pixel_format: src_pixel_format,
            color_space: ColorSpace::Rgb,
            num_planes: 1,
        };
        let dst_format = ImageFormat {
            pixel_format: dst_pixel_format,
            color_space: ColorSpace::Rgb,
            num_planes,
        };

        let src_buffers = &[&src_image[..]];
        let dst_buffers = &mut [&mut dst_image[..]];

        let mut expected = Ok(());
        set_expected!(
            expected,
            is_valid_format(&src_format, WIDTH, HEIGHT),
            ErrorKind::InvalidValue
        );
        set_expected!(
            expected,
            is_valid_format(&dst_format, WIDTH, HEIGHT),
            ErrorKind::InvalidValue
        );

        let status = convert_image(
            WIDTH,
            HEIGHT,
            &src_format,
            Some(&[src_stride; 1]),
            src_buffers,
            &dst_format,
            Some(&[dst_stride; 1]),
            dst_buffers,
        );

        assert_eq!(expected.is_ok(), status.is_ok());
        match status {
            Ok(_) => check_bounds(
                WIDTH,
                HEIGHT,
                &src_format,
                src_buffers,
                &dst_format,
                dst_buffers,
            ),
            Err(err) => check_err(err, expected.unwrap_err()),
        }
    }
}

fn rgb_conversion_ok(src_pixel_format: PixelFormat, dst_pixel_format: PixelFormat) {
    const MAX_WIDTH: u32 = 49;
    const MAX_HEIGHT: u32 = 8;
    const MAX_PAD: usize = 3;

    let src_depth = get_depth(src_pixel_format);
    let dst_depth = get_depth(dst_pixel_format);

    let src_format = ImageFormat {
        pixel_format: src_pixel_format,
        color_space: ColorSpace::Rgb,
        num_planes: 1,
    };
    let dst_format = ImageFormat {
        pixel_format: dst_pixel_format,
        color_space: ColorSpace::Rgb,
        num_planes: 1,
    };
    let mut rng = rand::thread_rng();

    for (width, height, src_pad, dst_pad) in
        iproduct!(0..=MAX_WIDTH, 0..=MAX_HEIGHT, 0..MAX_PAD, 0..MAX_PAD)
    {
        let w = width as usize;
        let h = height as usize;
        let src_stride = src_depth * w + src_pad;
        let dst_stride = dst_depth * w + dst_pad;

        let src_strides = if src_pad == 0 {
            STRIDE_AUTO
        } else {
            src_stride
        };
        let dst_strides = if dst_pad == 0 {
            STRIDE_AUTO
        } else {
            dst_stride
        };

        let mut src_image = vec![0_u8; src_stride * h].into_boxed_slice();
        let mut dst_image = vec![0_u8; dst_stride * h].into_boxed_slice();
        for y in 0..h {
            for x in 0..w {
                let offset = y * src_stride + x * src_depth;

                src_image[offset] = rng.gen::<u8>();
                src_image[offset + 1] = rng.gen::<u8>();
                src_image[offset + 2] = rng.gen::<u8>();
                if src_depth == 4 {
                    src_image[offset + 3] = 255;
                }
            }
        }

        assert!(convert_image(
            width,
            height,
            &src_format,
            Some(&[src_strides]),
            &[&src_image[..]],
            &dst_format,
            Some(&[dst_strides]),
            &mut [&mut dst_image[..]],
        )
        .is_ok());

        for y in 0..h {
            for x in 0..w {
                let input_index = y * src_stride + x * src_depth;
                let output_index = y * dst_stride + x * dst_depth;

                assert_eq!(dst_image[output_index], src_image[input_index + 2]);
                assert_eq!(dst_image[output_index + 1], src_image[input_index + 1]);
                assert_eq!(dst_image[output_index + 2], src_image[input_index]);
                if dst_depth == 4 {
                    assert_eq!(dst_image[output_index + 3], 255);
                }
            }
        }
    }
}

fn rgb_to_nv12_ok() {
    rgb_to_yuv_ok(PixelFormat::Nv12, 1);
    rgb_to_yuv_ok(PixelFormat::Nv12, 2);
}

fn rgb_to_i420_ok() {
    rgb_to_yuv_ok(PixelFormat::I420, 3);
}

fn rgb_to_i444_ok() {
    rgb_to_yuv_ok(PixelFormat::I444, 3);
}

fn i420_to_bgra_ok() {
    yuv_to_bgra_ok(PixelFormat::I420, 3);
}

fn nv12_to_bgra_ok() {
    yuv_to_bgra_ok(PixelFormat::Nv12, 1);
    yuv_to_bgra_ok(PixelFormat::Nv12, 2);
}

fn i444_to_bgra_ok() {
    yuv_to_bgra_ok(PixelFormat::I444, 3);
}

fn nv12_to_rgb_ok() {
    yuv_to_rgb_ok(1);
    yuv_to_rgb_ok(2);
}

fn rgb_ok() {
    rgb_conversion_ok(PixelFormat::Rgb, PixelFormat::Bgra);
    rgb_conversion_ok(PixelFormat::Bgra, PixelFormat::Rgb);
    rgb_conversion_ok(PixelFormat::Bgr, PixelFormat::Rgb);
}

fn rgb_errors() {
    rgb_conversion_errors(PixelFormat::Rgb, PixelFormat::Bgra);
    rgb_conversion_errors(PixelFormat::Bgra, PixelFormat::Rgb);
    rgb_conversion_errors(PixelFormat::Bgr, PixelFormat::Rgb);
}

fn rgb_to_yuv_errors(pixel_format: PixelFormat) {
    let (w, h) = match pixel_format {
        PixelFormat::I444 => (33, 3),
        _ => (34, 2),
    };
    let cw = match pixel_format {
        PixelFormat::I420 => w / 2,
        _ => w,
    } as usize;
    let ch = match pixel_format {
        PixelFormat::I444 => h,
        _ => h / 2,
    } as usize;
    let y_size = (w as usize) * (h as usize);
    let uv_size = cw * ch;

    let slices = &[0, y_size + uv_size, y_size, 0];
    let mut y_plane = match pixel_format {
        PixelFormat::Nv12 => vec![0_u8; y_size + uv_size].into_boxed_slice(),
        _ => vec![0_u8; y_size].into_boxed_slice(),
    };
    let mut u_plane = vec![0_u8; uv_size].into_boxed_slice();
    let mut v_plane = vec![0_u8; uv_size].into_boxed_slice();

    for src_pixel_format in PIXEL_FORMATS {
        let src_stride = get_depth(*src_pixel_format) * (w as usize);
        let src_size = src_stride * (h as usize);
        let src_image = vec![0_u8; src_size].into_boxed_slice();

        for (num_planes, src_color_space, dst_color_space) in
            iproduct!(0..4, COLOR_SPACES, COLOR_SPACES)
        {
            let src_format = ImageFormat {
                pixel_format: *src_pixel_format,
                color_space: *src_color_space,
                num_planes: 1,
            };
            let dst_format = ImageFormat {
                pixel_format,
                color_space: *dst_color_space,
                num_planes,
            };

            let src_buffers = &[&src_image[..]];
            let mut dst_strides = Vec::with_capacity(1);
            let mut dst_buffers = Vec::with_capacity(1);

            dst_strides.push(w as usize);
            if let PixelFormat::Nv12 = pixel_format {
                dst_buffers.push(&mut y_plane[..slices[num_planes as usize]]);
                dst_buffers.push(&mut u_plane[..]);
                if num_planes > 1 {
                    dst_strides.push(cw);
                }
            } else {
                dst_strides.push(cw);
                dst_strides.push(cw);
                dst_buffers.push(&mut y_plane[..]);
                dst_buffers.push(&mut u_plane[..]);
                dst_buffers.push(&mut v_plane[..]);
            }

            let mut expected = Ok(());
            let src_pf = *src_pixel_format as u32;
            let src_cs = *src_color_space as u32;
            let dst_cs = *dst_color_space as u32;
            let src_pf_rgb = src_pf < PIXEL_FORMAT_I444;
            let src_cs_rgb = src_cs == COLOR_SPACE_RGB;
            let dst_cs_rgb = dst_cs == COLOR_SPACE_RGB;

            set_expected!(expected, !src_pf_rgb && src_cs_rgb, ErrorKind::InvalidValue);
            set_expected!(expected, src_pf_rgb && !src_cs_rgb, ErrorKind::InvalidValue);
            set_expected!(expected, dst_cs_rgb, ErrorKind::InvalidValue);
            set_expected!(
                expected,
                is_valid_format(&src_format, w, h),
                ErrorKind::InvalidValue
            );
            set_expected!(
                expected,
                is_valid_format(&dst_format, w, h),
                ErrorKind::InvalidValue
            );
            set_expected!(
                expected,
                src_pf != PIXEL_FORMAT_ARGB
                    && src_pf != PIXEL_FORMAT_BGRA
                    && src_pf != PIXEL_FORMAT_BGR,
                ErrorKind::InvalidOperation
            );
            set_expected!(
                expected,
                src_cs != COLOR_SPACE_RGB,
                ErrorKind::InvalidOperation
            );

            let status = convert_image(
                w,
                h,
                &src_format,
                None,
                src_buffers,
                &dst_format,
                Some(&dst_strides),
                &mut dst_buffers,
            );

            assert_eq!(expected.is_ok(), status.is_ok());
            match status {
                Ok(_) => check_bounds(
                    w,
                    h,
                    &src_format,
                    src_buffers,
                    &dst_format,
                    &mut dst_buffers,
                ),
                Err(err) => check_err(err, expected.unwrap_err()),
            }
        }
    }
}

fn yuv_to_rgb_errors(pixel_format: PixelFormat) {
    let (w, h) = match pixel_format {
        PixelFormat::I444 => (33, 3),
        _ => (34, 2),
    };
    let cw = match pixel_format {
        PixelFormat::I420 => w / 2,
        _ => w,
    } as usize;
    let ch = match pixel_format {
        PixelFormat::I444 => h,
        _ => h / 2,
    } as usize;
    let y_size = (w as usize) * (h as usize);
    let uv_size = cw * ch;
    let dst_size = (w as usize) * (h as usize) * 4;

    let slices = &[0, y_size + uv_size, y_size, 0];
    let y_plane = match pixel_format {
        PixelFormat::Nv12 => vec![0_u8; y_size + uv_size].into_boxed_slice(),
        _ => vec![0_u8; y_size].into_boxed_slice(),
    };
    let u_plane = vec![0_u8; uv_size].into_boxed_slice();
    let v_plane = vec![0_u8; uv_size].into_boxed_slice();
    let mut dst_image = vec![0_u8; dst_size].into_boxed_slice();

    for (num_planes, dst_pixel_format, dst_color_space, src_color_space) in
        iproduct!(0..4, PIXEL_FORMATS, COLOR_SPACES, COLOR_SPACES)
    {
        let src_format = ImageFormat {
            pixel_format,
            color_space: *src_color_space,
            num_planes,
        };
        let dst_format = ImageFormat {
            pixel_format: *dst_pixel_format,
            color_space: *dst_color_space,
            num_planes: 1,
        };

        let mut src_strides = Vec::with_capacity(1);
        let mut src_buffers = Vec::with_capacity(1);
        let dst_buffers = &mut [&mut dst_image[..]];

        src_strides.push(w as usize);
        if let PixelFormat::Nv12 = pixel_format {
            src_buffers.push(&y_plane[..slices[num_planes as usize]]);
            src_buffers.push(&u_plane[..]);
            if num_planes > 1 {
                src_strides.push(cw);
            }
        } else {
            src_strides.push(cw);
            src_strides.push(cw);
            src_buffers.push(&y_plane[..]);
            src_buffers.push(&u_plane[..]);
            src_buffers.push(&v_plane[..]);
        }

        let mut expected = Ok(());
        let src_pf = pixel_format as u32;
        let dst_pf = *dst_pixel_format as u32;
        let dst_cs = *dst_color_space as u32;
        let src_cs = *src_color_space as u32;
        let dst_pf_rgb = dst_pf < PIXEL_FORMAT_I444;
        let dst_cs_rgb = dst_cs == COLOR_SPACE_RGB;
        let src_cs_rgb = src_cs == COLOR_SPACE_RGB;

        set_expected!(expected, src_cs_rgb, ErrorKind::InvalidValue);
        set_expected!(expected, !dst_pf_rgb && dst_cs_rgb, ErrorKind::InvalidValue);
        set_expected!(expected, dst_pf_rgb && !dst_cs_rgb, ErrorKind::InvalidValue);
        set_expected!(
            expected,
            is_valid_format(&src_format, w, h),
            ErrorKind::InvalidValue
        );
        set_expected!(
            expected,
            is_valid_format(&dst_format, w, h),
            ErrorKind::InvalidValue
        );
        set_expected!(
            expected,
            !(dst_pf == PIXEL_FORMAT_BGRA
                || (src_pf == PIXEL_FORMAT_NV12 && dst_pf == PIXEL_FORMAT_RGB)),
            ErrorKind::InvalidOperation
        );
        set_expected!(
            expected,
            dst_cs != COLOR_SPACE_RGB,
            ErrorKind::InvalidOperation
        );

        let status = convert_image(
            w,
            h,
            &src_format,
            Some(&src_strides),
            &src_buffers,
            &dst_format,
            None,
            dst_buffers,
        );

        assert_eq!(expected.is_ok(), status.is_ok());
        match status {
            Ok(_) => check_bounds(w, h, &src_format, &src_buffers, &dst_format, dst_buffers),
            Err(err) => check_err(err, expected.unwrap_err()),
        }
    }
}

fn buffers_size() {
    const WIDTH: u32 = 4098;
    const HEIGHT: u32 = 258;
    let buffers_size = &mut [0_usize; MAX_NUMBER_OF_PLANES as usize];

    for (num_planes, pixel_format) in iproduct!(0..=MAX_NUMBER_OF_PLANES + 1, PIXEL_FORMATS) {
        let pf = *pixel_format as u32;
        let format = ImageFormat {
            pixel_format: *pixel_format,
            color_space: ColorSpace::Rgb,
            num_planes,
        };

        // Invalid width
        let mut expected = Ok(());
        set_expected!(expected, pf >= PIXEL_FORMAT_I422, ErrorKind::InvalidValue);
        set_expected!(
            expected,
            is_valid_format(&format, WIDTH, HEIGHT),
            ErrorKind::InvalidValue
        );

        let status = get_buffers_size(1, HEIGHT, &format, None, buffers_size);
        match status {
            Ok(_) => assert!(expected.is_ok()),
            Err(err) => check_err(err, expected.err().unwrap()),
        }

        // Invalid height
        let mut expected = Ok(());
        set_expected!(expected, pf >= PIXEL_FORMAT_I420, ErrorKind::InvalidValue);
        set_expected!(
            expected,
            is_valid_format(&format, WIDTH, HEIGHT),
            ErrorKind::InvalidValue
        );

        let status = get_buffers_size(WIDTH, 1, &format, None, buffers_size);
        match status {
            Ok(_) => assert!(expected.is_ok()),
            Err(err) => check_err(err, expected.err().unwrap()),
        }

        // Test size is valid
        let mut expected = Ok(());
        set_expected!(
            expected,
            is_valid_format(&format, WIDTH, HEIGHT),
            ErrorKind::InvalidValue
        );

        let status = get_buffers_size(WIDTH, HEIGHT, &format, None, buffers_size);
        assert_eq!(expected.is_ok(), status.is_ok());

        match status {
            Ok(_) => {
                let pf = pf as usize;
                let num_planes = num_planes as usize;
                let area = (WIDTH as usize) * (HEIGHT as usize);

                if num_planes == 1 {
                    assert_eq!(
                        buffers_size[0],
                        (area * NUM_LOG2_DEN[pf][0]) >> NUM_LOG2_DEN[pf][1]
                    );
                } else {
                    let mut strides = Vec::new();

                    for (i, buffer_size) in buffers_size.iter().enumerate().take(num_planes) {
                        let mul = NUM_LOG2_DEN_PER_PLANE[pf][2 * i];
                        let shf = NUM_LOG2_DEN_PER_PLANE[pf][2 * i + 1];
                        assert_eq!(*buffer_size, (area * mul) >> shf);

                        let row = match pixel_format {
                            PixelFormat::I422 | PixelFormat::I420 => i32::from(i > 0),
                            _ => 0,
                        };

                        strides.push(((WIDTH as usize) * mul) >> row);
                    }

                    assert!(get_buffers_size(
                        WIDTH,
                        HEIGHT,
                        &format,
                        Some(&strides[..]),
                        buffers_size
                    )
                    .is_ok());

                    for (i, buffer_size) in buffers_size.iter().enumerate().take(num_planes) {
                        assert_eq!(
                            *buffer_size,
                            (area * NUM_LOG2_DEN_PER_PLANE[pf][2 * i])
                                >> NUM_LOG2_DEN_PER_PLANE[pf][2 * i + 1]
                        );
                    }
                }

                // empty buffer vector should return not enough data
                assert!(
                    get_buffers_size(WIDTH, HEIGHT, &format, None, &mut buffers_size[..0]).is_err()
                );
            }
            Err(err) => check_err(err, expected.err().unwrap()),
        }
    }
}

#[cfg(target_arch = "x86_64")]
#[cfg(not(tarpaulin))]
fn over_4gb() {
    // In this test the output image will larger than 4GB:
    // width = 536870944 * 2 * 4 = 4294967552
    const WIDTH: u32 = 0x2000_0020;
    const HEIGHT: u32 = 2;
    const LUMA_SIZE: usize = (WIDTH as usize) * (HEIGHT as usize);
    const EXPECTED_SRC_BUFFER_SIZE: usize = (LUMA_SIZE * 3) / 2;
    const EXPECTED_DST_BUFFER_SIZE: usize = LUMA_SIZE * 4;
    const PAGE_SIZE: usize = 4096;
    const MAX_LUMA: u8 = 235;
    const HALF_CHROMA: u8 = 128;

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

    let src_size = &mut [0_usize; 1];
    assert!(get_buffers_size(WIDTH, HEIGHT, &src_format, None, src_size).is_ok());
    assert_eq!(src_size[0], EXPECTED_SRC_BUFFER_SIZE);

    let dst_size = &mut [0_usize; 1];
    assert!(get_buffers_size(WIDTH, HEIGHT, &dst_format, None, dst_size).is_ok());
    assert_eq!(dst_size[0], EXPECTED_DST_BUFFER_SIZE);

    #[allow(unsafe_code)]
    unsafe {
        let src_layout = Layout::from_size_align_unchecked(EXPECTED_SRC_BUFFER_SIZE, 1);
        let src_ptr = alloc(src_layout);
        if src_ptr.is_null() {
            return;
        }

        write_bytes::<u8>(src_ptr, MAX_LUMA, LUMA_SIZE);
        write_bytes::<u8>(
            src_ptr.add(LUMA_SIZE),
            HALF_CHROMA,
            EXPECTED_SRC_BUFFER_SIZE - LUMA_SIZE,
        );

        let dst_layout = Layout::from_size_align_unchecked(EXPECTED_DST_BUFFER_SIZE, 1);
        let dst_ptr = alloc_zeroed(dst_layout);
        if !dst_ptr.is_null() {
            let src_image: &[u8] = from_raw_parts_mut(src_ptr, EXPECTED_SRC_BUFFER_SIZE);
            let dst_image = from_raw_parts_mut(dst_ptr, EXPECTED_DST_BUFFER_SIZE);

            // Touch output
            for i in (0..EXPECTED_DST_BUFFER_SIZE).step_by(PAGE_SIZE) {
                dst_image[i] = 0;
            }

            dst_image[dst_image.len() - 1] = 0;

            let src_buffers = &[src_image];
            let dst_buffers = &mut [&mut *dst_image];

            assert!(convert_image(
                WIDTH,
                HEIGHT,
                &src_format,
                None,
                src_buffers,
                &dst_format,
                None,
                dst_buffers,
            )
            .is_ok());

            // Check all samples are correct
            #[allow(clippy::cast_ptr_alignment)]
            let dst_image_as_u64: &[u64] =
                from_raw_parts(dst_ptr as *const u64, EXPECTED_DST_BUFFER_SIZE / 8);
            assert!(dst_image_as_u64.iter().all(|&x| x == std::u64::MAX));

            dealloc(dst_ptr, dst_layout);
        }

        dealloc(src_ptr, src_layout);
    }
}

#[test]
fn functional_tests() {
    for set in &SETS {
        bootstrap(set);

        buffers_size();

        yuv_to_rgb_errors(PixelFormat::Nv12);
        yuv_to_rgb_errors(PixelFormat::I420);
        yuv_to_rgb_errors(PixelFormat::I444);
        rgb_to_yuv_errors(PixelFormat::Nv12);
        rgb_to_yuv_errors(PixelFormat::I420);
        rgb_to_yuv_errors(PixelFormat::I444);
        rgb_errors();

        i444_to_bgra_ok();
        i420_to_bgra_ok();
        nv12_to_bgra_ok();
        rgb_to_i444_ok();
        rgb_to_i420_ok();
        rgb_to_nv12_ok();
        nv12_to_rgb_ok();

        rgb_ok();
    }

    #[cfg(target_arch = "x86_64")]
    #[cfg(not(tarpaulin))]
    over_4gb();
}


/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

import { ILogData, IValidator } from "../core/ext-types"
import { IBuiltStage, IBuiltStageWrapper } from "../core/pipeline-types";
const fs = require('fs');
const readline = require('readline');
const DATA_PATH = "data/"

/*
 * Cloudwatch validator
 * Sets up and tears down cloudwatch
 */

interface ICloudwatchValidatorConfig {
    "dataFilename": string;
    "batchSize": number;
}

const cloudwatchValidator: (ICloudwatchValidatorConfig) => IConfiguredValidator = {
    name: "cloudwatch",

    subtreeModifier: (subtree: IBuiltStage) => true, /* modify subtree, potentially inserting other BuiltStageWrappers in subtree */

    setup: async (root: IBuiltStage, subtree: IBuiltStage) => {
        return true;
    },

    validation: async (root: IBuiltStage, subtree: IBuiltStage) => {
        return {
            isValidationSuccess: true,
            // Other data can be added here for validation metric collection.
            validationData: {},
        };
    },

    breakdown: async (root: IBuiltStage, subtree: IBuiltStage) => {
        return true;
    },

    isValidationAsync: false,
};

export default cloudwatchValidator;


///
/// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License").
/// You may not use this file except in compliance with the License.
/// A copy of the License is located at
///
///  http://aws.amazon.com/apache2.0
///
/// or in the "license" file accompanying this file. This file is distributed
/// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
/// express or implied. See the License for the specific language governing
/// permissions and limitations under the License.
///
use std::env;
use std::fs::File;
use std::io::Write;

const CR: u8 = b'\r';
const LF: u8 = b'\n';
const SP: u8 = b' ';
const HT: u8 = b'\t';
const DEL: u8 = 127;
const NIL: u8 = 0;
const TCHAR: &[u8] = b"-_.!#$%&'*+^`|~";

fn format_char(b: u8) -> String {
    use std::fmt::Write;

    let mut str = String::new();
    match b {
        b'\r' => str.push_str("\\r"),
        b'\n' => str.push_str("\\n"),
        b'\t' => str.push_str("\\t"),
        _ if is_rfc_vchar(b) => {
            str.push(b as char);
        }
        _ => {
            write!(str, "\\{:#04x}", b).expect("Writing to strings is infallible");
        }
    };
    str
}

fn generate_table(name: &str, predicate: fn(u8) -> bool) -> String {
    use std::fmt::Write;

    let mut t = String::new();
    write!(t, "pub static {}: [bool; 256] = [\r\n    ", name).ok();
    for i in 0..=255_u8 {
        write!(t, " {} /* {} */,", (predicate)(i), format_char(i)).ok();
        if i % 4 == 3 {
            write!(t, "\r\n    ").ok();
        }
    }
    write!(t, "];\r\n").ok();
    t
}

fn is_valid_uri_char(b: u8) -> bool {
    // disable CTL in URL
    b > SP && b != DEL
}

fn is_rfc_tchar(b: u8) -> bool {
    b.is_ascii_alphanumeric() || TCHAR.contains(&b)
}

fn is_rfc_vchar(b: u8) -> bool {
    (SP..DEL).contains(&b)
}

fn is_rfc_obs_text(b: u8) -> bool {
    b >= 0x80
}

fn is_rfc_whitespace(b: u8) -> bool {
    b == SP || b == HT
}

fn is_bad_http_character(b: u8) -> bool {
    // these characters are absolutely unacceptable in header names/values
    // it means multi-line headers are not accepted too
    b == CR || b == LF || b == NIL
}

type TableGenerator = fn(u8) -> bool;

fn generate_lookup_tables() {
    use std::fmt::Write;

    let tables: &[(&str, TableGenerator)] = &[
        ("TCHAR_TABLE", |b| is_rfc_tchar(b)),
        ("VCHAR", |b| is_rfc_vchar(b)),
        ("BAD_CHARACTERS", |b| is_bad_http_character(b)),
        ("RFC_WHITE_SPACE", |b| is_rfc_whitespace(b)),
        ("VALID_URI_CHARS", |b| is_valid_uri_char(b)),
        ("HEADER_VALUE_CHARS", |b| {
            is_rfc_vchar(b) || is_rfc_obs_text(b) || is_rfc_whitespace(b)
        }),
    ];

    let mut char_tables = String::new();
    for table in tables {
        write!(char_tables, "{}", generate_table(table.0, table.1))
            .expect("Writing to strings is infallible");
    }
    let out_dir = env::var("OUT_DIR").expect("OUT_DIR variable is not set");
    let mut file =
        File::create(format!("{}/char_tables.rs", out_dir)).expect("Cannot open file for writing");
    file.write_all(char_tables.as_bytes())
        .expect("Cannot write to file");
}

fn main() {
    generate_lookup_tables();
}


use criterion::measurement::WallTime;
///
/// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License").
/// You may not use this file except in compliance with the License.
/// A copy of the License is located at
///
///  http://aws.amazon.com/apache2.0
///
/// or in the "license" file accompanying this file. This file is distributed
/// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
/// express or implied. See the License for the specific language governing
/// permissions and limitations under the License.
///
use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput};
use http_desync_guardian::http_token_utils::http_token;
use http_desync_guardian::request_analyzer::{
    HttpHeader, HttpRequestData, HEADERS_STACK_STORAGE_SIZE,
};
use http_desync_guardian::ultralight_rate_limiter::UltraLightRateLimiter;
use http_desync_guardian::{
    http_desync_guardian_initialize_logging_settings,
    http_desync_guardian_register_tier_metrics_callback, ExtLoggingSettings,
    ExtRequestAnalysisMetricsUnit, ExtString, ExtTierMetricsSettings, RequestSafetyTier,
};
use lazy_static::lazy_static;
use smallvec::SmallVec;
use std::slice;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant};

lazy_static! {
    pub static ref START_TIME: Instant = Instant::now();
}

static LOG_COUNTER: AtomicU64 = AtomicU64::new(0);
static LAST_TIME: AtomicU64 = AtomicU64::new(0);
static SKIPS: AtomicU64 = AtomicU64::new(0);

/// Monotonic clock function, returning `ms` since startup.
/// Makes a system call only in 10% of cases (coarse-grained time, good enough)
fn get_current_time_millis() -> u64 {
    let start_time = *START_TIME;
    let x = SKIPS.fetch_add(1, Ordering::Relaxed);
    if x > 10 {
        SKIPS.fetch_and(0, Ordering::Relaxed);
    }
    if x == 1 {
        let current = Instant::now().duration_since(start_time).as_millis() as u64;
        LAST_TIME.store(current, Ordering::Relaxed);
        current
    } else {
        LAST_TIME.load(Ordering::Relaxed)
    }
}

fn as_bytes(s: &ExtString) -> &[u8] {
    let len = s.length - 1;
    let ptr = s.data_ptr as *const u8;
    // we know it's safe in this benchmark
    // as we control the input which is always valid
    unsafe { slice::from_raw_parts(ptr, len as usize + 1) }
}

extern "C" fn handle_log_messages(_tier: RequestSafetyTier, n: u32, msg: *const u8) {
    let string = ExtString {
        length: n,
        data_ptr: msg as *const i8,
    };
    let x = std::str::from_utf8(as_bytes(&string)).unwrap();
    LOG_COUNTER.fetch_add(x.len() as u64, Ordering::Relaxed);
}

extern "C" fn handle_metrics(
    n: u32,
    _msg: *const ExtRequestAnalysisMetricsUnit<RequestSafetyTier>,
) {
    LOG_COUNTER.fetch_add(n as u64, Ordering::Relaxed);
}

fn benchmark_ultralight_rate_limiter(c: &mut Criterion) {
    let rate_limiter = UltraLightRateLimiter::new(100., get_current_time_millis);

    c.bench_function(format!("Rate limiter").as_str(), |b| {
        b.iter(|| black_box(rate_limiter.try_acquire()))
    });
}

fn calculate_request_size(request: &HttpRequestData) -> usize {
    let mut size = 0;
    size += request.uri.len();
    size += request.method.len();
    size += request.version.len();
    for header in &request.headers {
        size += header.name.len();
        size += header.value.len();
    }
    size
}

struct SyntheticRequestParameters {
    name: &'static str,
    bad: bool,
    malicious: bool,
}

fn benchmark_request_analysis(
    c: &mut Criterion<WallTime>,
    request_params: SyntheticRequestParameters,
) {
    let mut group = c.benchmark_group(request_params.name);
    group.warm_up_time(Duration::from_secs(5)).sample_size(200);
    for size in &[50, 100, 500] {
        let c = if request_params.malicious {
            '\x01'
        } else {
            'a'
        };
        let mut field_value = std::iter::repeat(c).take(*size).collect::<String>();

        if request_params.bad {
            field_value.pop();
            field_value.pop();
            field_value.push('\x01');
            field_value.push('\x00');
        }

        let header_names: Vec<String> = (0..=size / 10)
            .map(|i| format!("Header-name-{}", i))
            .collect();

        let mut headers: SmallVec<[HttpHeader; HEADERS_STACK_STORAGE_SIZE]> = (0..=size / 10)
            .map(|i| HttpHeader::new(http_token(&header_names[i]), http_token(&field_value)))
            .collect();

        if request_params.bad {
            headers.push(HttpHeader::new(
                http_token("transfer-encoding"),
                http_token("gzip, chunked"),
            ));
            headers.push(HttpHeader::new(
                http_token("content-length"),
                http_token("123456789"),
            ));
        }

        let mut request = HttpRequestData {
            uri: http_token(&field_value),
            version: http_token("HTTP/1.1"),
            method: http_token("POST"),
            headers,
        };
        group
            .throughput(Throughput::Bytes(calculate_request_size(&request) as u64))
            .bench_function(
                format!(
                    "analyze_request: size {} bytes",
                    calculate_request_size(&request)
                ),
                |b| b.iter(|| black_box(request.analyze_parsed_request())),
            );
    }
    group.finish();
}

fn benchmark_request_parsing(
    c: &mut Criterion<WallTime>,
    request_params: SyntheticRequestParameters,
) {
    use std::fmt::Write;

    let mut group = c.benchmark_group(request_params.name);
    group.warm_up_time(Duration::from_secs(5)).sample_size(200);
    for size in &[50, 100, 500] {
        let c = if request_params.malicious {
            '\x01'
        } else {
            'a'
        };
        let field_value = std::iter::repeat(c).take(*size).collect::<String>();
        let mut request_as_string = String::new();
        write!(request_as_string, "GET {} HTTP/1.1\r\n", field_value,).ok();

        for i in 0..=size / 10 {
            write!(request_as_string, "Header-Name-{}: {}\r\n", i, field_value).ok();
        }
        write!(request_as_string, "\r\n").ok();

        let request_blob = request_as_string.as_bytes();

        group
            .throughput(Throughput::Bytes(request_blob.len() as u64))
            .bench_function(
                format!("analyze_raw_request: size {} bytes", request_blob.len()),
                |b| b.iter(|| black_box(HttpRequestData::analyze_raw_request(request_blob))),
            );
    }
    group.finish();
}

fn benchmark_compliant_request(c: &mut Criterion) {
    benchmark_request_analysis(
        c,
        SyntheticRequestParameters {
            name: "Compliant",
            bad: false,
            malicious: false,
        },
    );
}

fn benchmark_simplest_compliant_request_with_logging(c: &mut Criterion) {
    http_desync_guardian_register_tier_metrics_callback(Some(&ExtTierMetricsSettings {
        period_seconds: 10,
        callback: Some(handle_metrics),
    }));
    http_desync_guardian_initialize_logging_settings(Some(&ExtLoggingSettings {
        callback: Some(handle_log_messages),
    }));
    benchmark_request_analysis(
        c,
        SyntheticRequestParameters {
            name: "Compliant with logging",
            bad: false,
            malicious: false,
        },
    );
}

fn benchmark_non_compliant_request(c: &mut Criterion) {
    benchmark_request_analysis(
        c,
        SyntheticRequestParameters {
            name: "Non-Compliant",
            bad: true,
            malicious: false,
        },
    );
}

fn benchmark_malicious_request(c: &mut Criterion) {
    benchmark_request_analysis(
        c,
        SyntheticRequestParameters {
            name: "Malicious",
            bad: true,
            malicious: true,
        },
    );
}

fn benchmark_parse_request(c: &mut Criterion) {
    benchmark_request_parsing(
        c,
        SyntheticRequestParameters {
            name: "Parsing",
            bad: false,
            malicious: false,
        },
    );
}

criterion_group!(
    benches,
    benchmark_ultralight_rate_limiter,
    benchmark_parse_request,
    benchmark_compliant_request,
    benchmark_non_compliant_request,
    benchmark_malicious_request,
    benchmark_simplest_compliant_request_with_logging,
);

criterion_main!(benches);