#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct KernelTap {
    pub dx: i8,
    pub dy: i8,
    pub weight_num: i16,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ErrorKernel {
    pub taps: &'static [KernelTap],
    pub weight_den: i16,
}

pub const FLOYD_STEINBERG: ErrorKernel = ErrorKernel {
    taps: &[
        KernelTap {
            dx: 1,
            dy: 0,
            weight_num: 7,
        },
        KernelTap {
            dx: -1,
            dy: 1,
            weight_num: 3,
        },
        KernelTap {
            dx: 0,
            dy: 1,
            weight_num: 5,
        },
        KernelTap {
            dx: 1,
            dy: 1,
            weight_num: 1,
        },
    ],
    weight_den: 16,
};

pub const FALSE_FLOYD_STEINBERG: ErrorKernel = ErrorKernel {
    taps: &[
        KernelTap {
            dx: 1,
            dy: 0,
            weight_num: 3,
        },
        KernelTap {
            dx: 0,
            dy: 1,
            weight_num: 3,
        },
        KernelTap {
            dx: 1,
            dy: 1,
            weight_num: 2,
        },
    ],
    weight_den: 8,
};

pub const JARVIS_JUDICE_NINKE: ErrorKernel = ErrorKernel {
    taps: &[
        KernelTap {
            dx: 1,
            dy: 0,
            weight_num: 7,
        },
        KernelTap {
            dx: 2,
            dy: 0,
            weight_num: 5,
        },
        KernelTap {
            dx: -2,
            dy: 1,
            weight_num: 3,
        },
        KernelTap {
            dx: -1,
            dy: 1,
            weight_num: 5,
        },
        KernelTap {
            dx: 0,
            dy: 1,
            weight_num: 7,
        },
        KernelTap {
            dx: 1,
            dy: 1,
            weight_num: 5,
        },
        KernelTap {
            dx: 2,
            dy: 1,
            weight_num: 3,
        },
        KernelTap {
            dx: -2,
            dy: 2,
            weight_num: 1,
        },
        KernelTap {
            dx: -1,
            dy: 2,
            weight_num: 3,
        },
        KernelTap {
            dx: 0,
            dy: 2,
            weight_num: 5,
        },
        KernelTap {
            dx: 1,
            dy: 2,
            weight_num: 3,
        },
        KernelTap {
            dx: 2,
            dy: 2,
            weight_num: 1,
        },
    ],
    weight_den: 48,
};

pub const STUCKI: ErrorKernel = ErrorKernel {
    taps: &[
        KernelTap {
            dx: 1,
            dy: 0,
            weight_num: 8,
        },
        KernelTap {
            dx: 2,
            dy: 0,
            weight_num: 4,
        },
        KernelTap {
            dx: -2,
            dy: 1,
            weight_num: 2,
        },
        KernelTap {
            dx: -1,
            dy: 1,
            weight_num: 4,
        },
        KernelTap {
            dx: 0,
            dy: 1,
            weight_num: 8,
        },
        KernelTap {
            dx: 1,
            dy: 1,
            weight_num: 4,
        },
        KernelTap {
            dx: 2,
            dy: 1,
            weight_num: 2,
        },
        KernelTap {
            dx: -2,
            dy: 2,
            weight_num: 1,
        },
        KernelTap {
            dx: -1,
            dy: 2,
            weight_num: 2,
        },
        KernelTap {
            dx: 0,
            dy: 2,
            weight_num: 4,
        },
        KernelTap {
            dx: 1,
            dy: 2,
            weight_num: 2,
        },
        KernelTap {
            dx: 2,
            dy: 2,
            weight_num: 1,
        },
    ],
    weight_den: 42,
};

