/**
 * Copyright (c) The openTCS Authors.
 *
 * This program is free software and subject to the MIT license. (For details,
 * see the licensing information (LICENSE.txt) you should have received with
 * this copy of the software.)
 */
package org.opentcs.kernel.services;

import java.util.List;
import static java.util.Objects.requireNonNull;
import javax.inject.Inject;
import org.opentcs.access.to.order.OrderSequenceCreationTO;
import org.opentcs.access.to.order.TransportOrderCreationTO;
import org.opentcs.components.kernel.services.InternalTransportOrderService;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.components.kernel.services.TransportOrderService;
import org.opentcs.customizations.kernel.GlobalSyncObject;
import org.opentcs.data.ObjectExistsException;
import org.opentcs.data.ObjectUnknownException;
import org.opentcs.data.TCSObjectReference;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.DriveOrder;
import org.opentcs.data.order.OrderSequence;
import org.opentcs.data.order.TransportOrder;
import org.opentcs.kernel.workingset.Model;
import org.opentcs.kernel.workingset.TCSObjectPool;
import org.opentcs.kernel.workingset.TransportOrderPool;

/**
 * This class is the standard implementation of the {@link TransportOrderService} interface.
 *
 * @author Martin Grzenia (Fraunhofer IML)
 */
public class StandardTransportOrderService
    extends AbstractTCSObjectService
    implements InternalTransportOrderService {

  /**
   * A global object to be used for synchronization within the kernel.
   */
  private final Object globalSyncObject;
  /**
   * The container of all course model and transport order objects.
   */
  private final TCSObjectPool globalObjectPool;
  /**
   * The order facade to the object pool.
   */
  private final TransportOrderPool orderPool;
  /**
   * The model facade to the object pool.
   */
  private final Model model;

  /**
   * Creates a new instance.
   *
   * @param objectService The tcs obejct service.
   * @param globalSyncObject The kernel threads' global synchronization object.
   * @param globalObjectPool The object pool to be used.
   * @param orderPool The oder pool to be used.
   * @param model The model to be used.
   */
  @Inject
  public StandardTransportOrderService(TCSObjectService objectService,
                                       @GlobalSyncObject Object globalSyncObject,
                                       TCSObjectPool globalObjectPool,
                                       TransportOrderPool orderPool,
                                       Model model) {
    super(objectService);
    this.globalSyncObject = requireNonNull(globalSyncObject, "globalSyncObject");
    this.globalObjectPool = requireNonNull(globalObjectPool, "globalObjectPool");
    this.orderPool = requireNonNull(orderPool, "orderPool");
    this.model = requireNonNull(model, "model");
  }

  @Override
  @Deprecated
  public void registerTransportOrderRejection(TCSObjectReference<TransportOrder> ref,
                                              org.opentcs.data.order.Rejection rejection)
      throws ObjectUnknownException {
    synchronized (globalSyncObject) {
      orderPool.addTransportOrderRejection(ref, rejection);
    }
  }

  @Override
  public void markOrderSequenceFinished(TCSObjectReference<OrderSequence> ref)
      throws ObjectUnknownException {
    synchronized (globalSyncObject) {
      OrderSequence seq = globalObjectPool.getObject(OrderSequence.class, ref);
      // Make sure we don't execute this if the sequence is already marked as finished, as that 
      // would make it possible to trigger disposition of a vehicle at any given moment.
      if (seq.isFinished()) {
        return;
      }

      orderPool.setOrderSequenceFinished(ref);
      // If the sequence was being processed by a vehicle, clear its back reference to the sequence 
      // to make it available again and dispatch it.
      if (seq.getProcessingVehicle() != null) {
        Vehicle vehicle = globalObjectPool.getObject(Vehicle.class,
                                                     seq.getProcessingVehicle());
        model.setVehicleOrderSequence(vehicle.getReference(), null);
      }
    }
  }

  @Override
  public void updateOrderSequenceFinishedIndex(TCSObjectReference<OrderSequence> ref, int index)
      throws ObjectUnknownException {
    synchronized (globalSyncObject) {
      orderPool.setOrderSequenceFinishedIndex(ref, index);
    }
  }

  @Override
  public void updateOrderSequenceProcessingVehicle(TCSObjectReference<OrderSequence> seqRef,
                                                   TCSObjectReference<Vehicle> vehicleRef)
      throws ObjectUnknownException {
    synchronized (globalSyncObject) {
      orderPool.setOrderSequenceProcessingVehicle(seqRef, vehicleRef);
    }
  }

  @Override
  public void updateTransportOrderProcessingVehicle(TCSObjectReference<TransportOrder> orderRef,
                                                    TCSObjectReference<Vehicle> vehicleRef,
                                                    List<DriveOrder> driveOrders)
      throws ObjectUnknownException, IllegalArgumentException {
    synchronized (globalSyncObject) {
      orderPool.setTransportOrderProcessingVehicle(orderRef, vehicleRef, driveOrders);
    }
  }

  @Override
  public void updateTransportOrderDriveOrders(TCSObjectReference<TransportOrder> ref,
                                              List<DriveOrder> driveOrders)
      throws ObjectUnknownException {
    synchronized (globalSyncObject) {
      orderPool.setTransportOrderDriveOrders(ref, driveOrders);
    }
  }

  @Override
  public void updateTransportOrderNextDriveOrder(TCSObjectReference<TransportOrder> ref)
      throws ObjectUnknownException {
    synchronized (globalSyncObject) {
      orderPool.setTransportOrderNextDriveOrder(ref);
    }
  }

  @Override
  public void updateTransportOrderState(TCSObjectReference<TransportOrder> ref,
                                        TransportOrder.State state)
      throws ObjectUnknownException {
    synchronized (globalSyncObject) {
      orderPool.setTransportOrderState(ref, state);
    }
  }

  @Override
  @SuppressWarnings("deprecation")
  public OrderSequence createOrderSequence(OrderSequenceCreationTO to) {
    synchronized (globalSyncObject) {
      return orderPool.createOrderSequence(to).clone();
    }
  }

  @Override
  @SuppressWarnings("deprecation")
  public TransportOrder createTransportOrder(TransportOrderCreationTO to)
      throws ObjectUnknownException, ObjectExistsException {
    synchronized (globalSyncObject) {
      return orderPool.createTransportOrder(to).clone();
    }
  }

  @Override
  public void markOrderSequenceComplete(TCSObjectReference<OrderSequence> ref)
      throws ObjectUnknownException {
    synchronized (globalSyncObject) {
      OrderSequence seq = globalObjectPool.getObject(OrderSequence.class, ref);
      // Make sure we don't execute this if the sequence is already marked as finished, as that 
      // would make it possible to trigger disposition of a vehicle at any given moment.
      if (seq.isComplete()) {
        return;
      }
      orderPool.setOrderSequenceComplete(ref);
      // If there aren't any transport orders left to be processed as part of the sequence, mark 
      // it as finished, too.
      if (seq.getNextUnfinishedOrder() == null) {
        orderPool.setOrderSequenceFinished(ref);
        // If the sequence was being processed by a vehicle, clear its back reference to the 
        // sequence to make it available again and dispatch it.
        if (seq.getProcessingVehicle() != null) {
          Vehicle vehicle = globalObjectPool.getObject(Vehicle.class,
                                                       seq.getProcessingVehicle());
          model.setVehicleOrderSequence(vehicle.getReference(), null);
        }
      }
    }
  }

}