// 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::collections::BTreeMap;
use std::collections::VecDeque;
use std::cell::RefCell;

use dvcompute::simulation::Point;
use dvcompute::simulation::strategy::QueueStorage;

/// A basic queue storage.
struct PriorityStorage<T> {

    /// The underlying queue.
    queue: RefCell<BTreeMap<isize, VecDeque<T>>>,

    /// Whether the FCFS queue.
    is_fcfs: bool
}

impl<T> PriorityStorage<T> {

    /// Create a new queue storage.
    pub fn new(is_fcfs: bool) -> Self {
        PriorityStorage {
            queue: RefCell::new(BTreeMap::new()),
            is_fcfs: is_fcfs
        }
    }
}

impl<T> QueueStorage for PriorityStorage<T> {

    type Priority = isize;
    type Item     = T;

    #[inline]
    fn is_empty(&self, _p: &Point) -> bool {
        let queue = self.queue.borrow();
        queue.is_empty()
    }

    fn pop(&self, _p: &Point) -> Option<Self::Item> {
        let (result, key_to_remove) = {
            let mut queue = self.queue.borrow_mut();
            match queue.iter_mut().next() {
                None => (None, None),
                Some((key, deque)) => {
                    let item = if self.is_fcfs {
                        deque.pop_front()
                    } else {
                        deque.pop_back()
                    };
                    if deque.is_empty() {
                        (item, Some(*key))
                    } else {
                        (item, None)
                    }
                }
            }
        };
        match key_to_remove {
            None => {},
            Some(key) => {
                let mut queue = self.queue.borrow_mut();
                let _ = queue.remove(&key);
            }
        }
        result
    }

    fn push(&self, _item: Self::Item, _p: &Point) {
        panic!("Not supported operation");
    }

    fn push_with_priority(&self, priority: Self::Priority, item: Self::Item, _p: &Point) {
        let key = - priority;
        let deque_to_insert = {
            let mut queue = self.queue.borrow_mut();
            match queue.get_mut(&key) {
                None => {
                    let mut deque = VecDeque::new();
                    deque.push_back(item);
                    Some(deque)
                },
                Some(deque) => {
                    deque.push_back(item);
                    None
                }
            }
        };
        match deque_to_insert {
            None => {},
            Some(deque) => {
                let mut queue = self.queue.borrow_mut();
                queue.insert(key, deque);
            }
        }
    }

    fn remove_by<F>(&self, predicate: F, p: &Point) -> Option<Self::Item>
        where F: Fn(&Self::Item) -> bool
    {
        let (result, key_to_remove) = self.remove_and_find_key(predicate, p);
        match key_to_remove {
            None => {},
            Some(key) => {
                let mut queue = self.queue.borrow_mut();
                let _ = queue.remove(&key);
            }
        }
        result
    }

    fn exists<F>(&self, predicate: F, p: &Point) -> bool
        where F: Fn(&Self::Item) -> bool
    {
        self.exists_impl(predicate, p)
    }

    fn find<F>(&self, predicate: F, p: &Point) -> Option<Self::Item>
        where F: Fn(&Self::Item) -> bool,
              T: Clone
    {
        self.find_impl(predicate, p)
    }
}

impl<T> PriorityStorage<T> {

    /// Remove an item satisfying the predicate and return a key
    /// of the deque that becomes empty if there is such one.
    fn remove_and_find_key<F>(&self, predicate: F, p: &Point) -> (Option<T>, Option<isize>)
        where F: Fn(&T) -> bool
    {
        if !self.is_fcfs {
            panic!("Not supported mode")
        } else {
            let mut queue = self.queue.borrow_mut();
            for (key, deque) in queue.iter_mut() {
                {
                    match self.remove_from_deque(deque, &predicate, p) {
                        None => {},
                        Some(item) => {
                            if deque.is_empty() {
                                return (Some(item), Some(*key))
                            } else {
                                return (Some(item), None)
                            }
                        }
                    }
                }
            }
            (None, None)
        }
    }

    /// Test whether there is an item satisfying the predicate.
    fn exists_impl<F>(&self, predicate: F, p: &Point) -> bool
        where F: Fn(&T) -> bool
    {
        if !self.is_fcfs {
            panic!("Not supported mode")
        } else {
            let queue = self.queue.borrow();
            for (_, deque) in queue.iter() {
                {
                    match self.exists_in_deque(deque, &predicate, p) {
                        false => {},
                        true => return true
                    }
                }
            }
            false
        }
    }

    /// Find an item satisfying the predicate.
    fn find_impl<F>(&self, predicate: F, p: &Point) -> Option<T>
        where F: Fn(&T) -> bool,
              T: Clone
    {
        if !self.is_fcfs {
            panic!("Not supported mode")
        } else {
            let queue = self.queue.borrow();
            for (_, deque) in queue.iter() {
                {
                    match self.find_in_deque(deque, &predicate, p) {
                        None => {},
                        y@Some(_) => return y
                    }
                }
            }
            None
        }
    }

