use std::{
    error::Error,
    net::{Ipv4Addr, ToSocketAddrs, UdpSocket},
};

// The format of a Wake-on-LAN (WOL) magic packet is defined
// as a byte array with 6 bytes of value 255 (0xFF) followed by
// 16 repetitions of the target machine’s 48-bit (6-byte) MAC address.
// This becomes a total of 102 bytes:

const MAGIC_HEADER: [u8; 6] = [0xFF; 6];

/// Contains raw bytes for magic packet
pub struct MagicPacket {
    pub bytes: [u8; 102],
}

impl MagicPacket {
    /// Create new MagicPacket from a raw 6-byte MAC address
    pub fn new(mac_bytes: &[u8; 6]) -> MagicPacket {
        let mut magic_bytes: [u8; 102];
        unsafe {
            magic_bytes = std::mem::MaybeUninit::uninit().assume_init();
            let mut src: *const u8 = &MAGIC_HEADER[0];
            let mut dst: *mut u8 = &mut magic_bytes[0];
            dst.copy_from_nonoverlapping(src, 6);

            src = &mac_bytes[0];
            for _ in 0..16 {
                dst = dst.offset(6);
                dst.copy_from_nonoverlapping(src, 6);
            }
        }
        return MagicPacket { bytes: magic_bytes };
    }

    // This is horrible code
    /// Parse a MAC-string into a packet.
    /// Takes both separated and unseparated
    pub fn from_str(mac_str: &str) -> Result<MagicPacket, Box<dyn Error>> {
        use hex::FromHex;
        let mut mac_string = mac_str.trim().to_string();

        if mac_string.len() == 17 {
            for i in 0..5 {
                mac_string.remove(14 - (i * 3)); // Deal with it
            }
        }

        let mut mac_arr: [u8; 6];
        let hex_vec = Vec::from_hex(mac_string)?;

        unsafe {
            mac_arr = std::mem::MaybeUninit::uninit().assume_init();
            let src: *const u8 = &hex_vec[0];
            let dst: *mut u8 = &mut mac_arr[0];
            dst.copy_from_nonoverlapping(src, 6);
        }
        return Ok(MagicPacket::new(&mac_arr));
    }

    pub fn from_str2(mac_str: &str) -> Result<MagicPacket, Box<dyn Error>> {
        return Ok(MagicPacket::new(&MagicPacket::parse(mac_str).unwrap()));
    }

    // Stolen 
    fn mac_to_byte(data: &str, sep: char) -> Vec<u8> {
        data.split(sep)
            .flat_map(|x| hex::decode(x).expect("Invalid mac!"))
            .collect()
    }

    // Parses string by position if string is 12+5 characters long (delimited by : for example)
    pub fn parse<S: AsRef<str>>(mac_str: S) -> Result<Box<[u8; 6]>, Box<dyn Error>> {
        use hex::FromHex;
        let mut mstr: &str = mac_str.as_ref();
        mstr = mstr.trim();
        let mut bytes: [u8; 6] = [0, 0, 0, 0, 0, 0];

        for (index, byte) in bytes.iter_mut().enumerate() {
            // 0,3,6,9...
            let substr = &mstr[3 * index..3 * index + 2];
            *byte = <[u8; 1]>::from_hex(substr).unwrap()[0];
        }

        return Ok(Box::new(bytes));
    }

    // Loops the string and parses any valid hex
    pub fn parse_harder<S: Into<String>>(mac_str: S) -> Result<Box<[u8; 6]>, Box<dyn Error>> {
        let mstr: String = mac_str.into();
        let mut hexdigits: String = mstr
            .chars()
            .filter(|c| char::is_ascii_hexdigit(c))
            .collect();
        let mut bytes: [u8; 6] = [0; 6];

        if hexdigits.len() >= 12 {
            hexdigits.truncate(12); // May not be needed, since bytes is only 6 bytes, and from_hex might be smart enough to realize...
                                    //let bytes: [u8; 6] = hex::FromHex::from_hex(hexdigits)?;
            bytes = hex::FromHex::from_hex(hexdigits)?;
        }
        return Ok(Box::new(bytes));
    }

    /// Send packet to/from specific address/interface
    pub fn send_to<A: ToSocketAddrs>(&self, to_addr: A, from_addr: A) -> std::io::Result<()> {
        let socket = UdpSocket::bind(from_addr)?;
        socket.set_broadcast(true)?;
        socket.send_to(&self.bytes, to_addr)?;
        Ok(())
    }

    /// Send package from whatever interface the os picks
    pub fn send(&self) -> std::io::Result<()> {
        self.send_to(
            (Ipv4Addr::new(0xFF, 0xFF, 0xFF, 0xFF), 9),
            (Ipv4Addr::new(0x0, 0x0, 0x0, 0x0), 0),
        )
    }

    /// Return raw bytes, ready to be broadcasted
    pub fn get_bytes(&self) -> &[u8; 102] {
        &self.bytes
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn init_packet() {
        let packet1 = MagicPacket::new(&[0xFF; 6]);
        let packet2 = MagicPacket::new(&[0xAA; 6]);

        let vec: [u8; 102] = [0xFF; 102];

        assert_eq!(packet1.bytes, vec);
        assert_ne!(packet2.bytes, vec);
    }

    #[test]
    fn magic_packet_from_new() {
        let packet3 = MagicPacket::from_str2("10:10:10:10:10:10").unwrap();
        //let vec: [u8; 102] = [0xFF; 102];
        let slice = &packet3.bytes[packet3.bytes.len() - 6..];
        for a in slice {
            println!("{}", a);
        }

        let bytes: [u8; 6] = [0x10; 6];
        assert_eq!(slice, bytes);
    }

    #[test]
    fn test_parse() {
        let mp = MagicPacket::parse("ff:ff:ff:ff:ff:ff").unwrap();
        assert_eq!([0xFF; 6], *mp);
        let mp2 = MagicPacket::parse("10:10:10:10:10:10").unwrap();
        assert_eq!([0x10; 6], *mp2);
    }

    #[test]
    fn parse_harder_test() {
        let mac = MagicPacket::parse_harder("10:10:10:10:10:10").unwrap();
        assert_eq!([0x10; 6], *mac);
    }
}