package at.ac.tuwien.sbc.valesriegler.xvsm;


import at.ac.tuwien.sbc.valesriegler.common.Bill;
import at.ac.tuwien.sbc.valesriegler.common.Util;
import at.ac.tuwien.sbc.valesriegler.types.*;
import at.ac.tuwien.sbc.valesriegler.xvsm.spacehelpers.SpaceAction;
import org.mozartspaces.capi3.EntryLockedException;
import org.mozartspaces.core.ContainerReference;
import org.mozartspaces.core.MzsConstants;
import org.mozartspaces.core.MzsCoreException;
import org.mozartspaces.core.TransactionReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.net.URI;
import java.util.Arrays;
import java.util.List;

public class DriverXVSM extends AbstractXVSMConnector {
    private static final Logger log = LoggerFactory.getLogger(DriverXVSM.class);

    private int driverId;

    public DriverXVSM(int id, int port) {
        super(port);

        this.driverId = id;

        preparedDeliveryPizzasContainer = useContainer(Util.DELIVER_DELIVERY_PIZZAS);
        deliveryOrderTakenContainer = useContainer(Util.DELIVERY_ORDER_TAKEN);
        deliverDeliveryOrderContainer = useContainer(Util.DELIVER_DELIVERY_ORDER);
        pizzeriaDeliveryContainer = useContainer(Util.PIZZERIA_DELIVERY);
    }


    public void listenForPreparedDeliveryOrders() {
        getDefaultBuilder("listenForPreparedDeliveryOrders").setCref(deliverDeliveryOrderContainer).setLookaround(true).setSpaceAction(new SpaceAction() {

            @Override
            public void onEntriesWritten(List<? extends Serializable> entries) throws Exception {
                handleDeliveries(entries);

            }
        }).createSpaceListenerImpl();
    }

    private void handleDeliveries(List<? extends Serializable> entries) throws MzsCoreException {
        final List<DeliveryGroupData> deliveries = castEntries(entries);

        for (DeliveryGroupData delivery : deliveries) {
            DeliveryGroupData template = new DeliveryGroupData(delivery.getId());
            template.setDeliveryStatus(DeliveryStatus.ORDERED);

            final TransactionReference tx = capi.createTransaction(
                    7000,
                    URI.create(String.format(Util.SERVER_ADDR, port)));
            final String error = "Another driver wants to deliver this order!";

            try {
                // Get lock for delivering this delivery order
                takeMatchingEntity(template, deliverDeliveryOrderContainer, tx, MzsConstants.RequestTimeout.ZERO, error);
                final DeliveryGroupData group = takeMatchingEntity(template, pizzeriaDeliveryContainer, tx, MzsConstants.RequestTimeout.DEFAULT, "Cannot get the delivery order!");
                group.setDriverId(driverId);
                group.setDeliveryStatus(DeliveryStatus.IN_PROGRESS);

                final List<DeliveryGroupData> groups = Arrays.asList(group);
                        sendItemsToContainer(groups,
                                pizzeriaDeliveryContainer, MzsConstants.RequestTimeout.ZERO, null);

                if (!Util.runSimulation) {
                    Thread.sleep(3000);
                }

                final String address = String.format(Util.SERVER_ADDR, Util.DELIVERY_CUSTOMERS_PORT);
                ContainerReference cref = null;
                boolean success = false;
                try {
                    cref = capi.lookupContainer(group.getAddress(), URI.create(address), MzsConstants.RequestTimeout.ZERO, null);
                    log.debug("Looked up container {} successfully!", address);
                    success = true;

                } catch (Exception ex) {
                    log.info("Could not find container {}", address);
                }
                if (success) {
                    group.setDeliveryStatus(DeliveryStatus.DELIVERED);
                    if (!Util.runSimulation) {
                        double sum = computeSum(group.getOrder());
                        sendItemsToContainer(Arrays.asList(new Bill(sum, group.getId())), cref, MzsConstants.RequestTimeout.ZERO, null);
                    }
                } else {
                    group.setDeliveryStatus(DeliveryStatus.DELIVERY_FAILED);
                }
                sendItemsToContainer(groups, pizzeriaDeliveryContainer, MzsConstants.RequestTimeout.ZERO, tx);
                capi.commitTransaction(tx);


            } catch(EntryLockedException e) {
                capi.rollbackTransaction(tx);
            } catch(NullPointerException e) {
                log.info("strange npe from space!");
            } catch (Exception e) {
                log.info("DRiverXVSM exception");
                log.info(e.getMessage());
                e.printStackTrace();
            }
        }
    }

    private double computeSum(Order order) {
        final List<PizzaOrder> orderedPizzas = order.getOrderedPizzas();
        double sum = 0;
        for (PizzaOrder pizza : orderedPizzas) {
            final PizzaType pizzaType = pizza.getPizzaType();
            switch (pizzaType) {
                case CARDINALE: sum = sum + 6;
                    break;
                case MARGHERITA: sum = sum + 5;
                    break;
                case SALAMI: sum = sum + 5.5;
                    break;
                default:
                    throw new RuntimeException("Unhandled Pizzatype: " + pizzaType);
            }
        }
        return sum;
    }

    public void listenForPreparedDeliveryPizzas() {
        getDefaultBuilder("listenForPreparedDeliveryPizzas").setCref(preparedDeliveryPizzasContainer).setLookaround(true).setSpaceAction(new SpaceAction() {

            @Override
            public void onEntriesWritten(List<? extends Serializable> entries)
                    throws Exception {

                List<Pizza> pizzas = castEntries(entries);

                for (Pizza pizza : pizzas) {
                    int orderId = pizza.getOrderId();
                    Order order = new Order();
                    order.setId(orderId);

                    TransactionReference tx = getDefaultTransaction();

                    try {
                        DeliveryGroupData entity = new DeliveryGroupData();
                        entity.setDeliveryStatus(null);
                        entity.setOrder(order);

                        final DeliveryGroupData groupData = takeMatchingEntity(entity,
                                deliveryOrderTakenContainer, tx, MzsConstants.RequestTimeout.DEFAULT,
                                "Another driver just checks if the delivery order is complete");
                        int numberOfPizzas = groupData.getOrder().getNumberOfPizzas();

                        Pizza pizzaTemplate = new Pizza();
                        pizzaTemplate.setOrderId(orderId);

                        List<Pizza> pizzasOfOrder = takeMatchingEntities(
                                pizzaTemplate, preparedDeliveryPizzasContainer, tx,
                                MzsConstants.RequestTimeout.DEFAULT,
                                "Cannot take the pizzas from the preparedDeliveryPizzasContainer");

                        if (pizzasOfOrder.size() == numberOfPizzas) {
                            final List<DeliveryGroupData> groupsWithCompleteOrder = Arrays.asList(groupData);
                            sendItemsToContainer(groupsWithCompleteOrder,
                                    deliverDeliveryOrderContainer, MzsConstants.RequestTimeout.DEFAULT,
                                    tx);

                            capi.commitTransaction(tx);
                        } else {
                            log.info("Not yet all pizzas prepared! Order with id "
                                    + orderId + " has " + numberOfPizzas
                                    + " pizzas, but only " + pizzasOfOrder.size()
                                    + " pizzas are ready by now!");
                            capi.rollbackTransaction(tx);
                        }
                    } catch (NullPointerException e) {

                    } catch (Exception e) {
                        capi.rollbackTransaction(tx);
                    }
                }
            }
        }).createSpaceListenerImpl();
    }
}
