package store;

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

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

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

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

        this.lambda = lambda;
        this.totalTime = totalTime;
//        this.threshold = threshold;
        this.timeInterval = timeInterval;
        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);
        //Создание события первого заказа.
        CallEvent ce = new CallEvent();
        ce.time = timeInterval;
        events.offer(ce);
        


    }

    /**
     * Начальное заполнение склада.
     */
    public void initStorage(int n) {
        if (n > storage.capacity) {
            n = (int) 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("00.##");
//        System.out.println("t: " + df.format(time) + "; "
//                + "; profit: " + df.format(profit) + "; inventory: "
//                + nf.format(storage.products.size()) + "; " + (waitProducts ? " W" : "")
//                + (se instanceof AddEvent ? "++++++++++++++++++" + ((AddEvent)se).number: 
//                        se instanceof CallEvent ? "      ~~ " + (storage.capacity - storage.products.size()) : " -->"));
        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(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 = (int) (storage.capacity - storage.products.size());
//                //Пополнение произойдёт через время доставки.
//                ev.time = time + addDelay;
//                //Занесение события пополнения в перечень событий.
//                events.add(ev);
//                //Отметка о сделанном заказе.
//                waitProducts = true;
//            }
        } else if (se instanceof CallEvent) {
                //Новое событие заказа товара.
                CallEvent ce = new CallEvent();
                //Следующий заказ будет через заданный промежуток времени.
                ce.time = time + timeInterval;
                //Занесение события заказа в перечень событий.
                events.add(ce); 
                if (!waitProducts) {
                    //Отметка о заказе.
                    waitProducts = true;
                    //Новое событие пополнения.
                    AddEvent ae = new AddEvent();
                    //Пополнение до полного склада.
                    ae.number = (int) (storage.capacity - storage.products.size());
                    //Пополнение произойдёт через время доставки.
                    ae.time = time + addDelay;
                    //Занесение события пополнения в перечень событий.
                    events.add(ae);        
            }
        } else {
            throw new RuntimeException("Unknown event: " + se.getClass().getName() );
        }
        return ret;
    }

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

}