use crate::util::{paths, CargoResult, CargoResultExt};
use crypto_hash::{Algorithm, Hasher};
use std::fs::File;
use std::io::{self, Read, Write};
use std::path::Path;

pub struct Sha256(Hasher);

impl Sha256 {
    pub fn new() -> Sha256 {
        let hasher = Hasher::new(Algorithm::SHA256);
        Sha256(hasher)
    }

    pub fn update(&mut self, bytes: &[u8]) -> &mut Sha256 {
        let _ = self.0.write_all(bytes);
        self
    }

    pub fn update_file(&mut self, mut file: &File) -> io::Result<&mut Sha256> {
        let mut buf = [0; 64 * 1024];
        loop {
            let n = file.read(&mut buf)?;
            if n == 0 {
                break Ok(self);
            }
            self.update(&buf[..n]);
        }
    }

    pub fn update_path<P: AsRef<Path>>(&mut self, path: P) -> CargoResult<&mut Sha256> {
        let path = path.as_ref();
        let file = paths::open(path)?;
        self.update_file(&file)
            .chain_err(|| format!("failed to read `{}`", path.display()))?;
        Ok(self)
    }

    pub fn finish(&mut self) -> [u8; 32] {
        let mut ret = [0u8; 32];
        let data = self.0.finish();
        ret.copy_from_slice(&data[..]);
        ret
    }

    pub fn finish_hex(&mut self) -> String {
        hex::encode(self.finish())
    }
}

impl Default for Sha256 {
    fn default() -> Self {
        Self::new()
    }
}
