// Copyright (c) 2020-2022  David Sorokin <david.sorokin@gmail.com>, based in Yoshkar-Ola, Russia
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use std::rc::Rc;

use dvcompute::simulation::event::*;
use dvcompute::simulation::process::*;

use crate::simulation::block::*;
use crate::simulation::queue::*;
use crate::simulation::facility::*;
use crate::simulation::transact::*;

/// The Advance block to simulate some activity.
#[inline]
pub fn advance_block<M, T>(comp: M) -> impl Block<Input = T, Output = T>
    where M: Process<Item = ()> + 'static,
          T: 'static
{
    cons_block(move |x| {
        comp.map(move |()| x)
    })
}

/// The Queue block to enqueue.
#[inline]
pub fn queue_block<T>(queue: Rc<Queue>, increment: isize) -> impl Block<Input = Rc<Transact<T>>, Output = Rc<Transact<T>>>
    where T: 'static
{
    cons_block(move |x: Rc<Transact<T>>| {
        Queue::enqueue(queue, x.clone(), increment)
            .map(move |()| x)
            .into_process()
    })
}

/// The Depart block to depart from the queue.
#[inline]
pub fn depart_block<T>(queue: Rc<Queue>, decrement: isize) -> impl Block<Input = Rc<Transact<T>>, Output = Rc<Transact<T>>>
    where T: 'static
{
    cons_block(move |x: Rc<Transact<T>>| {
        Queue::dequeue(queue, x.clone(), decrement)
            .map(move |()| x)
            .into_process()
    })
}

/// The Seize block to seize the facility.
#[inline]
pub fn seize_block<T>(facility: Rc<Facility<T>>) -> impl Block<Input = Rc<Transact<T>>, Output = Rc<Transact<T>>>
    where T: 'static
{
    cons_block(move |x: Rc<Transact<T>>| {
        seize_facility(facility, x.clone())
            .map(move |()| x)
            .into_process()
    })
}

/// The Release block to release the facility.
#[inline]
pub fn release_block<T>(facility: Rc<Facility<T>>) -> impl Block<Input = Rc<Transact<T>>, Output = Rc<Transact<T>>>
    where T: 'static
{
    cons_block(move |x: Rc<Transact<T>>| {
        release_facility(facility, x.clone())
            .map(move |()| x)
            .into_process()
    })
}

/// The Return block to release the facility.
#[inline]
pub fn return_block<T>(facility: Rc<Facility<T>>) -> impl Block<Input = Rc<Transact<T>>, Output = Rc<Transact<T>>>
    where T: 'static
{
    cons_block(move |x: Rc<Transact<T>>| {
        return_facility(facility, x.clone())
            .map(move |()| x)
            .into_process()
    })
}

/// The Preempt block to preempt the facility.
#[inline]
pub fn preempt_block<T>(facility: Rc<Facility<T>>, mode: PreemptBlockMode<T>) -> impl Block<Input = Rc<Transact<T>>, Output = Rc<Transact<T>>>
    where T: 'static
{
    cons_block(move |x: Rc<Transact<T>>| {
        preempt_facility(facility, x.clone(), mode.into())
            .map(move |()| x)
            .into_process()
    })
}

/// The Preempt block mode.
#[derive(Clone)]
pub struct PreemptBlockMode<T> {

    /// The Priority mode; otherwise, the Interrupt mode.
    pub priority_mode: bool,

    /// Where to transfer the preempted transact,
    /// passing in the remaining time from the process holding
    /// computation such as the Advance block.
    pub transfer: Option<PreemptBlockTransfer<T>>,

    /// The Remove mode.
    pub remove_mode: bool
}

impl<T> From<FacilityPreemptMode<T>> for PreemptBlockMode<T>
    where T: 'static
{
    fn from(mode: FacilityPreemptMode<T>) -> Self {
        let FacilityPreemptMode { priority_mode, transfer, remove_mode } = mode;
        let transfer = match transfer {
            None => None,
            Some(f) => Some({
                PreemptBlockTransfer::new(move |dt| {
                    cons_block(move |transact| {
                        f.call_box((transact, dt))
                    }).into_boxed()
                })
            })
        };

        PreemptBlockMode {
            priority_mode: priority_mode,
            transfer: transfer,
            remove_mode: remove_mode
        }
    }
}

/// Proceed with the computation by the specified preempted transact
/// and remaining time from the process holding computation such as the Advance block.
pub struct PreemptBlockTransfer<T> {
    f: Box<dyn PreemptBlockTransferFnBoxClone<T>>
}

impl<T> PreemptBlockTransfer<T> {

    /// Create a new transfer.
    #[inline]
    pub fn new<F>(f: F) -> Self
        where F: FnOnce(Option<f64>) -> BlockBox<Rc<Transact<T>>, ()> + Clone + 'static
    {
        PreemptBlockTransfer {
            f: Box::new(f)
        }
    }

    /// Call the boxed function.
    #[inline]
    pub fn call_box(self, arg: Option<f64>) -> BlockBox<Rc<Transact<T>>, ()> {
        let PreemptBlockTransfer { f } = self;
        f.call_box(arg)
    }
}

impl<T> Clone for PreemptBlockTransfer<T> {

    #[inline]
    fn clone(&self) -> Self {
        PreemptBlockTransfer {
            f: self.f.call_clone()
        }
    }
}

/// A trait to support the stable version of Rust, where there is no `FnBox`.
trait PreemptBlockTransferFnBox<T> {

    /// Call the corresponding function.
    fn call_box(self: Box<Self>, args: Option<f64>) -> BlockBox<Rc<Transact<T>>, ()>;
}

impl<T, F> PreemptBlockTransferFnBox<T> for F
    where F: FnOnce(Option<f64>) -> BlockBox<Rc<Transact<T>>, ()>
{
    fn call_box(self: Box<Self>, args: Option<f64>) -> BlockBox<Rc<Transact<T>>, ()> {
        let this: Self = *self;
        this(args)
    }
}

/// A trait to implement a cloneable `FnBox`.
trait PreemptBlockTransferFnBoxClone<T>: PreemptBlockTransferFnBox<T> {

    /// Clone the function.
    fn call_clone(&self) -> Box<dyn PreemptBlockTransferFnBoxClone<T>>;
}

impl<T, F> PreemptBlockTransferFnBoxClone<T> for F
    where F: FnOnce(Option<f64>) -> BlockBox<Rc<Transact<T>>, ()> + Clone + 'static
{
    fn call_clone(&self) -> Box<dyn PreemptBlockTransferFnBoxClone<T>> {
        Box::new(self.clone())
    }
}
