use osshkeys::{error::OsshResult, keys::ed25519::Ed25519KeyPair, KeyPair};
use sha2::{Digest, Sha256};

pub fn generate_ed25519_keypair<S1, S2>(
    passphrase: S1,
    id: Option<S2>,
) -> OsshResult<KeyPair>
where
    S1: AsRef<str>,
    S2: AsRef<str>,
{
    let hash = {
        let mut hasher = Sha256::new();
        hasher.update(passphrase.as_ref());
        if let Some(id) = id {
            hasher.update(id.as_ref());
        }

        hasher.finalize()
    };

    Ed25519KeyPair::from_seed(&hash).map(|key| Into::<KeyPair>::into(key))
}

pub mod randomart {
    use std::fmt::Write;

    const WIDTH: usize = 17;
    const HEIGHT: usize = 9;
    const TILES: &[char] = &[
        ' ', '.', 'o', '+', '=', '*', 'B', 'O', 'X', '@', '%', '&', '#', '/',
        '^',
    ];

    pub struct RandomArt {
        tiles: [[i8; WIDTH]; HEIGHT],
    }

    impl RandomArt {
        pub fn render(
            &self,
            title: &str,
            subtitle: &str,
        ) -> Result<String, std::fmt::Error> {
            let mut string = String::new();

            string
                .write_str(&format!("+{:-^17}+\n", format!("[{}]", title)))?;

            for row in self.tiles {
                string.write_char('|')?;
                for cell in row {
                    if cell == -1 {
                        string.write_char('S')?;
                    } else if cell == -2 {
                        string.write_char('E')?;
                    } else {
                        let cell = (TILES.len() - 1).min(cell as usize);
                        string.write_char(TILES[cell])?;
                    }
                }
                string.write_char('|')?;
                string.write_char('\n')?;
            }

            string.write_str(&format!(
                "+{:-^17}+\n",
                format!("[{}]", subtitle)
            ))?;

            Ok(string)
        }

        pub fn from_digest(digest: &[u8]) -> Self {
            let mut tiles = [[0i8; WIDTH]; HEIGHT];
            let mut x = WIDTH / 2;
            let mut y = HEIGHT / 2;

            tiles[y][x] = -1;

            for byte in digest.iter() {
                for i in 0..4 {
                    let b = (*byte >> (i * 2)) & 3;
                    match b {
                        0 | 1 => {
                            if y > 0 {
                                y -= 1;
                            }
                        }
                        2 | 3 => {
                            if y < HEIGHT - 1 {
                                y += 1;
                            }
                        }
                        _ => {}
                    }
                    match b {
                        0 | 2 => {
                            if x > 0 {
                                x -= 1;
                            }
                        }
                        1 | 3 => {
                            if x < WIDTH - 1 {
                                x += 1;
                            }
                        }
                        _ => {}
                    }

                    if tiles[y][x] >= 0 {
                        tiles[y][x] += 1;
                    }
                }
            }

            tiles[y][x] = -2;

            Self { tiles }
        }
    }

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

        const FINGERPRINT: &str =
            "L5N7A2PETaGegW5P3qh/Vjd8AW6Mn4B+VB2SHK+eZCY=";

        #[test]
        fn render() {
            let randomart =
                RandomArt::from_digest(&base64::decode(FINGERPRINT).unwrap())
                    .render("Title", "Subtitle")
                    .unwrap();

            println!("{}", randomart);
        }
    }
}

mod tests {
    #![allow(dead_code)]
    #![allow(unused_imports)]
    use osshkeys::{
        keys::ed25519::Ed25519KeyPair, KeyPair, PrivateParts, PublicParts,
    };
    use sha2::Digest;

    use super::randomart::RandomArt;

    const PASSPHRASE: &str = "the spice must flow";

    const DATA: &str = "I’d just like to interject for a moment. What you’re refering to as Linux, is in fact, GNU/Linux, or as I’ve recently taken to calling it, GNU plus Linux. Linux is not an operating system unto itself, but rather another free component of a fully functioning GNU system made useful by the GNU corelibs, shell utilities and vital system components comprising a full OS as defined by POSIX.";

    const SIGNATURE: &[u8] = &[
        115, 18, 156, 87, 192, 149, 107, 105, 13, 87, 219, 90, 26, 146, 41,
        114, 20, 143, 253, 206, 216, 236, 222, 66, 252, 136, 38, 216, 184, 127,
        94, 255, 68, 246, 64, 228, 141, 64, 63, 64, 236, 222, 184, 214, 3, 157,
        73, 186, 73, 156, 20, 100, 76, 241, 113, 81, 38, 131, 174, 31, 103,
        181, 220, 11,
    ];

    #[test]
    fn test() {
        let mut hasher = sha2::Sha256::new();
        hasher.update(PASSPHRASE);

        let hash = hasher.finalize();

        let keypair: KeyPair = Ed25519KeyPair::from_seed(&hash).unwrap().into();

        let asdf = keypair.sign(DATA.as_bytes()).unwrap();
        assert_eq!(keypair.verify(DATA.as_bytes(), SIGNATURE).unwrap(), true);
    }
}