    /// Remove from the deque an item satisfying the predicate.
    fn remove_from_deque<F>(&self, deque: &mut VecDeque<T>, predicate: &F, _p: &Point) -> Option<T>
        where F: Fn(&T) -> bool
    {
        if !self.is_fcfs {
            panic!("Not supported mode")
        } else {
            for i in 0 .. deque.len() {
                if predicate(&deque[i]) {
                    return deque.remove(i);
                }
            }
            None
        }
    }

    /// Test whether in the deque there is an item satisfying the predicate.
    fn exists_in_deque<F>(&self, deque: &VecDeque<T>, predicate: &F, _p: &Point) -> bool
        where F: Fn(&T) -> bool
    {
        if !self.is_fcfs {
            panic!("Not supported mode")
        } else {
            for i in 0 .. deque.len() {
                if predicate(&deque[i]) {
                    return true;
                }
            }
            false
        }
    }

    /// Find an item satisfying the predicate in the deque.
    fn find_in_deque<F>(&self, deque: &VecDeque<T>, predicate: &F, _p: &Point) -> Option<T>
        where F: Fn(&T) -> bool,
              T: Clone
    {
        if !self.is_fcfs {
            panic!("Not supported mode")
        } else {
            for i in 0 .. deque.len() {
                if predicate(&deque[i]) {
                    return Some(deque[i].clone());
                }
            }
            None
        }
    }
}

/// A queue storage based on the FCFS strategy.
pub struct FCFSStorage<T> {

    /// The queue storage itself.
    queue: PriorityStorage<T>
}

impl<T> FCFSStorage<T> {

    /// Create a new storage.
    pub fn new() -> Self {
        FCFSStorage { queue: PriorityStorage::new(true) }
    }
}

impl<T> QueueStorage for FCFSStorage<T> {

    type Priority = isize;
    type Item     = T;

    #[inline]
    fn is_empty(&self, p: &Point) -> bool {
        self.queue.is_empty(p)
    }

    #[inline]
    fn pop(&self, p: &Point) -> Option<Self::Item> {
        self.queue.pop(p)
    }

    #[inline]
    fn push(&self, item: Self::Item, p: &Point) {
        self.queue.push(item, p)
    }

    #[inline]
    fn push_with_priority(&self, priority: Self::Priority, item: Self::Item, p: &Point) {
        self.queue.push_with_priority(priority, item, p)
    }

    #[inline]
    fn remove_by<F>(&self, predicate: F, p: &Point) -> Option<Self::Item>
        where F: Fn(&Self::Item) -> bool
    {
        self.queue.remove_by(predicate, p)
    }

    #[inline]
    fn exists<F>(&self, predicate: F, p: &Point) -> bool
        where F: Fn(&Self::Item) -> bool
    {
        self.queue.exists(predicate, p)
    }

    #[inline]
    fn find<F>(&self, predicate: F, p: &Point) -> Option<Self::Item>
        where F: Fn(&Self::Item) -> bool,
              T: Clone
    {
        self.queue.find(predicate, p)
    }
}

/// A queue storage based on the LCFS strategy.
pub struct LCFSStorage<T> {

    /// The queue storage itself.
    queue: PriorityStorage<T>
}

impl<T> LCFSStorage<T> {

    /// Create a new storage.
    pub fn new() -> Self {
        LCFSStorage { queue: PriorityStorage::new(false) }
    }
}

impl<T> QueueStorage for LCFSStorage<T> {

    type Priority = isize;
    type Item     = T;

    #[inline]
    fn is_empty(&self, p: &Point) -> bool {
        self.queue.is_empty(p)
    }

    #[inline]
    fn pop(&self, p: &Point) -> Option<Self::Item> {
        self.queue.pop(p)
    }

    #[inline]
    fn push(&self, item: Self::Item, p: &Point) {
        self.queue.push(item, p)
    }

    #[inline]
    fn push_with_priority(&self, priority: Self::Priority, item: Self::Item, p: &Point) {
        self.queue.push_with_priority(priority, item, p)
    }

    #[inline]
    fn remove_by<F>(&self, predicate: F, p: &Point) -> Option<Self::Item>
        where F: Fn(&Self::Item) -> bool
    {
        self.queue.remove_by(predicate, p)
    }

    #[inline]
    fn exists<F>(&self, predicate: F, p: &Point) -> bool
        where F: Fn(&Self::Item) -> bool
    {
        self.queue.exists(predicate, p)
    }

    #[inline]
    fn find<F>(&self, predicate: F, p: &Point) -> Option<Self::Item>
        where F: Fn(&Self::Item) -> bool,
              T: Clone
    {
        self.queue.find(predicate, p)
    }
}