pub const BURKES: ErrorKernel = ErrorKernel {
    taps: &[
        KernelTap {
            dx: 1,
            dy: 0,
            weight_num: 8,
        },
        KernelTap {
            dx: 2,
            dy: 0,
            weight_num: 4,
        },
        KernelTap {
            dx: -2,
            dy: 1,
            weight_num: 2,
        },
        KernelTap {
            dx: -1,
            dy: 1,
            weight_num: 4,
        },
        KernelTap {
            dx: 0,
            dy: 1,
            weight_num: 8,
        },
        KernelTap {
            dx: 1,
            dy: 1,
            weight_num: 4,
        },
        KernelTap {
            dx: 2,
            dy: 1,
            weight_num: 2,
        },
    ],
    weight_den: 32,
};

pub const SIERRA: ErrorKernel = ErrorKernel {
    taps: &[
        KernelTap {
            dx: 1,
            dy: 0,
            weight_num: 5,
        },
        KernelTap {
            dx: 2,
            dy: 0,
            weight_num: 3,
        },
        KernelTap {
            dx: -2,
            dy: 1,
            weight_num: 2,
        },
        KernelTap {
            dx: -1,
            dy: 1,
            weight_num: 4,
        },
        KernelTap {
            dx: 0,
            dy: 1,
            weight_num: 5,
        },
        KernelTap {
            dx: 1,
            dy: 1,
            weight_num: 4,
        },
        KernelTap {
            dx: 2,
            dy: 1,
            weight_num: 2,
        },
        KernelTap {
            dx: -1,
            dy: 2,
            weight_num: 2,
        },
        KernelTap {
            dx: 0,
            dy: 2,
            weight_num: 3,
        },
        KernelTap {
            dx: 1,
            dy: 2,
            weight_num: 2,
        },
    ],
    weight_den: 32,
};

pub const TWO_ROW_SIERRA: ErrorKernel = ErrorKernel {
    taps: &[
        KernelTap {
            dx: 1,
            dy: 0,
            weight_num: 4,
        },
        KernelTap {
            dx: 2,
            dy: 0,
            weight_num: 3,
        },
        KernelTap {
            dx: -2,
            dy: 1,
            weight_num: 1,
        },
        KernelTap {
            dx: -1,
            dy: 1,
            weight_num: 2,
        },
        KernelTap {
            dx: 0,
            dy: 1,
            weight_num: 3,
        },
        KernelTap {
            dx: 1,
            dy: 1,
            weight_num: 2,
        },
        KernelTap {
            dx: 2,
            dy: 1,
            weight_num: 1,
        },
    ],
    weight_den: 16,
};

pub const SIERRA_LITE: ErrorKernel = ErrorKernel {
    taps: &[
        KernelTap {
            dx: 1,
            dy: 0,
            weight_num: 2,
        },
        KernelTap {
            dx: -1,
            dy: 1,
            weight_num: 1,
        },
        KernelTap {
            dx: 0,
            dy: 1,
            weight_num: 1,
        },
    ],
    weight_den: 4,
};

pub const STEVENSON_ARCE: ErrorKernel = ErrorKernel {
    taps: &[
        KernelTap {
            dx: 2,
            dy: 0,
            weight_num: 32,
        },
        KernelTap {
            dx: -3,
            dy: 1,
            weight_num: 12,
        },
        KernelTap {
            dx: -1,
            dy: 1,
            weight_num: 26,
        },
        KernelTap {
            dx: 1,
            dy: 1,
            weight_num: 30,
        },
        KernelTap {
            dx: 3,
            dy: 1,
            weight_num: 16,
        },
        KernelTap {
            dx: -2,
            dy: 2,
            weight_num: 12,
        },
        KernelTap {
            dx: 0,
            dy: 2,
            weight_num: 26,
        },
        KernelTap {
            dx: 2,
            dy: 2,
            weight_num: 12,
        },
        KernelTap {
            dx: -3,
            dy: 3,
            weight_num: 5,
        },
        KernelTap {
            dx: -1,
            dy: 3,
            weight_num: 12,
        },
        KernelTap {
            dx: 1,
            dy: 3,
            weight_num: 12,
        },
        KernelTap {
            dx: 3,
            dy: 3,
            weight_num: 5,
        },
    ],
    weight_den: 200,
};

