
extern crate dvcompute;
extern crate dvcompute_gpss;

use std::rc::Rc;

use dvcompute::simulation::Specs;
use dvcompute::simulation::generator::*;
use dvcompute::simulation::simulation::*;
use dvcompute::simulation::event::*;
use dvcompute::simulation::composite::*;
use dvcompute::simulation::observable::random::*;
use dvcompute::simulation::process::*;
use dvcompute::simulation::process::random::*;

use dvcompute_gpss::simulation::transact::*;
use dvcompute_gpss::simulation::queue::*;
use dvcompute_gpss::simulation::facility::*;
use dvcompute_gpss::simulation::block::*;
use dvcompute_gpss::simulation::block::ops::*;
use dvcompute_gpss::simulation::block::generator::*;
use dvcompute_gpss::simulation::block::basic::*;

fn block_chain1(joeq: Rc<Queue>, joe: Rc<Facility<f64>>) -> BlockBox<Rc<Transact<f64>>, ()> {
    queue_block(joeq.clone(), 1)
        .and_then(seize_block(joe.clone()))
        .and_then(depart_block(joeq, 1))
        .and_then({
            advance_block(random_uniform_process_(18.0 - 6.0, 18.0 + 6.0))
        })
        .and_then(release_block(joe))
        .and_then(terminate_block())
        .into_boxed()
}

fn block_chain2(joeq: Rc<Queue>, joe: Rc<Facility<f64>>) -> BlockBox<Rc<Transact<f64>>, ()> {
    queue_block(joeq.clone(), 1)
        .and_then(seize_block(joe.clone()))
        .and_then(depart_block(joeq, 1))
        .and_then({
            advance_block(random_uniform_process_(10.0 - 2.0, 10.0 + 2.0))
        })
        .and_then({
            advance_block(random_uniform_process_(18.0 - 6.0, 18.0 + 6.0))
        })
        .and_then(release_block(joe))
        .and_then(terminate_block())
        .into_boxed()
}

#[test]
fn test_model() {

    let specs = Specs {
        start_time: 0.0,
        stop_time: 480.0,
        // stop_time: 480000.0,
        // stop_time: 48000000.0,
        // stop_time: 480000000.0,
        dt: 1.0,
        generator_type: GeneratorType::Simple
    };

    let model = {
        Queue::new()
            .run_in_start_time()
            .flat_map(move |joeq| {
                let joeq = Rc::new(joeq);
                Facility::new()
                    .run_in_start_time()
                    .flat_map(move |joe| {
                        let joe = Rc::new(joe);
                        new_random_uniform_observable(35.0 - 10.0, 35.0 + 10.0)
                            .run_()
                            .run_in_start_time()
                            .flat_map(move |client_obs1| {
                                let clients1 = observable_generator_block0(client_obs1);
                                let chain1 = {
                                    let joeq = joeq.clone();
                                    let joe = joe.clone();
                                    BlockFn::new(move || {
                                        block_chain1(joeq.clone(), joe.clone())
                                    })
                                };

                                new_random_uniform_observable(60.0 - 20.0, 60.0 + 20.0)
                                    .run_()
                                    .run_in_start_time()
                                    .flat_map(move |client_obs2| {
                                        let clients2 = observable_generator_block0(client_obs2);
                                        let chain2 = {
                                            let joeq = joeq.clone();
                                            let joe = joe.clone();
                                            BlockFn::new(move || {
                                                block_chain2(joeq.clone(), joe.clone())
                                            })
                                        };

                                        clients1.run(chain1)
                                            .run()
                                            .run_in_start_time()
                                            .flat_map(move |()| {
                                                clients2.run(chain2)
                                                    .run()
                                                    .run_in_start_time()
                                                    .flat_map(move |()| {
                                                        // Facility::wait_time(joe)
                                                        Facility::holding_time(joe)
                                                        // Facility::util_count_stats(joe)
                                                            .run_in_stop_time()
                                                    })
                                            })
                                    })
                            })
                    })
            })
    };
    let result = model.run(specs).unwrap();

    println!("The result is {} (21.7 +/- 3 * 6)", result);
    assert!((result.mean - 21.7).abs() < 3.0 * 6.0);
}
