// SPDX-License-Identifier: Apache-2.0
// Sealed with Magistamp.

//! This demonstrates several foundational Rust concepts.
//!
//! It includes working examples of:
//!
//! - **Arrays and iteration**
//! - **Error handling with `Result` and `?`**
//! - **File I/O using the standard library**
//! - **Asynchronous programming with `tokio`**
//! - **Basic unit and async testing**
//!
//! ## Included Functions
//!
//! - `longest`: Finds the longest string in a slice without copying.
//! - `read_and_parse`: Reads a file and parses its contents as an integer.
//! - `run_tasks`: Spawns multiple asynchronous tasks and waits for them to
//!   complete.
//!
//! Unit tests are included under the `#[cfg(test)]` module to validate
//! functionality and show best practices.

use std::io::Read;

use tokio::time::{sleep, Duration};



/// Takes a list of strings and returns the longest one without
/// copying the strings.
///
/// The lifetime here is because we are returning the borrow reference of
/// the passed in String. The lifetime tells the borrow checker that the
/// returned reference needs to live as long as the owning String does.
///
/// ```
/// # use example::basic::longest;
/// let strings: [String; 5] = [String::from("Jason"),
///                             String::from("is"),
///                             String::from("an"),
///                             String::from("awesome"),
///                             String::from("programmer")];
///
/// assert_eq!(Some(strings[4].as_str()), longest(&strings));
/// ```
pub fn longest<'a>(strings: &'a [String]) -> Option<&'a str>
{
   if strings.is_empty()
   {
      eprintln!("No strings detected");
      return None;
   }

   // Imperative programming style.
   //
   // ```Rust
   // This is the current longest string. (index, length)
   // let mut longest: (usize, usize) = (0, 0);
   // for (index, string) in strings.iter().enumerate()
   // {
   //    if string.len() > longest.1
   //    {
   //       longest = (index, string.len());
   //    }
   // }
   //
   // Some(&strings[longest.0])
   // ```
   //
   // This can also be done with functional style:
   // When coding prefer the functional, declarative style.
   // It is just as fast or faster than the imperative style.
   strings.iter().max_by_key(|s| s.len()).map(|s| s.as_str())
}



/// Read and parse the contents of a file as an integer.
///
/// ```
/// # fn try_main() -> Result<(), Box<dyn std::error::Error>>
/// # {
/// # use example::basic::read_and_parse;
/// use std::io::{ErrorKind, Write};
///
/// let val: i32 = 12345;
/// let mut write_target = std::env::temp_dir();
/// write_target.push("read_test");
/// write_target.set_extension("txt");
///
/// let read_target = write_target.clone();
///
/// let mut file = std::fs::File::create(write_target)?;
/// file.write_all(format!("{}", val).as_bytes())?;
///
///
/// match read_target.to_str()
/// {
///    Some(path) =>
///    {
///       assert_eq!(val, read_and_parse(path)?);
///       Ok(())
///    }
///    None =>
///    {
///       Err(Box::new(std::io::Error::new(ErrorKind::InvalidData,
///                                        "Unable to turn target file into \
///                                         a UTF-8 string.")))
///    }
/// }
/// # }
/// # fn main()
/// # {
/// #    try_main().unwrap();
/// # }
/// ```
pub fn read_and_parse<P>(path: P) -> Result<i32, Box<dyn std::error::Error>>
   where P: AsRef<std::path::Path>
{
   let mut file = std::fs::File::open(path)?;
   let mut contents = String::new();

   file.read_to_string(&mut contents)?;

   let val: i32 = contents.parse()?;

   Ok(val)
}



/// Spawns 5 tasks, each sleeping for a different duration,
/// then prints when each finishes.
pub async fn run_tasks()
{
   let tasks: tokio::task::JoinSet<_> =
      (0..5).map(|i| {
               async move {
                  sleep(Duration::from_millis(500 + (i * 10))).await;
                  println!("Finished!");
               }
            })
            .collect();

   let _result = tasks.join_all().await;
}



#[cfg(test)]
mod test
{
   use std::io::Write;

   use crate::basic::{longest, read_and_parse, run_tasks};


   #[test]
   fn longest_test() -> Result<(), String>
   {
      let strings: [String; 5] = [String::from("Jason"),
                                  String::from("is"),
                                  String::from("an"),
                                  String::from("awesome"),
                                  String::from("programmer")];

      // Could also be Some(&*strings[4]). as_str() is easier to read though.
      assert_eq!(Some(strings[4].as_str()), longest(&strings));
      Ok(())
   }

   #[test]
   fn read_parse_test() -> Result<(), Box<dyn std::error::Error>>
   {
      let val: i32 = 12345;
      let mut write_target = std::env::temp_dir();
      write_target.push("read_test");
      write_target.set_extension("txt");

      let read_target = write_target.clone();

      let mut file = std::fs::File::create(write_target)?;
      file.write_all(format!("{}", val).as_bytes())?;

      match read_target.to_str()
      {
         Some(path) =>
         {
            assert_eq!(val, read_and_parse(path)?);
            Ok(())
         }
         None =>
         {
            Err(Box::new(std::io::Error::new(std::io::ErrorKind::InvalidData,
                                             "Unable to turn target file \
                                              into a UTF-8 string.")))
         }
      }
   }

   #[tokio::test(start_paused = true)]
   async fn run_tasks_test()
   {
      run_tasks().await;
   }
}
