package com.opendoorlogistics.components.jsprit;
import gnu.trove.map.hash.TLongObjectHashMap;
import gnu.trove.set.hash.TLongHashSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.opendoorlogistics.api.ODLApi;
import com.opendoorlogistics.api.StringConventions;
import com.opendoorlogistics.api.Tables.KeyValidationMode;
import com.opendoorlogistics.api.tables.ODLDatastore;
import com.opendoorlogistics.api.tables.ODLTable;
import com.opendoorlogistics.api.tables.ODLTableReadOnly;
import com.opendoorlogistics.components.jsprit.tabledefinitions.InputTablesDfn;
import com.opendoorlogistics.components.jsprit.tabledefinitions.StopOrderTableDfn;
import com.opendoorlogistics.components.jsprit.tabledefinitions.StopsTableDefn.StopType;
public class CleanStopOrderTable {
private final ODLApi api;
private final VRPConfig conf;
private final ODLDatastore<? extends ODLTable> ioDb;
private final InputTablesDfn dfn;
private final StopOrderTableDfn orderDfn;
private final ODLTable stopOrderTable;
public CleanStopOrderTable(ODLApi api, VRPConfig conf,
ODLDatastore<? extends ODLTable> ioDb, InputTablesDfn dfn) {
super();
this.api = api;
this.conf = conf;
this.ioDb = ioDb;
this.dfn = dfn;
orderDfn = dfn.stopOrder;
stopOrderTable = ioDb.getTableByImmutableId(orderDfn.tableId);
}
void validate() {
// remove any stop order records with an unknown stop id
ODLTable stopOrder = ioDb.getTableAt(dfn.stopOrder.tableIndex);
api.tables().validateForeignKey(ioDb.getTableAt(dfn.stops.tableIndex), dfn.stops.id, stopOrder, dfn.stopOrder.stopid, KeyValidationMode.REMOVE_CORRUPT_FOREIGN_KEY);
removeStopOrderWithUnknownVehicle(stopOrder);
ensurePDLogic();
}
private class StopRec{
String id;
long lastModified;
long orderRowId=-1;
String vehicleId;
int row;
boolean isLoaded(){
return orderRowId!=-1;
}
}
private class PD{
StopRec p = new StopRec();
StopRec d = new StopRec();
}
private void ensurePDLogic(){
ODLTableReadOnly stops = ioDb.getTableByImmutableId(dfn.stops.tableId);
Map<String, List<Integer>> grouped = dfn.stops.getGroupedByMultiStopJob(stops, false);
// For invalid p-d we check which one was moved last and this becomes the winner
// The other end is then moved to match this.
// We ignore any corrupt PDs (e.g. 2 deliveries), as this should be reported later
// get all pd ids
ArrayList<PD> pds = new ArrayList<CleanStopOrderTable.PD>(grouped.size());
Map<String, StopRec> byStopId = api.stringConventions().createStandardisedMap();
for(List<Integer> list : grouped.values()){
if(list.size()==2){
if(dfn.stops.getStopType(stops, list.get(0))== StopType.LINKED_PICKUP && dfn.stops.getStopType(stops, list.get(1))==StopType.LINKED_DELIVERY){
PD pd = new PD();
pd.p.id = dfn.stops.getId(stops, list.get(0));
pd.d.id = dfn.stops.getId(stops, list.get(1));
if(!api.stringConventions().isEmptyString(pd.p.id) && !api.stringConventions().isEmptyString(pd.d.id)){
pds.add(pd);
byStopId.put(pd.p.id, pd.p);
byStopId.put(pd.d.id, pd.d);
}
}
}
}
if(pds.size()==0){
return;
}
// read all necessary information
int n = stopOrderTable.getRowCount();
int nbFound=0;
for(int row =0 ; row< n ; row++){
String stopId = orderDfn.getStopId(stopOrderTable, row);
StopRec rec = byStopId.get(stopId);
if(rec!=null){
rec.orderRowId = stopOrderTable.getRowId(row);
rec.lastModified = stopOrderTable.getRowLastModifiedTimeMillsecs(rec.orderRowId);
rec.row = row;
rec.vehicleId = orderDfn.getVehicleId(stopOrderTable, row);
nbFound++;
}
}
if(nbFound==0){
return;
}
// decide on action
TLongHashSet toDelete = new TLongHashSet();
TLongObjectHashMap<StopRec> toInsertBefore= new TLongObjectHashMap<>();
TLongObjectHashMap<StopRec> toInsertAfter= new TLongObjectHashMap<>();
for(PD pd : pds){
if(pd.p.isLoaded() != pd.d.isLoaded()){
// one loaded, one not .. pickup wins as we can't do any different
if(pd.p.isLoaded()){
pd.d.vehicleId = pd.p.vehicleId;
toInsertAfter.put(pd.p.orderRowId, pd.d);
}else{
// delete the delivery row
toDelete.add(pd.d.orderRowId);
}
}
else if(pd.p.isLoaded()){
// both loaded - check for out-of-order
if(pd.p.row >= pd.d.row || api.stringConventions().equalStandardised(pd.p.vehicleId, pd.d.vehicleId)==false){
// easy to tell the winner, its the last one moved
boolean pIsWinner = pd.p.lastModified >= pd.d.lastModified;
if(pIsWinner){
// delete and re-add the delivery
toDelete.add(pd.d.orderRowId);
pd.d.vehicleId = pd.p.vehicleId;
toInsertAfter.put(pd.p.orderRowId, pd.d);
}else{
// delete and re-add the pickup
toDelete.add(pd.p.orderRowId);
pd.p.vehicleId = pd.d.vehicleId;
toInsertBefore.put(pd.d.orderRowId, pd.p);
}
}
}
}
if(toDelete.size()==0 && toInsertBefore.size()==0 && toInsertAfter.size()==0){
return;
}
// now do the actions
int row = 0;
while(row < stopOrderTable.getRowCount()){
long rowid = stopOrderTable.getRowId(row);
if(toDelete.contains(rowid)){
stopOrderTable.deleteRow(row);
toDelete.remove(rowid);
}
else if(toInsertBefore.contains(rowid)){
insert(toInsertBefore.remove(rowid), row);
}
else if(toInsertAfter.contains(rowid)){
insert(toInsertAfter.remove(rowid), row+1);
}
else{
row++;
}
}
}
private void insert(StopRec stop, int row){
stopOrderTable.insertEmptyRow(row, -1);
stopOrderTable.setValueAt(stop.id, row, orderDfn.stopid);
stopOrderTable.setValueAt(stop.vehicleId, row, orderDfn.vehicleid);
}
private void removeStopOrderWithUnknownVehicle(ODLTable stopOrder) {
StringConventions strings = api.stringConventions();
Set<String> processedVehicleIds = strings.createStandardisedSet();
Set<String> processedStopIds = strings.createStandardisedSet();
// remove any stop order records with unknown vehicle id
VehicleIds vehicleIds = new VehicleIds(api, conf, dfn, ioDb.getTableAt(dfn.vehicles.tableIndex));
int row =0 ;
String currentVehicleId=null;
while(row < stopOrder.getRowCount()){
boolean delete=false;
// check for known vehicle id
String vehicleId = dfn.stopOrder.getVehicleId(stopOrder, row);
if(vehicleId==null || vehicleIds.isKnown(vehicleId)==false){
delete = true;
}
// check for empty stop id
String stopId = (String)stopOrder.getValueAt(row, dfn.stopOrder.stopid);
if(strings.isEmptyString(stopId)){
delete = true;
}
// check for repeated stop id
if(stopId!=null && processedStopIds.contains(stopId)){
delete = true;
}
// check for non-consecutive vehicle ids
if(vehicleId!=null && strings.equalStandardised(vehicleId, currentVehicleId)==false && processedVehicleIds.contains(vehicleId)){
delete = true;
}
if(delete){
stopOrder.deleteRow(row);
}
else{
currentVehicleId = vehicleId;
processedVehicleIds.add(currentVehicleId);
processedStopIds.add(stopId);
row++;
}
}
}
}