
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 phone_chain(line: Rc<Queue>, prof: Rc<Facility<f64>>) -> BlockBox<Rc<Transact<f64>>, ()> {
    cons_block({
        let prof = prof.clone();
        move |a| {
            Facility::is_interrupted(prof)
                .into_process()
                .flat_map(move |f| {
                    if f {
                        transfer_block(busy_chain())
                            .run(a)
                            .into_boxed()
                    } else {
                        return_process(a)
                            .into_boxed()
                    }
                })
        }
    })
    .and_then({
        let mode = PreemptBlockMode {
            priority_mode: true,
            transfer: Some(add_chain(line, prof.clone())),
            remove_mode: false
        };
        preempt_block(prof.clone(), mode)
    })
    .and_then({
        advance_block(random_exponential_process_(200.0))
    })
    .and_then(return_block(prof))
    .and_then(busy_chain())
    .into_boxed()
}

fn busy_chain() -> BlockBox<Rc<Transact<f64>>, ()> {
    terminate_block()
        .into_boxed()
}

fn student_chain(line: Rc<Queue>, prof: Rc<Facility<f64>>) -> BlockBox<Rc<Transact<f64>>, ()> {
    queue_block(line.clone(), 1)
        .and_then(seize_block(prof.clone()))
        .and_then(depart_block(line.clone(), 1))
        .and_then({
            advance_block(random_exponential_process_(1000.0))
        })
        .and_then(let_go_chain(line, prof))
        .into_boxed()
}

fn let_go_chain(_line: Rc<Queue>, prof: Rc<Facility<f64>>) -> BlockBox<Rc<Transact<f64>>, ()> {
    release_block(prof)
        .and_then(terminate_block())
        .into_boxed()
}

fn add_chain(line: Rc<Queue>, prof: Rc<Facility<f64>>) -> PreemptBlockTransfer<f64> {
    PreemptBlockTransfer::new(move |dt| {
        let dt = match dt {
            None => 0.0,
            Some(dt) => dt
        };
        advance_block(hold_process(dt + 300.0))
            .and_then(transfer_block(let_go_chain(line, prof)))
            .into_boxed()
    })
}

#[test]
fn test_model() {

    let specs = Specs {
        start_time: 0.0,
        stop_time: 10000000.0,
        // stop_time: 1000000000.0,
        dt: 1.0,
        generator_type: GeneratorType::Simple
    };

    let model = {
        Queue::new()
            .run_in_start_time()
            .flat_map(move |line| {
                let line = Rc::new(line);
                Facility::new()
                    .run_in_start_time()
                    .flat_map(move |prof| {
                        let prof = Rc::new(prof);
                        new_random_uniform_observable(2000.0 - 500.0, 2000.0 + 500.0)
                            .run_()
                            .run_in_start_time()
                            .flat_map(move |phone_obs| {
                                let phones = observable_generator_block(phone_obs, 1);
                                let phone_chain = {
                                    let line = line.clone();
                                    let prof = prof.clone();
                                    BlockFn::new(move || {
                                        phone_chain(line.clone(), prof.clone())
                                    })
                                };

                                new_random_uniform_observable(2000.0 - 500.0, 2000.0 + 500.0)
                                    .run_()
                                    .run_in_start_time()
                                    .flat_map(move |student_obs| {
                                        let students = observable_generator_block0(student_obs);
                                        let student_chain = {
                                            let line = line.clone();
                                            let prof = prof.clone();
                                            BlockFn::new(move || {
                                                student_chain(line.clone(), prof.clone())
                                            })
                                        };

                                        phones.run(phone_chain)
                                            .run()
                                            .run_in_start_time()
                                            .flat_map(move |()| {
                                                students.run(student_chain)
                                                    .run()
                                                    .run_in_start_time()
                                                    .flat_map(move |()| {
                                                        // Facility::wait_time(prof)
                                                        Facility::holding_time(prof)
                                                        // Facility::util_count_stats(prof)
                                                            .run_in_stop_time()
                                                    })
                                            })
                                    })
                            })
                    })
            })
    };
    let result = model.run(specs).unwrap();

    println!("The result is {} (691.7 +/- 3 * 990)", result);
    assert!((result.mean - 691.7).abs() < 3.0 * 990.0);
}
