use core::net::{
	Ipv4Addr,
	Ipv6Addr,
};

use crate::{
	utils::bigendian::int_helpers::{
		u128,
		u32,
		u8,
	},
	FixedBitString,
};

fn with_ipv4_mut_slice<F, T>(addr: &mut Ipv4Addr, f: F) -> T
where
	F: FnOnce(&mut [u8]) -> T,
{
	let mut o = addr.octets();
	let result = f(&mut o);
	*addr = Ipv4Addr::from(o);
	result
}

fn with_ipv4_mut_u32<F, T>(addr: &mut Ipv4Addr, f: F) -> T
where
	F: FnOnce(&mut u32) -> T,
{
	let mut o = addr.to_bits();
	let result = f(&mut o);
	*addr = Ipv4Addr::from_bits(o);
	result
}

impl FixedBitString for Ipv4Addr {
	const LEN: usize = 32;

	fn inc(&mut self, prefix: usize) -> bool {
		with_ipv4_mut_u32(self, |num| u32::element_inc(num, prefix))
	}

	fn get(&self, ndx: usize) -> bool {
		u8::slice_get(&self.octets(), ndx)
	}

	fn set(&mut self, ndx: usize, bit: bool) {
		with_ipv4_mut_slice(self, |slice| u8::slice_set(slice, ndx, bit))
	}

	fn flip(&mut self, ndx: usize) {
		with_ipv4_mut_slice(self, |slice| u8::slice_flip(slice, ndx))
	}

	fn shared_prefix_len(&self, other: &Self) -> usize {
		u32::element_shared_prefix_len(self.to_bits(), other.to_bits(), Self::LEN)
	}

	fn set_false_from(&mut self, ndx: usize) {
		with_ipv4_mut_u32(self, |num| u32::element_set_false_from(num, ndx))
	}

	fn is_false_from(&self, ndx: usize) -> bool {
		u32::element_is_false_from(self.to_bits(), ndx)
	}

	fn set_true_from(&mut self, ndx: usize) {
		with_ipv4_mut_u32(self, |num| u32::element_set_true_from(num, ndx))
	}

	fn is_true_from(&self, ndx: usize) -> bool {
		u32::element_is_true_from(self.to_bits(), ndx)
	}

	fn new_all_false() -> Self {
		Ipv4Addr::from_bits(0)
	}

	fn new_all_true() -> Self {
		Ipv4Addr::from_bits(!0)
	}

	fn contains(&self, prefix: usize, other: &Self) -> bool {
		u32::element_contains(self.to_bits(), prefix, other.to_bits())
	}
}

fn with_ipv6_mut_slice<F, T>(addr: &mut Ipv6Addr, f: F) -> T
where
	F: FnOnce(&mut [u8]) -> T,
{
	let mut o = addr.octets();
	let result = f(&mut o);
	*addr = Ipv6Addr::from(o);
	result
}

fn with_ipv6_mut_u128<F, T>(addr: &mut Ipv6Addr, f: F) -> T
where
	F: FnOnce(&mut u128) -> T,
{
	let mut o = addr.to_bits();
	let result = f(&mut o);
	*addr = Ipv6Addr::from_bits(o);
	result
}

impl FixedBitString for Ipv6Addr {
	const LEN: usize = 128;

	fn inc(&mut self, prefix: usize) -> bool {
		with_ipv6_mut_u128(self, |num| u128::element_inc(num, prefix))
	}

	fn get(&self, ndx: usize) -> bool {
		u8::slice_get(&self.octets(), ndx)
	}

	fn set(&mut self, ndx: usize, bit: bool) {
		with_ipv6_mut_slice(self, |slice| u8::slice_set(slice, ndx, bit))
	}

	fn flip(&mut self, ndx: usize) {
		with_ipv6_mut_slice(self, |slice| u8::slice_flip(slice, ndx))
	}

	fn shared_prefix_len(&self, other: &Self) -> usize {
		u128::element_shared_prefix_len(self.to_bits(), other.to_bits(), Self::LEN)
	}

	fn set_false_from(&mut self, ndx: usize) {
		with_ipv6_mut_u128(self, |num| u128::element_set_false_from(num, ndx))
	}

	fn is_false_from(&self, ndx: usize) -> bool {
		u128::element_is_false_from(self.to_bits(), ndx)
	}

	fn set_true_from(&mut self, ndx: usize) {
		with_ipv6_mut_u128(self, |num| u128::element_set_true_from(num, ndx))
	}

	fn is_true_from(&self, ndx: usize) -> bool {
		u128::element_is_true_from(self.to_bits(), ndx)
	}

	fn new_all_false() -> Self {
		Ipv6Addr::from_bits(0)
	}

	fn new_all_true() -> Self {
		Ipv6Addr::from_bits(!0)
	}

	fn contains(&self, prefix: usize, other: &Self) -> bool {
		u128::element_contains(self.to_bits(), prefix, other.to_bits())
	}
}