pub const ATKINSON: ErrorKernel = ErrorKernel {
    taps: &[
        KernelTap {
            dx: 1,
            dy: 0,
            weight_num: 1,
        },
        KernelTap {
            dx: 2,
            dy: 0,
            weight_num: 1,
        },
        KernelTap {
            dx: -1,
            dy: 1,
            weight_num: 1,
        },
        KernelTap {
            dx: 0,
            dy: 1,
            weight_num: 1,
        },
        KernelTap {
            dx: 1,
            dy: 1,
            weight_num: 1,
        },
        KernelTap {
            dx: 0,
            dy: 2,
            weight_num: 1,
        },
    ],
    weight_den: 8,
};

pub const FAN: ErrorKernel = ErrorKernel {
    taps: &[
        KernelTap {
            dx: 1,
            dy: 0,
            weight_num: 7,
        },
        KernelTap {
            dx: -2,
            dy: 1,
            weight_num: 1,
        },
        KernelTap {
            dx: -1,
            dy: 1,
            weight_num: 3,
        },
        KernelTap {
            dx: 0,
            dy: 1,
            weight_num: 5,
        },
    ],
    weight_den: 16,
};

pub const SHIAU_FAN: ErrorKernel = ErrorKernel {
    taps: &[
        KernelTap {
            dx: 1,
            dy: 0,
            weight_num: 4,
        },
        KernelTap {
            dx: -2,
            dy: 1,
            weight_num: 1,
        },
        KernelTap {
            dx: -1,
            dy: 1,
            weight_num: 1,
        },
        KernelTap {
            dx: 0,
            dy: 1,
            weight_num: 2,
        },
    ],
    weight_den: 8,
};

pub const SHIAU_FAN_2: ErrorKernel = ErrorKernel {
    taps: &[
        KernelTap {
            dx: 1,
            dy: 0,
            weight_num: 8,
        },
        KernelTap {
            dx: -3,
            dy: 1,
            weight_num: 1,
        },
        KernelTap {
            dx: -2,
            dy: 1,
            weight_num: 1,
        },
        KernelTap {
            dx: -1,
            dy: 1,
            weight_num: 2,
        },
        KernelTap {
            dx: 0,
            dy: 1,
            weight_num: 4,
        },
    ],
    weight_den: 16,
};

#[cfg(test)]
mod tests {
    use super::{
        ErrorKernel, ATKINSON, BURKES, FALSE_FLOYD_STEINBERG, FAN, FLOYD_STEINBERG,
        JARVIS_JUDICE_NINKE, SHIAU_FAN, SHIAU_FAN_2, SIERRA, SIERRA_LITE, STEVENSON_ARCE, STUCKI,
        TWO_ROW_SIERRA,
    };

    #[test]
    fn floyd_steinberg_denominator_positive() {
        let kernel = all_kernels()[0];
        assert!(kernel.weight_den > 0);
    }

    #[test]
    fn all_kernel_taps_point_forward_or_same_row_forward() {
        for kernel in all_kernels() {
            for tap in kernel.taps {
                assert!(tap.dy > 0 || (tap.dy == 0 && tap.dx > 0));
            }
        }
    }

    #[test]
    fn all_kernel_weights_nonnegative() {
        for kernel in all_kernels() {
            assert!(kernel.weight_den > 0);
            for tap in kernel.taps {
                assert!(tap.weight_num >= 0);
            }
        }
    }

    fn all_kernels() -> [&'static ErrorKernel; 13] {
        [
            &FLOYD_STEINBERG,
            &FALSE_FLOYD_STEINBERG,
            &JARVIS_JUDICE_NINKE,
            &STUCKI,
            &BURKES,
            &SIERRA,
            &TWO_ROW_SIERRA,
            &SIERRA_LITE,
            &STEVENSON_ARCE,
            &ATKINSON,
            &FAN,
            &SHIAU_FAN,
            &SHIAU_FAN_2,
        ]
    }
}
