mod argp;
mod err;
mod procres;

use std::{sync::mpsc, time::Duration};


use qsu::{
  argp::ArgParser,
  rt::{InitCtx, RunEnv, ServiceHandler, SrvAppRt, SvcEvt, TermCtx}
};

use err::Error;
use procres::ProcRes;


struct InitData {
  name: String,
  value: u32
}

struct TermData {
  name: String,
  value: u32
}

struct AnotherTermData {
  name: String,
  value: u32
}


struct MyService {
  rx: mpsc::Receiver<SvcEvt>
}

impl ServiceHandler for MyService {
  type AppErr = Error;

  fn init(&mut self, ictx: &mut InitCtx) -> Result<(), Self::AppErr> {
    ictx.report(Some("Entered init"));

    // Extract passthrough initialization data
    let id = ictx.take::<InitData>().unwrap();
    assert_eq!(id.name, "hello");
    assert_eq!(id.value, 42);

    log::info!("init passthrough name={}, value={}", id.name, id.value);

    // Add some passthrough termination data
    let atd = AnotherTermData {
      name: "extra bye".to_string(),
      value: 23
    };
    ictx.term_passthrough(atd);

    Ok(())
  }

  fn run(&mut self, _re: &RunEnv) -> Result<(), Self::AppErr> {
    log::info!("Wait for termination event for up to 8 seconds ..");
    loop {
      let Ok(evt) = self.rx.recv_timeout(Duration::from_secs(8)) else {
        break;
      };
      if let SvcEvt::Shutdown(_) = evt {
        break;
      }
    }

    Ok(())
  }

  fn shutdown(&mut self, tctx: &mut TermCtx) -> Result<(), Self::AppErr> {
    tctx.report(Some("Entered shutdown"));
    log::trace!("Running shutdown()");

    // Extract termination data added from main
    let td = tctx.take::<TermData>().unwrap();
    assert_eq!(td.name, "bye");
    assert_eq!(td.value, 17);
    log::info!("term passthrough name={}, value={}", td.name, td.value);

    // Extract termination data added in init()
    let atd = tctx.take::<AnotherTermData>().unwrap();
    assert_eq!(atd.name, "extra bye");
    assert_eq!(atd.value, 23);
    log::info!(
      "another term passthrough name={}, value={}",
      atd.name,
      atd.value
    );

    Ok(())
  }
}


fn main() -> ProcRes {
  // In the future we'll be able to use Try to implement support for implicit
  // conversion to ProcRes from a Result using `?`, but for now use this hack.
  ProcRes::into(main2().into())
}

fn main2() -> Result<(), Error> {
  // Derive default service name from executable name.
  let svcname = qsu::default_service_name()
    .expect("Unable to determine default service name");

  // Create a closure that will generate a sync service runtime with a
  // service event handler that will forward any messages it receives to a
  // channel receiver end-point held by the main service handler.
  let bldr = Box::new(|| {
    let (tx, rx) = mpsc::channel();
    SrvAppRt::Sync {
      svcevt_handler: Box::new(move |msg| {
        // Just foward messages to runtime handler
        tx.send(msg).unwrap();
      }),
      rt_handler: Box::new(MyService { rx })
    }
  });

  let id = InitData {
    name: String::from("hello"),
    value: 42
  };
  let td = TermData {
    name: String::from("bye"),
    value: 17
  };

  // Parse, and process, command line arguments.
  // Add a service registration callback that will set service's description.
  let mut argsproc = argp::AppArgsProc { bldr };
  let ap = ArgParser::new(&svcname, &mut argsproc)
    .runsvc_proc(|rctx| rctx.init_passthrough(id).term_passthrough(td))
    .regsvc_proc(|mut regsvc| {
      // Set description, but only if it hasn't been set already (presumably
      // via the command line).
      if regsvc.description.is_none() {
        regsvc
          .description_ref("A sync server that says hello every 30 seconds");
      }
      regsvc
    });
  ap.proc()?;

  Ok(())
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :
