package store;

import java.text.DecimalFormat;
import java.util.PriorityQueue;

/**
 * Модель склада, продающего товар при случайном прибытии покупателей.
 *
 * Created on 2021-04-24 01:17:35
 *
 * @author Alexander Mikhailovich Kovshov
 */
public class Model1 {

    /**
     * Текущее время от начала опыта.
     */
    double time = 0;
    /**
     * Череда событий в порядке их возникновения.
     */
    java.util.PriorityQueue<StoreEvent> events = new PriorityQueue<>();
    /**
     * Хранилище товара (склад).
     */
    Storage storage = new Storage();
    /**
     * Длительность вычислительного опыта.
     */
    double totalTime;
    /**
     * Мощность (интенсивность) потока покупателей.
     */
    double lambda;
    /**
     * Порог запаса, при котором делается заказ.
     */
    double threshold;
    /**
     * Время доставки заказа.
     */
    double addDelay;
    /**
     * Стоимость хранения единицы товара в единицу времени.
     */
    double productStorageCosts;
    /**
     * Прибыль от продажи единицы товара.
     */
    double productPrice;
    /**
     * Состояние ожидания доставки товара на склад.
     */
    boolean waitProducts = false;
    /**
     * Итоговая прибыль.
     */
    double profit = 0;

    /**
     * Конструктор.
     *
     * @param totalTime Длительность вычислительного опыта.
     * @param lambda Мощность (интенсивность) потока покупателей.
     * @param threshold Порог запаса, при котором делается заказ.
     * @param capacity Вместимость хранилища (склада).
     * @param productStorageCosts Стоимость хранения единицы товара в единицу
     * времени.
     * @param productPrice Прибыль от продажи единицы товара.
     * @param addDelay Время доставки заказа.
     */
    public Model1(double totalTime, double lambda, double threshold, int capacity,
            double productStorageCosts, double productPrice, double addDelay) {

        this.lambda = lambda;
        this.totalTime = totalTime;
        this.threshold = threshold;
        storage.capacity = capacity;
        this.productStorageCosts = productStorageCosts;
        this.productPrice = productPrice;
        this.addDelay = addDelay;
        initStorage(capacity);
        //Создание события прибытия первого покупателя.
        RemoveEvent ae = new RemoveEvent();
        ae.time = -Math.log(1. - Math.random()) / lambda;
        events.offer(ae);


    }

    /**
     * Начальное заполнение склада.
     */
    public void initStorage(int n) {
        if (n > storage.capacity) {
            n = storage.capacity;
        }
        storage.products.clear();
        for (int i = 0; i < n; i++) {
            storage.products.add(new Product(time));
        }
    }

    /**
     * Обработка события на складе.
     *
     * @param se Событие.
     * @return Число недостающих товаров.
     */
    public int processStoreEvent(StoreEvent se) {
        time = se.time;
        DecimalFormat df = new DecimalFormat("000.00");
        DecimalFormat nf = new DecimalFormat("###.##");
//        System.out.println("t: " + df.format(time) + "; "
//                + "; profit: " + df.format(profit) + "; inventory: "
//                + nf.format(storage.products.size()) + "; " + (waitProducts ? " W" : "")
//                + (se instanceof AddEvent ? "++++++++++++++++++" : ""));
        int ret = 0;
        //Пополнение склада товарами.
        if (se instanceof AddEvent) {
            int n = ((AddEvent) se).number;
            if (storage.capacity < storage.products.size() + n) {
                throw new RuntimeException("Storage overflow");
            }
            for (int i = 0; i < n; i++) {
                Product prod = new Product();
                prod.time = time;
                storage.products.add(prod);
            }
            //Отметка о выполнении заказа.
            waitProducts = false;
        } else //Обращение за товаром.
        if (se instanceof RemoveEvent) {
            //Следующее обращение.
            RemoveEvent nextRemove = new RemoveEvent();
            //Время следующего обращения за товаром.
            nextRemove.time = this.time + -Math.log(1. - Math.random()) / lambda;
            //Занесение следующего обращения в перечень событий.
            events.add(nextRemove);
            //Если склад пуст...
            if (storage.products.isEmpty()) {
                //Число нехватки товара.
                ret++;
            } //Если склад не пуст
            else {
                //Товар уходит со склада.
                Product prod = storage.products.remove(0);
                //Вычет из прибыли затрат на хранение.
                profit -= (time - prod.time) * productStorageCosts;
                //Прибавка к прибыли от продажи товара.
                profit += productPrice;
            }
            //Если остаток товаров на складе ниже порога и заказ не сделан...
            if (storage.products.size() < threshold && !waitProducts) {
                //Новое событие пополнения.
                AddEvent ev = new AddEvent();
                //Пополнение до полного склада.
                ev.number = storage.capacity - storage.products.size();
                //Пополнение произойдёт через время доставки.
                ev.time = time + addDelay;
                //Занесение события пополнения в перечень событий.
                events.add(ev);
                //Отметка о сделанном заказе.
                waitProducts = true;
            }
        } else {
            throw new RuntimeException("Unknown event.");
        }
        return ret;
    }

    public static void main(String[] args) {
        Model1 model = new Model1(
                50, //Длительность вычислительного опыта. 
                1,  //Мощность (интенсивность) потока покупателей. 
                5,  //Порог запаса, при котором делается заказ.
                10, //Вместимость хранилища (склада).
                1,  //Стоимость хранения единицы товара в единицу времени.
                10, //Прибыль от продажи единицы товара.
                5   //Время доставки заказа. 
        );
        while (model.time < model.totalTime) {
            model.processStoreEvent(model.events.poll());
        }
            System.out.println("Средняя прибыль за единицу времени: " + (model.profit / model.time));
    }

}