package com.javarush.task.task27.task2712.kitchen;

import com.javarush.task.task27.task2712.ConsoleHelper;
import com.javarush.task.task27.task2712.Tablet;
import com.javarush.task.task27.task2712.statistic.StatisticManager;
import com.javarush.task.task27.task2712.statistic.event.CookedOrderEventDataRow;

import java.util.Observable;
import java.util.Observer;

public class Cook extends Observable implements Observer {
    private String name;

    public Cook(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name;
    }

    @Override
    public void update(Observable o, Object arg) {
        Tablet tablet = (Tablet)o;
        Order order = (Order) arg;
        ConsoleHelper.writeMessage(String.format("Start cooking — %s, cooking time %dmin", order, order.getTotalCookingTime()));
        StatisticManager.getInstance().register(new CookedOrderEventDataRow(tablet.toString(), this.name, order.getTotalCookingTime()*60, order.getDishes()));
        setChanged();
        notifyObservers(order);
    }
}
package com.javarush.task.task27.task2712;

import com.javarush.task.task27.task2712.kitchen.Dish;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

public class ConsoleHelper {
    private static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));


    public static void writeMessage(String message) {
        System.out.println(message);
    }

    public static String readString() throws IOException {
        return br.readLine();
    }

    public static List<Dish> getAllDishesForOrder() throws IOException  {
        List<Dish> list = new ArrayList<Dish>();
        writeMessage(Dish.allDishesToString());
        writeMessage("Выберите блюда. Для завершения наберите 'exit'.");
        String str = null;
        str = readString().trim();
        while (!str.equalsIgnoreCase("exit")) {
            if (Dish.allDishesToString().toLowerCase().contains(str.toLowerCase()))
                list.add(Dish.valueOf(str));
            else
                writeMessage("Такого блюда нет!");
            writeMessage("Введите строку — название блюда:");
            str = readString().trim();
        }
        return list;
    }
}
package com.javarush.task.task27.task2712.kitchen;

public enum Dish {
    Fish (25),
    Steak (30),
    Soup (15),
    Juice (5),
    Water (3);

    private int duration;

    public int getDuration() {
        return duration;
    }
    Dish(int duration) {
        this.duration = duration;
    }

    public static String allDishesToString(){
        StringBuilder sb = new StringBuilder("");
        for (Dish dish : Dish.values())
            sb.append(", " + dish);
        return sb.toString().substring(2);
    }
}
package com.javarush.task.task27.task2712.kitchen;

import com.javarush.task.task27.task2712.ConsoleHelper;
import com.javarush.task.task27.task2712.Tablet;

import java.io.IOException;
import java.util.List;

public class Order {
    private final Tablet tablet;
    protected List<Dish> dishes;

    public Order(Tablet tablet) throws IOException {
        this.tablet = tablet;
        this.dishes = ConsoleHelper.getAllDishesForOrder();
    }

    public List<Dish> getDishes() {
        return dishes;
    }

    public boolean isEmpty() {
        return dishes.isEmpty();
    }

    @Override
    public String toString(){
        if (dishes.isEmpty())
            return "";
        else
            return "Your order: " + dishes + " of " + tablet;
    }

    public int getTotalCookingTime() {
        int i = 0;
        for (Dish dish : dishes) {
            i += dish.getDuration();
        }
        return i;
    }

}
package com.javarush.task.task27.task2712;

import com.javarush.task.task27.task2712.ad.AdvertisementManager;
import com.javarush.task.task27.task2712.ad.NoVideoAvailableException;
import com.javarush.task.task27.task2712.kitchen.Order;

import java.io.IOException;
import java.util.Observable;
import java.util.logging.Level;
import java.util.logging.Logger;

import static com.javarush.task.task27.task2712.ConsoleHelper.writeMessage;

public class Tablet extends Observable {

    final int number;
    static Logger logger = Logger.getLogger(Tablet.class.getName());

    public Tablet(int number) {
        this.number = number;
    }

    @Override
    public String toString() {
        return "Tablet{number=" + number + "}";
    }

    public int getNumber() {
        return number;
    }

    public Order createOrder(){
        Order order = null;
        AdvertisementManager advertisementManager = null;
        try {
            order = new Order(this);
            if (!order.isEmpty()) {
                writeMessage(order.toString());
                advertisementManager = new AdvertisementManager(order.getTotalCookingTime()*60);
                advertisementManager.processVideos();
                setChanged();
                notifyObservers(order);
            }
        } catch (NoVideoAvailableException e) {
            logger.log(Level.INFO,"No video is available for the order " + order);
        } catch (IOException e) {
            logger.log(Level.SEVERE,"Console is unavailable.");
        }
        return order;
    }
}
package com.javarush.task.task27.task2712.ad;

import java.util.ArrayList;
import java.util.List;

public class AdvertisementStorage {
    private static AdvertisementStorage ourInstance = new AdvertisementStorage();
    private final List<Advertisement> videos = new ArrayList<>();

    public static AdvertisementStorage getInstance() {
        return ourInstance;
    }

    private AdvertisementStorage() {
        Object someContent = new Object();
        add(new Advertisement(someContent, "First Video", 5000, 100, 3 * 60)); // 3 min
        add(new Advertisement(someContent, "Second Video", 100, 10, 15 * 60)); //15 min
        add(new Advertisement(someContent, "Third Video", 400, 2, 10 * 60)); //10 min
    }

    public List<Advertisement> list() {
        return videos;
    }

    public void add(Advertisement advertisement) {
        videos.add(advertisement);
    }
}
package com.javarush.task.task27.task2712.ad;

public class Advertisement {
    private Object content;
    private String name;

    public Object getContent() {
        return content;
    }

    public long getInitialAmount() {
        return initialAmount;
    }

    private long initialAmount;
    private int hits;
    private int duration;
    private long amountPerOneDisplaying;

    public Advertisement(Object content, String name, long initialAmount, int hits, int duration) {
        this.content = content;
        this.name = name;
        this.initialAmount = initialAmount;
        this.hits = hits;
        this.duration = duration;
        this.amountPerOneDisplaying = initialAmount / hits;
    }

    public String getName() {
        return name;
    }

    public int getDuration() {
        return duration;
    }

    public long getAmountPerOneDisplaying() {
        return amountPerOneDisplaying;
    }

    public int getHits() {
        return hits;
    }

    public void revalidate() throws NoVideoAvailableException {
        if (hits < 1) {
            throw new NoVideoAvailableException();
        }
        hits--;
    }
}
package com.javarush.task.task27.task2712.ad;

import com.javarush.task.task27.task2712.ConsoleHelper;
import com.javarush.task.task27.task2712.statistic.StatisticManager;
import com.javarush.task.task27.task2712.statistic.event.VideoSelectedEventDataRow;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class AdvertisementManager {
    private final AdvertisementStorage storage = AdvertisementStorage.getInstance();
    private int timeSeconds;
    private List<Advertisement> videosToShow = new ArrayList<>();

    public AdvertisementManager(int timeSeconds) {
        this.timeSeconds = timeSeconds;
    }

    public void processVideos() throws NoVideoAvailableException {
        videosToShow = findVideosToShow();

        if (videosToShow == null) {
            throw new NoVideoAvailableException();
        } else {
            //сортируем ролики для показа в нужном порядке
            Collections.sort(videosToShow, (Advertisement ad1, Advertisement ad2) -> {
                if (ad1.getAmountPerOneDisplaying() > ad2.getAmountPerOneDisplaying()) return -1;
                else if (ad1.getAmountPerOneDisplaying() < ad2.getAmountPerOneDisplaying()) return 1;

                long oneSecAmount1 = ad1.getAmountPerOneDisplaying() * 1000 / ad1.getDuration();
                long oneSecAmount2 = ad2.getAmountPerOneDisplaying() * 1000 / ad2.getDuration();

                if (oneSecAmount1 < oneSecAmount2) return -1;
                else if(oneSecAmount1 > oneSecAmount2) return 1;

                return 0;
            });

            long totalAmount = 0;
            int totalDuration = 0;
            for (Advertisement ad : videosToShow) {
                totalAmount += ad.getAmountPerOneDisplaying();
                totalDuration += ad.getDuration();
            }

            StatisticManager.getInstance().register(new VideoSelectedEventDataRow(videosToShow, totalAmount, totalDuration));

            for (Advertisement ad : videosToShow) {
                long oneSecAmount = ad.getAmountPerOneDisplaying() * 1000 / ad.getDuration();

                ConsoleHelper.writeMessage(String.format("%s is displaying... %d, %d",
                        ad.getName(),
                        ad.getAmountPerOneDisplaying(),
                        oneSecAmount));

                ad.revalidate();
            }
        }
    }

    private List<Advertisement> findVideosToShow() {
        List<List<Advertisement>> listOfLists = new ArrayList<>();
        List<Advertisement> storageVideos = storage.list();
        int blockTime;

        //перебор всех возможных вариантов
        for (Advertisement ad : storageVideos) {
            blockTime = ad.getDuration();

            if (blockTime <= timeSeconds) {
                List<Advertisement> tempList = new ArrayList<>();
                tempList.add(ad);
                listOfLists.add(tempList);
            }
        }

        boolean flag = true;
        while (flag) {
            flag = false;

            for (int j = 0; j < listOfLists.size(); j++) {
                List<Advertisement> currentList = listOfLists.get(j);

                for (Advertisement ad : storageVideos) {
                    blockTime = ad.getDuration();
                    List<Advertisement> tempList = new ArrayList<>();
                    for (Advertisement a : currentList) {
                        tempList.add(a);
                        blockTime += a.getDuration();
                    }
                    tempList.add(ad);

                    if (!currentList.contains(ad) && !isListContains(listOfLists, tempList)
                            && blockTime <= timeSeconds && ad.getHits() > 0) {
                        listOfLists.add(tempList);
                        flag = true;
                    }
                }
            }
        }
        //окончание перебора

        //в результате сортировки подходящий вариант будет иметь индекс 0
        Collections.sort(listOfLists, (List<Advertisement> list1, List<Advertisement> list2) -> {
            long list1Amount = 0, list2Amount = 0;
            int list1Duration = 0, list2Duration = 0;
            int list1Count = list1.size();
            int list2Count = list2.size();

            for (Advertisement ad : list1) {
                list1Amount += ad.getAmountPerOneDisplaying();
                list1Duration += ad.getDuration();
            }

            for (Advertisement ad : list2) {
                list2Amount += ad.getAmountPerOneDisplaying();
                list2Duration += ad.getDuration();
            }

            if (list1Amount > list2Amount) return -1;
            else if (list1Amount < list2Amount) return 1;

            if (list1Duration > list2Duration) return -1;
            else if (list1Duration < list2Duration) return 1;

            if (list1Count < list2Count) return -1;
            else if (list1Count > list2Count) return 1;

            return 0;
        });

        if (!listOfLists.isEmpty()) {
            return listOfLists.get(0);
        } else {
            return null;
        }
    }

    private static boolean isListContains(List<List<Advertisement>> listOfLists, List<Advertisement> list) {
        for (List<Advertisement> l : listOfLists) {
            int x = list.size();
            for (Advertisement ad : list) {
                if (l.contains(ad)) {
                    x--;
                }

                if (x == 0) return true;
            }
        }
        return false;
    }
}
package com.javarush.task.task27.task2712.kitchen;

import com.javarush.task.task27.task2712.ConsoleHelper;

import java.io.IOException;
import java.util.Observable;
import java.util.Observer;

public class Waiter implements Observer {

    @Override
    public void update(Observable o, Object arg) {
        ConsoleHelper.writeMessage(arg + " was cooked by " + o);
    }
}
package com.javarush.task.task27.task2712.statistic.event;

import com.javarush.task.task27.task2712.kitchen.Dish;

import java.util.Date;
import java.util.List;

public class CookedOrderEventDataRow implements EventDataRow {
    private String tabletName;
    private String cookName;
    private int cookingTimeSeconds;
    private List<Dish> cookingDishs;
    private Date currentDate;

    public String getTabletName() {
        return tabletName;
    }

    public String getCookName() {
        return cookName;
    }

    public int getCookingTimeSeconds() {
        return cookingTimeSeconds;
    }

    public List<Dish> getCookingDishs() {
        return cookingDishs;
    }

    public Date getCurrentDate() {
        return currentDate;
    }

    public CookedOrderEventDataRow(String tabletName, String cookName, int cookingTimeSeconds, List<Dish> cookingDishs){
        this.tabletName = tabletName;
        this.cookName = cookName;
        this.cookingTimeSeconds = cookingTimeSeconds;
        this.cookingDishs = cookingDishs;
        currentDate = new Date();
    }

