use std::net::Ipv4Addr;

struct IpBlocking {
    upper_ips: Vec<u32>,
    lower_ips: Vec<u32>,
}

type E = Box<dyn std::error::Error>;

impl IpBlocking {
    fn from_path(path: &str) -> Result<Self, E> {
        let mut upper_ips: Vec<u32> = vec![];
        let mut lower_ips: Vec<u32> = vec![];
        let mut reader = csv::Reader::from_path(path)?;

        for record in reader.records() {
            let record = record?;
            let ip_range: Option<(u32, u32)> =
                record.get(0).map(|ip| ip.parse::<Ipv4Addr>().ok()).flatten().and_then(
                    |ip_start| {
                        record
                            .get(1)
                            .map(|ip| ip.parse::<Ipv4Addr>().ok())
                            .flatten()
                            .map(|ip_end| (ip_start.into(), ip_end.into()))
                    },
                );

            if let Some((ip_start, ip_end)) = ip_range {
                upper_ips.push(ip_end);
                lower_ips.push(ip_start);
            }
        }

        // Should already be sorted but just in case
        upper_ips.sort_unstable();
        lower_ips.sort_unstable();

        Ok(Self { upper_ips, lower_ips })
    }

    fn contains<I: Into<u32>>(&self, ip: I) -> bool {
        let ip: u32 = ip.into();

        let _prev_index = self.upper_ips.len() - 1;
        let mut index = self.upper_ips.len() / 2;
        loop {
            if self.upper_ips[index] <= ip && self.lower_ips[index] >= ip {
                return true;
            }

            let diff = self.upper_ips.len() - index;
            if self.upper_ips[index] < ip {
                index += diff / 2;
            }
        }
    }
}
