// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
// SPDX-FileCopyrightText: 2022 Linaro Ltd.
// SPDX-FileCopyrightText: 2022 Viresh Kumar <viresh.kumar@linaro.org>

mod common;

mod info_event {
    use libc::EINVAL;
    use std::{
        sync::{
            Arc, Mutex,
            mpsc::{self, Receiver, Sender},
        },
        thread,
        time::Duration,
    };

    use gpiosim_sys::Sim;
    use libgpiod::{
        Error as ChipError, OperationType,
        chip::Chip,
        line::{self, Direction, InfoChangeKind, Offset},
        request,
    };

    fn request_reconfigure_line(chip: Arc<Mutex<Chip>>, tx: Sender<()>, rx: Receiver<()>) {
        thread::spawn(move || {
            let mut lconfig1 = line::Config::new().unwrap();
            let lsettings = line::Settings::new().unwrap();
            lconfig1.add_line_settings(&[7], lsettings).unwrap();
            let rconfig = request::Config::new().unwrap();

            let mut request = chip
                .lock()
                .unwrap()
                .request_lines(Some(&rconfig), &lconfig1)
                .unwrap();

            // Signal the parent to continue
            tx.send(()).expect("Could not send signal on channel");

            // Wait for parent to signal
            rx.recv().expect("Could not receive from channel");

            let mut lconfig2 = line::Config::new().unwrap();
            let mut lsettings = line::Settings::new().unwrap();
            lsettings.set_direction(Direction::Output).unwrap();
            lconfig2.add_line_settings(&[7], lsettings).unwrap();

            request.reconfigure_lines(&lconfig2).unwrap();

            // Signal the parent to continue
            tx.send(()).expect("Could not send signal on channel");

            // Wait for parent to signal
            rx.recv().expect("Could not receive from channel");
        });
    }

    mod watch {
        use super::*;
        const NGPIO: usize = 8;
        const GPIO: Offset = 7;

        #[test]
        fn line_info() {
            let sim = Sim::new(Some(NGPIO), None, true).unwrap();
            let chip = Chip::open(&sim.dev_path()).unwrap();

            assert_eq!(
                chip.watch_line_info(NGPIO as u32).unwrap_err(),
                ChipError::OperationFailed(OperationType::ChipWatchLineInfo, errno::Errno(EINVAL))
            );

            let info = chip.watch_line_info(GPIO).unwrap();
            assert_eq!(info.offset(), GPIO);

            // No events available
            assert!(
                !chip
                    .wait_info_event(Some(Duration::from_millis(100)))
                    .unwrap()
            );
        }

        #[test]
        fn reconfigure() {
            let sim = Sim::new(Some(NGPIO), None, true).unwrap();
            let chip = Arc::new(Mutex::new(Chip::open(&sim.dev_path()).unwrap()));
            let info = chip.lock().unwrap().watch_line_info(GPIO).unwrap();

            assert_eq!(info.direction().unwrap(), Direction::Input);

            // Thread synchronizing mechanism
            let (tx_main, rx_thread) = mpsc::channel();
            let (tx_thread, rx_main) = mpsc::channel();

            // Generate events
            request_reconfigure_line(chip.clone(), tx_thread, rx_thread);

            // Wait for thread to signal
            rx_main.recv().expect("Could not receive from channel");

            // Line requested event
            assert!(
                chip.lock()
                    .unwrap()
                    .wait_info_event(Some(Duration::from_secs(1)))
                    .unwrap()
            );
            let event = chip.lock().unwrap().read_info_event().unwrap();
            let ts_req = event.timestamp();

            assert_eq!(event.event_type().unwrap(), InfoChangeKind::LineRequested);
            assert_eq!(
                event.line_info().unwrap().direction().unwrap(),
                Direction::Input
            );

            // Signal the thread to continue
            tx_main.send(()).expect("Could not send signal on channel");

            // Wait for thread to signal
            rx_main.recv().expect("Could not receive from channel");

            // Line changed event
            assert!(
                chip.lock()
                    .unwrap()
                    .wait_info_event(Some(Duration::from_secs(1)))
                    .unwrap()
            );
            let event = chip.lock().unwrap().read_info_event().unwrap();
            let ts_rec = event.timestamp();

            assert_eq!(
                event.event_type().unwrap(),
                InfoChangeKind::LineConfigChanged
            );
            assert_eq!(
                event.line_info().unwrap().direction().unwrap(),
                Direction::Output
            );

            // Signal the thread to continue
            tx_main.send(()).expect("Could not send signal on channel");

            // Line released event
            assert!(
                chip.lock()
                    .unwrap()
                    .wait_info_event(Some(Duration::from_secs(1)))
                    .unwrap()
            );
            let event = chip.lock().unwrap().read_info_event().unwrap();
            let ts_rel = event.timestamp();

            assert_eq!(event.event_type().unwrap(), InfoChangeKind::LineReleased);

            // No events available
            assert!(
                !chip
                    .lock()
                    .unwrap()
                    .wait_info_event(Some(Duration::from_millis(100)))
                    .unwrap()
            );

            // Check timestamps are really monotonic.
            assert!(ts_rel > ts_rec);
            assert!(ts_rec > ts_req);
        }
    }
}