    @Override
    public EventType getType() {
        return EventType.COOKED_ORDER;
    }
}
package com.javarush.task.task27.task2712.statistic.event;

import com.javarush.task.task27.task2712.statistic.event.EventDataRow;

import java.util.Date;

public class NoAvailableVideoEventDataRow implements EventDataRow {
    private int totalDuration;
    private Date currentDate;

    public int getTotalDuration() {
        return totalDuration;
    }

    public Date getCurrentDate() {
        return currentDate;
    }

    public NoAvailableVideoEventDataRow(int totalDuration){
        this.totalDuration = totalDuration;
        currentDate = new Date();
    }

    @Override
    public EventType getType() {
        return EventType.NO_AVAILABLE_VIDEO;
    }
}
package com.javarush.task.task27.task2712.statistic.event;

import com.javarush.task.task27.task2712.ad.Advertisement;
import com.javarush.task.task27.task2712.statistic.event.EventDataRow;

import java.util.Date;
import java.util.List;

public class VideoSelectedEventDataRow implements EventDataRow {
    private List<Advertisement> optimalVideoSet;
    private long amount;
    private int totalDuration;
    private Date currentDate;

    public List<Advertisement> getOptimalVideoSet() {
        return optimalVideoSet;
    }

    public long getAmount() {
        return amount;
    }

    public int getTotalDuration() {
        return totalDuration;
    }

    public Date getCurrentDate() {
        return currentDate;
    }

    public VideoSelectedEventDataRow(List<Advertisement> optimalVideoSet, long amount, int totalDuration){
        this.optimalVideoSet = optimalVideoSet;
        this.amount = amount;
        this.totalDuration = totalDuration;
        currentDate = new Date();
    }

    @Override
    public EventType getType() {
        return EventType.SELECTED_VIDEOS;
    }
}
ge com.javarush.task.task27.task2712.statistic;

import com.javarush.task.task27.task2712.ConsoleHelper;
import com.javarush.task.task27.task2712.statistic.event.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class StatisticManager {
    private static StatisticManager ourInstance = new StatisticManager();
    private StatisticStorage statisticStorage = new StatisticStorage();

    public static StatisticManager getInstance() {
        return ourInstance;
    }

    private StatisticManager() {
    }

    public void register(EventDataRow data){
        if (data == null) return;
        statisticStorage.put(data);
    }

    private class StatisticStorage {
        private Map<EventType, List<EventDataRow>> storage = new HashMap<EventType, List<EventDataRow>>();

        private StatisticStorage(){
            for (EventType et : EventType.values())
                storage.put(et, new ArrayList<EventDataRow>());
        }

        private void put(EventDataRow data){
            storage.get(data.getType()).add(data);
        }
    }
}
package com.javarush.task.task27.task2712;

import com.javarush.task.task27.task2712.ad.AdvertisementManager;
import com.javarush.task.task27.task2712.ad.NoVideoAvailableException;
import com.javarush.task.task27.task2712.kitchen.Order;

import java.io.IOException;
import java.util.Observable;
import java.util.logging.Level;
import java.util.logging.Logger;

import static com.javarush.task.task27.task2712.ConsoleHelper.writeMessage;

public class Tablet extends Observable {

    final int number;
    static Logger logger = Logger.getLogger(Tablet.class.getName());

    public Tablet(int number) {
        this.number = number;
    }

    @Override
    public String toString() {
        return "Tablet{number=" + number + "}";
    }

    public int getNumber() {
        return number;
    }

    public Order createOrder(){
        Order order = null;
        AdvertisementManager advertisementManager = null;
        try {
            order = new Order(this);
            if (!order.isEmpty()) {
                writeMessage(order.toString());
                advertisementManager = new AdvertisementManager(order.getTotalCookingTime()*60);
                advertisementManager.processVideos();
                setChanged();
                notifyObservers(order);
            }
        } catch (NoVideoAvailableException e) {
            logger.log(Level.INFO,"No video is available for the order " + order);
        } catch (IOException e) {
            logger.log(Level.SEVERE,"Console is unavailable.");
        }
        return order;
    }
}