/*
==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--==--

Namaste

Copyright (C) 2019, 2021-2025  Anonymous

There are several releases over multiple years,
they are listed as ranges, such as: "2021-2025".

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.

::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--::--
*/

#![cfg(any(target_os="linux", target_os="l4re", target_os="android"))]
#![cfg(not(feature="tokio"))]

extern crate alloc;

use {
    alloc::sync::Arc,
    core::{
        sync::atomic::{AtomicUsize, Ordering},
        time::Duration,
    },
    std::{
        io::ErrorKind,
        os::{
            fd::{AsRawFd, FromRawFd},
            linux::net::SocketAddrExt,
            unix::net::{SocketAddr, UnixListener, UnixStream},
        },
        thread::{self, JoinHandle},
    },
    blackhole::{BlackHole, OneTime},
    namaste::Result,
};

const ADDRESS: &[u8] = &[
    0x98, 0xc0, 0x6e, 0xa0, 0x33, 0xd5, 0xa9, 0x1c, 0xc7, 0xe1, 0xdc, 0x5d, 0xeb, 0x64, 0xc4, 0xb6, 0xff, 0x14, 0x25, 0x55, 0x23, 0xb7, 0x02,
    0x3e, 0xd2, 0x8e, 0x9c, 0x41, 0xf6, 0xa7, 0x90, 0x74, 0xed, 0x94, 0x2e, 0xdd, 0x9b, 0x7d, 0x4b, 0x43, 0x38, 0xe2, 0xad, 0xc0, 0xd9, 0x82,
    0x7d, 0x8e, 0xaf, 0x3e, 0x94, 0x88, 0x92, 0xc1, 0x8b, 0xbe, 0xb0, 0x91, 0x96, 0xaa, 0x7a, 0xb0, 0xdd, 0x80,
];

const CONNECTIONS: usize = 999;
const ATOMIC_ORDERING: Ordering = Ordering::Relaxed;

#[test]
fn tests() -> Result<()> {
    let first = UnixListener::bind_addr(&SocketAddr::from_abstract_name(ADDRESS)?)?;
    assert!(UnixListener::bind_addr(&SocketAddr::from_abstract_name(ADDRESS)?).is_err());
    let second = unsafe {
        match libc::dup(first.as_raw_fd()) {
            -1 => panic!(),
            other => UnixListener::from_raw_fd(other),
        }
    };

    let counter = Arc::new(AtomicUsize::new(usize::MIN));
    let threads = [spawn(first, counter.clone()), spawn(second, counter.clone())];

    let blackhole = BlackHole::make(CONNECTIONS)?;
    for _ in usize::MIN..CONNECTIONS {
        let job = || {
            UnixStream::connect_addr(&SocketAddr::from_abstract_name(ADDRESS)?)?;
            Result::Ok(())
        };
        assert!(blackhole.throw(OneTime::new(move || job().unwrap()))?.is_none());
    }

    for t in threads {
        t.join().unwrap();
    }

    assert_eq!(counter.load(ATOMIC_ORDERING), CONNECTIONS);

    Ok(())
}

fn spawn(unix_listener: UnixListener, counter: Arc<AtomicUsize>) -> JoinHandle<()> {
    thread::spawn(move || {
        unix_listener.set_nonblocking(true).unwrap();
        let mut count = usize::MIN;
        loop {
            match unix_listener.accept() {
                Ok(_) => {
                    counter.fetch_add(1, ATOMIC_ORDERING);
                    count = count.checked_add(1).unwrap();
                },
                Err(err) => match err.kind() {
                    ErrorKind::WouldBlock => thread::sleep(Duration::from_millis(10)),
                    _ => panic!("{err}"),
                },
            };

            if counter.load(ATOMIC_ORDERING) >= CONNECTIONS {
                break;
            }
        }
        assert!(count < counter.load(ATOMIC_ORDERING));
    })
}
