bubbles 3.1.1

Bubble integration server for powder diffraction
use cryiorust::frame::Array;
use integrustio::integrator::Pattern;
use itertools::izip;
use miniz_oxide::deflate::CompressionLevel::BestCompression;
use miniz_oxide::{deflate, inflate};
use std::fs::File;
use std::io::{BufRead, Write};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::{fmt, fs, io};

pub const KEY_DONT_TOUCH: &'static str = "Bubble_normalized";
pub const KEY_DUBBLE_MONITOR: &'static str = "Monitor";
pub const KEY_DUBBLE_PHOTO: &'static str = "Photo";

pub trait Decompressor {
    fn decompress(&self) -> io::Result<Vec<u8>>;
}

impl Decompressor for str {
    fn decompress(&self) -> io::Result<Vec<u8>> {
        let packed = match base64::decode(self) {
            Ok(packed) => packed,
            Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e)),
        };
        match inflate::decompress_to_vec_zlib(&packed) {
            Ok(decompressed) => Ok(decompressed),
            Err(e) => Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!("could not decompress: {:?}", e),
            )),
        }
    }
}

pub trait Compressor {
    fn compress(&self) -> String;
}

impl Compressor for [u8] {
    fn compress(&self) -> String {
        let packed = deflate::compress_to_vec_zlib(self, BestCompression as u8);
        base64::encode(&packed)
    }
}

pub trait Texter {
    fn to_text<P: io::Write>(&self, writer: &mut P) -> io::Result<()>;
}

impl Texter for Pattern {
    fn to_text<P: io::Write>(&self, writer: &mut P) -> io::Result<()> {
        for (position, intensity, sigma) in
            izip!(self.positions.iter(), &self.intensity, &self.sigma)
        {
            writeln!(writer, "{} {} {}", position, intensity, sigma)?;
        }
        Ok(())
    }
}

pub trait Resizer {
    fn resize(&self, bin: usize) -> Option<Array>;

    fn bindim(bin: usize, mut dim: usize) -> usize {
        while dim % bin != 0 {
            dim -= 1;
        }
        dim / bin
    }
}

impl Resizer for Array {
    fn resize(&self, bin: usize) -> Option<Array> {
        if bin <= 1 {
            return None;
        }
        let binf = bin as f64;
        let dim1 = Self::bindim(bin, self.dim1());
        let dim2 = Self::bindim(bin, self.dim2());
        let dim1bin = dim1 * bin;
        let dim2bin = dim2 * bin;
        let mut data = vec![0.; dim1 * dim2];
        for i in 0..dim1 {
            if i + bin >= dim1bin {
                continue;
            }
            let l = i * dim2;
            let ibin = i * bin;
            for j in 0..dim2 {
                if j + bin >= dim2bin {
                    continue;
                }
                let k = l + j;
                let jbin = j * bin;
                for x in 0..bin {
                    let offset = (ibin + x) * self.dim2() + jbin;
                    for y in 0..bin {
                        data[k] += self.data()[offset + y];
                    }
                }
                data[k] /= binf;
            }
        }
        Some(Array::with_data(dim1, dim2, data))
    }
}

pub enum WalkResult {
    None(io::Error),
    Dir(PathBuf),
    File(PathBuf),
}

pub trait MultiColumnWalker: AsRef<Path> + fmt::Debug {
    fn walk(&self, name: Arc<String>, ext: Arc<String>) {
        let entries = match fs::read_dir(self.as_ref()) {
            Ok(entries) => entries,
            Err(err) => {
                warn!(
                    "Failed to read path {:?} for multi column file: {}",
                    self, err
                );
                return;
            }
        };
        let mut files = vec![];
        for entry in entries {
            let entry = match entry {
                Ok(entry) => entry,
                Err(err) => {
                    warn!(
                        "Failed to process entry in {:?} for multi column file: {}",
                        self, err
                    );
                    return;
                }
            };
            match entry.process() {
                WalkResult::None(err) => {
                    warn!(
                        "Failed to process entry in {:?} for multi column file: {}",
                        self, err
                    );
                    return;
                }
                WalkResult::Dir(path) => {
                    let name = name.clone();
                    let ext = ext.clone();
                    rayon::spawn(move || Arc::new(path).walk(name, ext));
                }
                WalkResult::File(path) => {
                    if let Some(path_ext) = path.extension() {
                        if let Some(path_ext) = path_ext.to_str() {
                            if path_ext == ext.as_ref() {
                                files.push(path);
                            }
                        }
                    }
                }
            }
        }
        files.sort();
        self.save(files, name);
    }

    fn save(&self, files: Vec<PathBuf>, name: Arc<String>) {
        let name = name.as_str();
        let mut out = self.as_ref().to_path_buf();
        out.push(name);
        match Self::write(&out, files) {
            Ok(_) => info!("Multicolumn file {:?} is written", out),
            Err(e) => warn!("Failed to write multicolumn file {:?}: {}", out, e),
        }
    }

    fn write(name: &PathBuf, files: Vec<PathBuf>) -> io::Result<()> {
        let mut data = vec![];
        let mut names = vec![];
        let mut line_counter = 0;
        for path in &files {
            if path.as_os_str() == name {
                continue;
            }
            let reader = io::BufReader::new(File::open(path)?);
            let mut i = 0;
            for line in reader.lines() {
                let line = line?;
                data.push(line);
                i += 1;
            }
            names.push(path);
            if line_counter == 0 {
                line_counter = i
            } else if line_counter != i {
                return Err(io::Error::new(
                    io::ErrorKind::InvalidData,
                    format!(
                        "Integrated files in {:?} have different number of bins",
                        path
                    ),
                ));
            }
        }
        if line_counter == 0 || names.len() == 0 {
            return Ok(());
        }
        let mut writer = io::BufWriter::new(File::create(&name)?);
        write!(writer, "#q")?;
        for name in &names {
            if let Some(stem) = name.file_stem() {
                if let Some(name) = stem.to_str() {
                    write!(writer, " {}", name)?;
                }
            }
        }
        write!(writer, "\n")?;
        let mut write_q = true;
        for i in 0..line_counter {
            for j in 0..names.len() {
                let sl = data[j * line_counter + i].split_ascii_whitespace();
                for (k, item) in sl.enumerate() {
                    match k {
                        0 => {
                            if write_q {
                                write!(writer, "{}", item)?;
                                write_q = false;
                            }
                        }
                        1 => write!(writer, " {}", item)?,
                        _ => break,
                    }
                }
            }
            write!(writer, "\n")?;
            write_q = true;
        }
        Ok(())
    }
}

impl MultiColumnWalker for String {}

impl MultiColumnWalker for PathBuf {}

pub trait EntryProcesser {
    fn process(&self) -> WalkResult;
}

impl EntryProcesser for fs::DirEntry {
    fn process(&self) -> WalkResult {
        let path = self.path();
        match self.metadata() {
            Ok(md) => {
                if md.is_dir() {
                    WalkResult::Dir(path)
                } else {
                    WalkResult::File(path)
                }
            }
            Err(e) => WalkResult::None(e),
        }
    }
}

pub fn py_stamp() -> f64 {
    chrono::Local::now().timestamp_nanos() as f64 / 1e9
}