#[macro_use]
extern crate failure;

mod config;
mod widgets;
mod window;

use pango::LayoutExt;
use std::os::unix::io::AsRawFd;

use widgets::Size;
use window::{Display, Event, Window};

fn main() -> Result<(), failure::Error> {
    // set up the display and the window
    let mut config = config::Config::find_config()?;
    let height = config.get_height();

    let mut d = Display::create()?;
    let mut ws = Vec::new();

    for (x_off, wd) in d.get_widths()? {
        let size = Size {
            wd,
            ht: height,
            xo: x_off,
            yo: 0,
        };
        let mut w = Window::create(&d, size)?;
        // set some window-manager properties: this is a dock
        w.change_property("_NET_WM_WINDOW_TYPE", &["_NET_WM_WINDOW_TYPE_DOCK"])?;
        // ...and should push other windows out of the way
        w.change_property("_NET_WM_STRUT", &[x_off as i64, 0, size.ht as i64, 0])?;
        w.change_property(
            "_NET_WM_STRUT_PARTIAL",
            &[0, 0, size.ht as i64, 0, 0, 0, 0, 0, 0, size.wd as i64, 0, 0],
        )?;

        // we won't ever see this, but for good measure.
        w.set_title("rbar")?;
        // we care about some input events!
        w.set_input_masks()?;
        w.set_protocols()?;
        // and now show it!
        w.map();
        ws.push(w);
    }

    // we do some grossness with file descriptors later, so we need
    // the file descriptors we care about here
    let window_fds: Vec<i32> = ws.iter_mut().map(|w| w.get_fd()).collect();
    let stdin_fd = std::io::stdin().as_raw_fd();

    // To begin with, our left-hand side---which normally is whatever
    // was last passed in on stdin---will start as a generic
    // message...
    let mut input = "Loading...".to_string();
    // And let's get a buffered stdin handle now
    let mut stdin = std::io::BufReader::new(std::io::stdin());

    // In the absence of other events, let's refresh every five
    // seconds. Or whatever.
    let mut timer = libc::timeval {
        tv_sec: 5,
        tv_usec: 0,
    };

    let mut ctxs = Vec::new();
    for w in ws.iter_mut() {
        // let's grab the cairo context here
        let surf = w.get_cairo_surface();
        let ctx = cairo::Context::new(&surf);

        let layout = pangocairo::functions::create_layout(&ctx)
            .ok_or_else(|| format_err!("unable to create layout"))?;

        // allow for the whole width of the bar, minus a small fixed amount
        layout.set_width((w.width - 20) * pango::SCALE);
        // this should also be configurable, but Fira Mono is a good font
        let mut font = pango::FontDescription::from_string(config.font());
        font.set_weight(pango::Weight::Bold);
        layout.set_font_description(&font);

        // do an initial pass at drawing the bar!
        config.draw(&ctx, &layout, &input, w.size())?;

        ctxs.push((ctx, layout, w.size()));
    }

    let max_fd = window_fds.iter().max().unwrap_or(&0) + 1;
    // we're gonna keep looping until we don't
    loop {
        let mut fds = std::mem::MaybeUninit::uninit();

        unsafe {
            // set up the FD set to be the X11 fd and the state of stdin
            libc::FD_ZERO(fds.as_mut_ptr());
            fds.assume_init();
            for fd in window_fds.iter() {
                libc::FD_SET(*fd, fds.as_mut_ptr());
            }
            libc::FD_SET(stdin_fd, fds.as_mut_ptr());
            timer.tv_sec = 5;

            // this will block until there's input on either of the
            // above FDs or until five seconds have passed, whichever comes first
            libc::select(
                max_fd,
                fds.as_mut_ptr(),
                std::ptr::null_mut(),
                std::ptr::null_mut(),
                &mut timer,
            );
        }

        // if we _did_ have input on stdin, then read it in: that'll
        // be our new left-hand text
        if unsafe { libc::FD_ISSET(stdin_fd, fds.as_mut_ptr()) } {
            use std::io::BufRead;
            input = String::new();
            stdin.read_line(&mut input)?;
            if input.is_empty() {
                break;
            }
            for (ctx, layout, sz) in ctxs.iter() {
                config.draw(&ctx, &layout, &input, *sz)?;
            }
        }

        // if we have X11 events, handle them. If any one was a quit
        // event, then just... quit.
        for w in ws.iter_mut() {
            while w.has_events() {
                match w.handle() {
                    Some(Event::QuitEvent) => break,
                    _e => (),
                }
            }
        }

        for (ctx, layout, sz) in ctxs.iter() {
            // otherwise, draw the thing!
            config.draw(&ctx, &layout, &input, *sz)?;
        }
        config.update();
    }

    Ok(())
}
