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

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/>.

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

extern crate alloc;

use {
    alloc::borrow::Cow,
    core::borrow::Borrow,
    std::{
        io::{Error, ErrorKind},
        process,
    },
    dia_args::Args,
};

#[cfg(not(feature="tokio"))]
use {
    core::time::Duration,
    std::{
        thread,
        time::Instant,
    },
    cld::ClDuration,
    dia_time::Time,
    namaste::RwNamaste,
};

/// # Wrapper for format!(), which prefixes your optional message with: module_path!(), line!()
macro_rules! __ {
    ($($arg: tt)+) => {
        format!("[{module_path}-{line}] {msg}", module_path=module_path!(), line=line!(), msg=format!($($arg)+))
    };
    () => {
        __!("(internal error)")
    };
}

/// # Makes new std::io::Error
#[cfg(not(feature="tokio"))]
macro_rules! err {
    ($kind: path, $($arg: tt)+) => { std::io::Error::new($kind, __!($($arg)+)) };
    ($($arg: tt)+) => { err!(std::io::ErrorKind::Other, $($arg)+) };
    () => { std::io::Error::new(std::io::ErrorKind::Other, __!()) };
}

#[macro_use]
#[allow(unused_macros)]
mod __;

/// # Result type used in this crate
pub type Result<T> = core::result::Result<T, std::io::Error>;

const CMD_HELP: &str = "help";
const CMD_HELP_DOCS: Cow<str> = Cow::Borrowed("Prints help and exits.");

#[cfg(not(feature="tokio"))]
const CMD_READ: &str = "read";
#[cfg(not(feature="tokio"))]
const CMD_READ_DOCS: Cow<str> = Cow::Borrowed("Takes a read.");

#[cfg(not(feature="tokio"))]
const CMD_WRITE: &str = "write";
#[cfg(not(feature="tokio"))]
const CMD_WRITE_DOCS: Cow<str> = Cow::Borrowed("Takes a write.");

#[cfg(not(feature="tokio"))]
const OPTION_TIMEOUT: &[&str] = &["--timeout"];
#[cfg(not(feature="tokio"))]
const OPTION_TIMEOUT_DOCS: Cow<str> = Cow::Borrowed("Timeout.");

#[cfg(not(feature="tokio"))]
const READ_ID: &[u8] = &[
    0x3b, 0xc4, 0xf4, 0xef, 0x53, 0x27, 0x7f, 0xee, 0x06, 0x1e, 0x4f, 0x27, 0xa1, 0xce, 0xcd, 0x82, 0x8e, 0xa2, 0x52, 0x13, 0xd5, 0x48, 0x8d,
    0xb4, 0xe2, 0xc9, 0xd3, 0x6c, 0xaf, 0x86, 0xc5, 0x3c, 0x5e, 0x2c, 0x50, 0xa0, 0x12, 0x38, 0xcf, 0x7d, 0x46, 0x34, 0x67, 0xa3, 0x7b, 0xb5,
    0xf4, 0x80, 0x3a, 0x46, 0x0f, 0x8b, 0xe1, 0x25, 0x2c, 0xd8, 0x60, 0x93, 0xc5, 0xd6, 0x00, 0xd8, 0xb1, 0xd3,
];
#[cfg(not(feature="tokio"))]
const WRITE_ID: &[u8] = &[
    0xee, 0xfb, 0x28, 0xd0, 0xf9, 0xff, 0x00, 0x8a, 0xfc, 0x4f, 0x46, 0x7b, 0x4c, 0xab, 0xed, 0x45, 0xd6, 0x11, 0x77, 0x95, 0x07, 0xca, 0xca,
    0xfa, 0xd4, 0xd6, 0xf7, 0x41, 0x6a, 0xbd, 0xa8, 0xd2, 0xdf, 0x3b, 0x18, 0x53, 0x4b, 0x7d, 0x6b, 0x24, 0xc5, 0x5f, 0xb5, 0xd9, 0xc3, 0x1d,
    0xb4, 0x90, 0x4d, 0xee, 0xa0, 0xb9, 0xc4, 0x26, 0x89, 0xa2, 0x39, 0x95, 0xee, 0x35, 0x12, 0x0e, 0xfa, 0xc0,
];

