#![allow(unused)]

use process_image as pi;

mod display;
mod fieldbus;
mod logic;
mod util;

// Parameters
const CYCLE_TIME: u64 = 5000;

pi::process_image! {
    struct Pii: 256 {
        pub bgb1: (X, 0, 0), // Left Limit Switch
        pub bgb2: (X, 0, 1), // Right Limit Switch
        pub bpb1: (X, 0, 2), // Pressure > 1.5 barg

        pub want_run: (X, 0, 4), // Start/Stop Switch
        pub want_run_once: (X, 0, 5), // Start Once Button

        pub connection_alive: (X, 64, 0),
    }
}

pi::process_image! {
    struct mut PiqMut: 256 {
        pub mbb1: (X, 0, 0), // Coil for turning cylinder to the left
        pub mbb2: (X, 0, 1), // Coil for turning cylinder to the right
        pub pza1_white: (X, 0, 2), // White status indicator light
        pub pza1_blue: (X, 0, 3), // Blue status indicator light
        pub pza1_orange: (X, 0, 4), // Orange status indicator light
        pub pza1_alarm: (X, 0, 5), // Alarm buzzer

        pub timer: (L, 104),
        pub fault_time: (L, 112),
        pub counter: (L, 120),
        pub running: (X, 200, 0),
        pub faulted: (X, 200, 1),
        pub state: (X, 200, 2),
        pub pressure_ok: (X, 200, 3),
    }
}

fn program(inp: Pii, mut out: PiqMut, now: profirust::time::Instant) {
    let now = u64::try_from(now.total_millis()).unwrap();

    let pressure_ok = inp.bpb1() && (*out.pressure_ok() || !inp.want_run());
    *out.pressure_ok() = pressure_ok;

    let running = inp.want_run() && (*out.running() || *out.pressure_ok());
    let mut faulted = *out.faulted() && running;
    *out.pza1_orange() = faulted;
    *out.pza1_blue() = !running && !faulted;
    *out.pza1_white() = running && !faulted;
    *out.pza1_alarm() = faulted && (now - *out.fault_time()) < 1000;

    if running && !inp.connection_alive() {
        faulted = true;
    }

    if running && !inp.bpb1() {
        faulted = true;
    }

    if running && (inp.bgb1() | inp.bgb2()) {
        faulted = true;
    }

    if !running || faulted {
        *out.timer() = now;
        *out.counter() = 0;
    }
    if (now - *out.timer()) > CYCLE_TIME && *out.state() {
        *out.timer() = now;
        *out.state() = false;
        *out.counter() += 1;
        log::info!("Load cycles: {}", *out.counter());
    }
    if (now - *out.timer()) > CYCLE_TIME && !*out.state() {
        *out.timer() = now;
        *out.state() = true;
        *out.counter() += 1;
        log::info!("Load cycles: {}", *out.counter());
    }
    let mbb1 = *out.state() && running && !faulted;
    let mbb2 = !*out.state() && running && !faulted;

    *out.mbb1() = mbb1;
    *out.mbb2() = mbb2;

    if !*out.faulted() && faulted {
        *out.fault_time() = now;
    }

    *out.running() = running;
    *out.faulted() = faulted;
}

fn main() {
    env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
        .format_timestamp_micros()
        .init();

    let mut display = display::Display::new();
    display.update_line(0, &format!("{:^20}", "torque tester"));

    let mut fieldbus = fieldbus::Fieldbus::new();
    let mut logic = logic::Logic::new();

    fieldbus.enter_state(fieldbus::OperatingState::Operate);

    loop {
        fieldbus.with_process_images(|pii, piq| {
            use process_image::{tag, tag_mut};

            *tag_mut!(piq, X, 0, 0) = logic.outputs().cylinder_left; // -MBB1
            *tag_mut!(piq, X, 0, 1) = logic.outputs().cylinder_right; // -MBB2
                                                                      //
            *tag_mut!(piq, X, 0, 2) = logic.outputs().signal_operate; // -PZA1:WHITE
            *tag_mut!(piq, X, 0, 3) = logic.outputs().signal_ready; // -PZA1:BLUE
            *tag_mut!(piq, X, 0, 4) = logic.outputs().signal_fault; // -PZA1:ORANGE
            *tag_mut!(piq, X, 0, 5) = logic.outputs().signal_alarm; // -PZA1:BUZZER (VIOLET)

            logic.inputs_mut().left_limit = tag!(pii, X, 0, 0); // -BGB1
            logic.inputs_mut().right_limit = tag!(pii, X, 0, 1); // -BGB2
            logic.inputs_mut().pressure_switch = tag!(pii, X, 0, 2); // -BPB1

            logic.inputs_mut().switch_run_continuous = tag!(pii, X, 0, 4);
            logic.inputs_mut().switch_run_once = tag!(pii, X, 0, 5);

            logic.inputs_mut().rio_online = tag!(pii, X, 64, 0);
        });

        logic.run(profirust::time::Instant::now());

        display.update_all(&logic.outputs().display);

        std::thread::sleep(std::time::Duration::from_millis(20));
    }
}
