#[macro_use]
extern crate chan;
extern crate chan_signal;

use std::io::prelude::*;
use std::fs::File;
use std::thread;
use std::thread::JoinHandle;
use std::path::Path;
use std::time::Duration;

use chan_signal::Signal;

static GPIO_PORT_BTN: &'static str = "17";
static GPIO_PORT_LED: &'static str = "18";

static GPIO_DIRECTION_IN: &'static str = "in";
static GPIO_DIRECTION_OUT: &'static str = "out";

static GPIO_BTN_ON: &'static str = "0";
//static GPIO_BTN_OFF: &'static str = "1";

static GPIO_LED_ON: &'static str = "0";
static GPIO_LED_OFF: &'static str = "1";

fn write(path: &str, value: &str) {
    let mut file = File::create(Path::new(path)).expect(format!("Open file '{}' failed", path)
                                                            .as_str());

    file.write_all(value.as_bytes())
        .expect(format!("Write value '{}' to '{}' file failed", value, path).as_str());

    #[cfg(debug_assertions)]
    println!("Wrote value '{}' to '{}'.", value, path);
}
fn read(path: &str) -> String {
    let mut file = File::open(Path::new(path)).expect(format!("Open file '{}' failed", path)
                                                          .as_str());
    let mut contents = String::new();

    file.read_to_string(&mut contents)
        .expect(format!("Read from '{}' file failed", path).as_str());

    #[cfg(debug_assertions)]
    println!("Read value '{}' from '{}'.", contents, path);

    contents
}

fn export(port: &str) {
    write("/sys/class/gpio/export", port)
}
fn unexport(port: &str) {
    write("/sys/class/gpio/unexport", port)
}

fn set_direction(port: &str, direction: &str) {
    write(format!("/sys/class/gpio/gpio{}/direction", port).as_str(),
          direction);
}

fn set_value(port: &str, value: &str) {
    write(format!("/sys/class/gpio/gpio{}/value", port).as_str(),
          value);
}
fn get_value(port: &str) -> String {
    String::from(read(format!("/sys/class/gpio/gpio{}/value", port).as_str()).trim())
}

fn main() {
    let signal = chan_signal::notify(&[Signal::INT, Signal::TERM]);
    let (sdone, rdone) = chan::sync(0);
    let (spanic, rpanic) = chan::sync(0);
    let worker = thread::Builder::new()
        .name("worker".to_string())
        .spawn(move || run(rdone))
        .expect("Create worker thread failed");
    let watchdog = thread::Builder::new()
        .name("watchdog".to_string())
        .spawn(move || watchdog(worker, spanic))
        .expect("Create watchdog thread failed");

    chan_select! {
        rpanic.recv() => { },
        signal.recv() -> signal => {
            println!("received signal: {:?}", signal);
            sdone.send(());
        }
    }

    watchdog.join().expect("Watchdog thread paniced");
}

fn watchdog(thread: JoinHandle<()>, _spanic: chan::Sender<()>) {
    thread.join().expect("Thread paniced");
}

fn run(rdone: chan::Receiver<()>) {
    // init
    export(GPIO_PORT_BTN);
    export(GPIO_PORT_LED);
    set_direction(GPIO_PORT_BTN, GPIO_DIRECTION_IN);
    set_direction(GPIO_PORT_LED, GPIO_DIRECTION_OUT);

    let btn_tick = chan::tick(Duration::from_millis(50));
    let led_tick = chan::tick(Duration::from_millis(1000 / 5));
    let mut btn_last = false;
    let mut led_on = false;
    let mut enabled = true;

    loop {
        chan_select! {
            btn_tick.recv() => {
                //#[cfg(debug_assertions)]
                //println!("btn_tick");

                // switch enabled if button state changed
                let btn = get_value(GPIO_PORT_BTN) == GPIO_BTN_ON;
                if btn && btn != btn_last {
                    enabled = !enabled;
                }

                btn_last = btn;
            },
            led_tick.recv() => {
                #[cfg(debug_assertions)]
                println!("led_tick");

                // blink led if enabled
                if enabled {
                    led_on = !led_on;
                } else {
                    led_on = false;
                }

                // set led state
                if led_on {
                    set_value(GPIO_PORT_LED, GPIO_LED_ON);
                }
                else {
                    set_value(GPIO_PORT_LED, GPIO_LED_OFF);
                }
            },
            rdone.recv() => {
                // unexport
                unexport(GPIO_PORT_BTN);
                unexport(GPIO_PORT_LED);
                return;
            },
        }
    }
}
