#![feature(exit_status_error)]

use std::collections::HashMap;
use std::error::Error;
use std::ffi::{OsStr, OsString};
use std::sync::Arc;
use std::time::Duration;

use debounce::MixedEventDebouncer;
use inotify::{Event, EventMask, Inotify, WatchDescriptor};

mod config;
mod processor;

#[derive(PartialEq, Eq)]
struct File {
    wd: WatchDescriptor,
    name: OsString,
}

impl From<Event<&OsStr>> for File {
    fn from(event: Event<&OsStr>) -> Self {
        File {
            wd: event.wd,
            name: event.name.unwrap().to_owned(),
        }
    }
}

fn run(inotify: &mut Inotify) -> Result<(), Box<dyn Error>> {
    let processors: Arc<HashMap<_, _>> = Arc::new(
        config::load()?
            .processors
            .iter()
            .flat_map(|p| {
                p.paths().map(move |path| processor::Processor {
                    path,
                    command: p.command.clone(),
                    args: p.args.clone(),
                    debounce: p.debounce,
                    keep_file: p.keep_file,
                })
            })
            .filter_map(|p| p.register(inotify).map(|d| (d, p)))
            .collect(),
    );

    let debouncer = {
        let processors = processors.clone();
        MixedEventDebouncer::new(move |file: File| {
            let processor = &processors[&file.wd];
            let filename = processor.path.join(&file.name);
            println!("Processing {}", filename.display());
            processor
                .process(&filename)
                .unwrap_or_else(|e| eprintln!("Error: {}", e));
        })
    };

    let mut buffer = [0; 1024];
    let mut cookie = u32::MAX;
    loop {
        for event in inotify.read_events_blocking(&mut buffer)? {
            match event.mask {
                m if m.contains(EventMask::ISDIR) => return Ok(()),
                EventMask::MOVED_FROM => cookie = event.cookie,
                m if m.intersects(EventMask::CLOSE_WRITE | EventMask::MOVED_TO)
                    && cookie != event.cookie =>
                {
                    let delay = Duration::from_millis(processors[&event.wd].debounce);
                    debouncer.put(event.into(), delay)
                }
                _ => (),
            }
        }
    }
}

fn main() -> Result<(), Box<dyn Error>> {
    let mut inotify = Inotify::init()?;
    loop {
        run(&mut inotify)?
    }
}