#[cfg(not(feature="tokio"))]
const RW_TIMEOUT: Duration = Duration::from_secs(32);

/// # Main
fn main() -> Result<()> {
    if let Err(err) = run() {
        __err!("{}\n", err);
        process::exit(1);
    }

    Ok(())
}

/// # Runs the program
fn run() -> Result<()> {
    let args = dia_args::parse()?;
    match args.cmd() {
        Some(CMD_HELP) => {
            ensure_args_are_empty(args.try_into_sub_cmd()?.1)?;
            print_help()
        },
        #[cfg(not(feature="tokio"))]
        Some(CMD_READ) => read(args.try_into_sub_cmd()?.1),
        #[cfg(not(feature="tokio"))]
        Some(CMD_WRITE) => write(args.try_into_sub_cmd()?.1),
        Some(other) => Err(Error::new(ErrorKind::InvalidInput, format!("Unknown command: {:?}", other))),
        None => Err(Error::new(ErrorKind::Other, "Missing command")),
    }
}

/// # Ensures arguments are empty
fn ensure_args_are_empty<A>(args: A) -> Result<()> where A: Borrow<Args> {
    let args = args.borrow();
    if args.is_empty() {
        Ok(())
    } else {
        Err(Error::new(ErrorKind::InvalidInput, format!("Unknown arguments: {:?}", args)))
    }
}

/// # Prints help
fn print_help() -> Result<()> {
    use dia_args::docs::{Cmd, Docs};
    #[cfg(not(feature="tokio"))]
    use dia_args::docs::{NO_VALUES, Option};

    #[cfg(not(feature="tokio"))]
    let option_timeout = Option::new(OPTION_TIMEOUT, true, NO_VALUES, None, OPTION_TIMEOUT_DOCS);

    let commands = dia_args::make_cmds![
        Cmd::new(CMD_HELP, CMD_HELP_DOCS, None),
    ];
    #[cfg(not(feature="tokio"))]
    let commands = commands.into_iter().chain(dia_args::make_cmds![
        Cmd::new(CMD_READ, CMD_READ_DOCS, Some(dia_args::make_options!(&option_timeout))),
        Cmd::new(CMD_WRITE, CMD_WRITE_DOCS, Some(dia_args::make_options!(&option_timeout))),
    ].into_iter()).collect();

    let mut docs = Docs::new("rw-namaste".into(), concat!().into());
    docs.commands = Some(commands);
    docs.print()
}

#[cfg(not(feature="tokio"))]
fn make_rw_namaste<'a>() -> Result<RwNamaste<&'a [u8], &'a [u8]>> {
    RwNamaste::make(READ_ID, WRITE_ID, RW_TIMEOUT)
}

#[cfg(not(feature="tokio"))]
fn read(mut args: Args) -> Result<()> {
    let timeout = args.take::<ClDuration>(OPTION_TIMEOUT)?.ok_or_else(|| err!("Missing {OPTION_TIMEOUT:?}"))?;

    ensure_args_are_empty(args)?;

    let _read = make_rw_namaste()?.try_read(None)?;
    __info!("[{:?}] Acquired read\n", Time::make_utc()?);

    let start = Instant::now();
    loop {
        thread::sleep(Duration::from_millis(10));
        if Instant::now().duration_since(start) >= *timeout {
            return print_done();
        }
    }
}

#[cfg(not(feature="tokio"))]
fn write(mut args: Args) -> Result<()> {
    let timeout = args.take::<ClDuration>(OPTION_TIMEOUT)?.ok_or_else(|| err!("Missing {OPTION_TIMEOUT:?}"))?;

    ensure_args_are_empty(args)?;

    let _write = make_rw_namaste()?.try_write(None)?;
    __info!("[{:?}] Acquired write\n", Time::make_utc()?);

    let start = Instant::now();
    loop {
        thread::sleep(Duration::from_millis(10));
        if Instant::now().duration_since(start) >= *timeout {
            return print_done();
        }
    }
}

#[cfg(not(feature="tokio"))]
fn print_done() -> Result<()> {
    __info!("[{:?}] Done\n", Time::make_local()?);
    Ok(())
}
