package mekanism.common.transporter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import mekanism.api.Coord4D;
import mekanism.common.ILogisticalTransporter;
import mekanism.common.tile.TileEntityLogisticalSorter;
import mekanism.common.transporter.TransporterPathfinder.Pathfinder.DestChecker;
import mekanism.common.transporter.TransporterStack.Path;
import mekanism.common.util.InventoryUtils;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;
public final class TransporterPathfinder
{
public static class IdlePath
{
public World worldObj;
public Coord4D start;
public TransporterStack transportStack;
public IdlePath(World world, Coord4D obj, TransporterStack stack)
{
worldObj = world;
start = obj;
transportStack = stack;
}
public Destination find()
{
ArrayList<Coord4D> ret = new ArrayList<Coord4D>();
ret.add(start);
if(transportStack.idleDir == ForgeDirection.UNKNOWN)
{
ForgeDirection newSide = findSide();
if(newSide == null)
{
return null;
}
transportStack.idleDir = newSide;
loopSide(ret, newSide);
return new Destination(ret, 0, true, null).setPathType(Path.NONE);
}
else {
TileEntity tile = start.getFromSide(transportStack.idleDir).getTileEntity(worldObj);
if(transportStack.canInsertToTransporter(tile, transportStack.idleDir))
{
loopSide(ret, transportStack.idleDir);
return new Destination(ret, 0, true, null).setPathType(Path.NONE);
}
else {
Destination newPath = TransporterPathfinder.getNewBasePath((ILogisticalTransporter)start.getTileEntity(worldObj), transportStack, 0);
if(newPath != null && TransporterManager.didEmit(transportStack.itemStack, newPath.rejected))
{
transportStack.idleDir = ForgeDirection.UNKNOWN;
newPath.setPathType(Path.DEST);
return newPath;
}
else {
ForgeDirection newSide = findSide();
if(newSide == null)
{
return null;
}
transportStack.idleDir = newSide;
loopSide(ret, newSide);
return new Destination(ret, 0, true, null).setPathType(Path.NONE);
}
}
}
}
private void loopSide(List<Coord4D> list, ForgeDirection side)
{
int count = 1;
while(true)
{
Coord4D coord = start.getFromSide(side, count);
if(transportStack.canInsertToTransporter(coord.getTileEntity(worldObj), side))
{
list.add(coord);
count++;
}
else {
break;
}
}
}
private ForgeDirection findSide()
{
if(transportStack.idleDir == ForgeDirection.UNKNOWN)
{
for(ForgeDirection side : ForgeDirection.VALID_DIRECTIONS)
{
TileEntity tile = start.getFromSide(side).getTileEntity(worldObj);
if(transportStack.canInsertToTransporter(tile, side))
{
return side;
}
}
}
else {
for(ForgeDirection side : EnumSet.complementOf(EnumSet.of(ForgeDirection.UNKNOWN, transportStack.idleDir.getOpposite())))
{
TileEntity tile = start.getFromSide(side).getTileEntity(worldObj);
if(transportStack.canInsertToTransporter(tile, side))
{
return side;
}
}
TileEntity tile = start.getFromSide(transportStack.idleDir.getOpposite()).getTileEntity(worldObj);
if(transportStack.canInsertToTransporter(tile, transportStack.idleDir.getOpposite()))
{
return transportStack.idleDir.getOpposite();
}
}
return null;
}
}
public static class Destination implements Comparable<Destination>
{
public List<Coord4D> path = new ArrayList<Coord4D>();
public Path pathType;
public double score;
public ItemStack rejected;
public Destination(ArrayList<Coord4D> list, double d, boolean inv, ItemStack rejects)
{
path = (List<Coord4D>)list.clone();
if(inv)
{
Collections.reverse(path);
}
score = d;
rejected = rejects;
}
public Destination setPathType(Path type)
{
pathType = type;
return this;
}
@Override
public int hashCode()
{
int code = 1;
code = 31 * code + path.hashCode();
code = 31 * code + new Double(score).hashCode();
return code;
}
@Override
public boolean equals(Object dest)
{
return dest instanceof Destination && ((Destination)dest).path.equals(path) && ((Destination)dest).score == score;
}
@Override
public int compareTo(Destination dest)
{
if(score < dest.score)
{
return -1;
}
else if(score > dest.score)
{
return 1;
}
else {
return 0;
}
}
}
public static class InventoryFinder
{
public World worldObj;
public Set<Coord4D> iterated = new HashSet<Coord4D>();
public Set<Coord4D> destsFound = new HashSet<Coord4D>();
public Map<Coord4D, ItemStack> rejects = new HashMap<Coord4D, ItemStack>();
public Coord4D start;
public TransporterStack transportStack;
public InventoryFinder(World world, Coord4D obj, TransporterStack stack)
{
worldObj = world;
start = obj;
transportStack = stack;
}
public void loop(Coord4D pointer)
{
if(pointer == null)
{
return;
}
iterated.add(pointer);
for(ForgeDirection side : ForgeDirection.VALID_DIRECTIONS)
{
TileEntity tile = pointer.getFromSide(side).getTileEntity(worldObj);
if(tile != null)
{
if(Coord4D.get(tile).equals(transportStack.originalLocation))
{
continue;
}
if(tile instanceof IInventory)
{
if(pointer.getTileEntity(worldObj) instanceof ILogisticalTransporter)
{
ILogisticalTransporter trans = (ILogisticalTransporter)pointer.getTileEntity(worldObj);
if(!trans.canEmitTo(tile, side))
{
continue;
}
}
ItemStack stack = TransporterManager.getPredictedInsert(tile, transportStack.color, transportStack.itemStack, side.ordinal());
if(TransporterManager.didEmit(transportStack.itemStack, stack))
{
destsFound.add(Coord4D.get(tile));
rejects.put(Coord4D.get(tile), stack);
}
}
else if(transportStack.canInsertToTransporter(tile, side) && !iterated.contains(Coord4D.get(tile)))
{
loop(Coord4D.get(tile));
}
}
}
}
public Set<Coord4D> find()
{
loop(start);
return destsFound;
}
}
public static List<Destination> getPaths(ILogisticalTransporter start, TransporterStack stack, int min)
{
DestChecker checker = new DestChecker()
{
@Override
public boolean isValid(TransporterStack stack, int side, TileEntity tile)
{
return InventoryUtils.canInsert(tile, stack.color, stack.itemStack, side, false);
}
};
InventoryFinder d = new InventoryFinder(start.getTile().getWorldObj(), Coord4D.get(start.getTile()), stack);
Set<Coord4D> destsFound = d.find();
List<Destination> paths = new ArrayList<Destination>();
for(Coord4D obj : destsFound)
{
Pathfinder p = new Pathfinder(checker, start.getTile().getWorldObj(), obj, Coord4D.get(start.getTile()), stack);
if(p.getPath().size() >= 2)
{
if(TransporterManager.getToUse(stack.itemStack, d.rejects.get(obj)).stackSize >= min)
{
paths.add(new Destination(p.getPath(), p.finalScore, false, d.rejects.get(obj)));
}
}
}
Collections.sort(paths);
return paths;
}
public static Destination getNewBasePath(ILogisticalTransporter start, TransporterStack stack, int min)
{
List<Destination> paths = getPaths(start, stack, min);
if(paths.isEmpty())
{
return null;
}
return paths.get(0);
}
public static Destination getNewRRPath(ILogisticalTransporter start, TransporterStack stack, TileEntityLogisticalSorter outputter, int min)
{
List<Destination> paths = getPaths(start, stack, min);
Map<Coord4D, Destination> destPaths = new HashMap<Coord4D, Destination>();
for(Destination d : paths)
{
if(destPaths.get(d.path.get(0)) == null || destPaths.get(d.path.get(0)).score < d.score)
{
destPaths.put(d.path.get(0), d);
}
}
List<Destination> dests = new ArrayList<Destination>();
dests.addAll(destPaths.values());
Collections.sort(dests);
Destination closest = null;
if(!dests.isEmpty())
{
if(outputter.rrIndex <= dests.size()-1)
{
closest = dests.get(outputter.rrIndex);
if(outputter.rrIndex == dests.size()-1)
{
outputter.rrIndex = 0;
}
else if(outputter.rrIndex < dests.size()-1)
{
outputter.rrIndex++;
}
}
else {
closest = dests.get(dests.size()-1);
outputter.rrIndex = 0;
}
}
if(closest == null)
{
return null;
}
return closest;
}
public static class Pathfinder
{
public final Set<Coord4D> openSet, closedSet;
public final HashMap<Coord4D, Coord4D> navMap;
public final HashMap<Coord4D, Double> gScore, fScore;
public final Coord4D start;
public final Coord4D finalNode;
public final TransporterStack transportStack;
public final DestChecker destChecker;
public double finalScore;
public ArrayList<Coord4D> results;
private World worldObj;
public Pathfinder(DestChecker checker, World world, Coord4D finishObj, Coord4D startObj, TransporterStack stack)
{
destChecker = checker;
worldObj = world;
finalNode = finishObj;
start = startObj;
transportStack = stack;
openSet = new HashSet<Coord4D>();
closedSet = new HashSet<Coord4D>();
navMap = new HashMap<Coord4D, Coord4D>();
gScore = new HashMap<Coord4D, Double>();
fScore = new HashMap<Coord4D, Double>();
results = new ArrayList<Coord4D>();
find(start);
}
public boolean find(Coord4D start)
{
openSet.add(start);
gScore.put(start, 0D);
fScore.put(start, gScore.get(start) + getEstimate(start, finalNode));
int blockCount = 0;
for(int i = 0; i < 6; i++)
{
ForgeDirection direction = ForgeDirection.getOrientation(i);
Coord4D neighbor = start.translate(direction.offsetX, direction.offsetY, direction.offsetZ);
if(!transportStack.canInsertToTransporter(neighbor.getTileEntity(worldObj), direction) && (!neighbor.equals(finalNode) || !destChecker.isValid(transportStack, i, neighbor.getTileEntity(worldObj))))
{
blockCount++;
}
}
if(blockCount >= 6)
{
return false;
}
double maxSearchDistance = start.distanceTo(finalNode) * 2;
while(!openSet.isEmpty())
{
Coord4D currentNode = null;
double lowestFScore = 0;
for(Coord4D node : openSet)
{
if(currentNode == null || fScore.get(node) < lowestFScore)
{
currentNode = node;
lowestFScore = fScore.get(node);
}
}
if(currentNode == null && start.distanceTo(currentNode) > maxSearchDistance)
{
break;
}
openSet.remove(currentNode);
closedSet.add(currentNode);
for(int i = 0; i < 6; i++)
{
ForgeDirection direction = ForgeDirection.getOrientation(i);
Coord4D neighbor = currentNode.getFromSide(direction);
if(transportStack.canInsertToTransporter(neighbor.getTileEntity(worldObj), direction))
{
TileEntity tile = neighbor.getTileEntity(worldObj);
double tentativeG = gScore.get(currentNode) + currentNode.distanceTo(neighbor);
tentativeG += ((ILogisticalTransporter)tile).getCost();
if(closedSet.contains(neighbor))
{
if(tentativeG >= gScore.get(neighbor))
{
continue;
}
}
TileEntity currTile = currentNode.getTileEntity(worldObj);
if(!openSet.contains(neighbor) || tentativeG < gScore.get(neighbor))
{
navMap.put(neighbor, currentNode);
gScore.put(neighbor, tentativeG);
fScore.put(neighbor, gScore.get(neighbor) + getEstimate(neighbor, finalNode));
openSet.add(neighbor);
}
}
else if(neighbor.equals(finalNode) && destChecker.isValid(transportStack, i, neighbor.getTileEntity(worldObj)))
{
results = reconstructPath(navMap, currentNode);
return true;
}
}
}
return false;
}
private ArrayList<Coord4D> reconstructPath(HashMap<Coord4D, Coord4D> naviMap, Coord4D currentNode)
{
ArrayList<Coord4D> path = new ArrayList<Coord4D>();
path.add(currentNode);
if(naviMap.containsKey(currentNode))
{
path.addAll(reconstructPath(naviMap, naviMap.get(currentNode)));
}
finalScore = gScore.get(currentNode) + currentNode.distanceTo(finalNode);
return path;
}
public ArrayList<Coord4D> getPath()
{
ArrayList<Coord4D> path = new ArrayList<Coord4D>();
path.add(finalNode);
path.addAll((ArrayList<Coord4D>)results.clone());
return path;
}
private double getEstimate(Coord4D start, Coord4D target2)
{
return start.distanceTo(target2);
}
public static class DestChecker
{
public boolean isValid(TransporterStack stack, int side, TileEntity tile)
{
return false;
}
}
}
public static List<Coord4D> getIdlePath(ILogisticalTransporter start, TransporterStack stack)
{
if(stack.homeLocation != null)
{
DestChecker checker = new DestChecker()
{
@Override
public boolean isValid(TransporterStack stack, int side, TileEntity tile)
{
return InventoryUtils.canInsert(tile, stack.color, stack.itemStack, side, true);
}
};
Pathfinder p = new Pathfinder(checker, start.getTile().getWorldObj(), stack.homeLocation, Coord4D.get(start.getTile()), stack);
List<Coord4D> path = p.getPath();
if(path.size() >= 2)
{
stack.pathType = Path.HOME;
return path;
}
else {
stack.homeLocation = null;
}
}
IdlePath d = new IdlePath(start.getTile().getWorldObj(), Coord4D.get(start.getTile()), stack);
Destination dest = d.find();
if(dest == null)
{
return null;
}
stack.pathType = dest.pathType;
return dest.path;
}
}