class ExtractorModule extends Module implements IExtractorModule, IExtractorModuleController {

    @Override
    protected void onNewTurn() {
        // Mine some resources.
        Tile location = getGameObject().getLocation().get();
        Maybe<Integer> availableResources = location.getResourceQuantity( resourceType );
        if (!availableResources.isPresent())
            // No resources left to mine.
            return;

        int newAvailableResources = Math.max( 0, availableResources.get() - speed );
        int minedResources = availableResources.get() - newAvailableResources;
        if (!availableResources.isPresent())
            // No speed left for mining.
            return;

        // Initialize path finding functions.
        PredicateNN<IGameObject> foundFunction = gameObject -> {
            for (final ContainerModule containerModule : gameObject.getModules( ModuleType.CONTAINER ))
                if (containerModule.getAvailable() > 0)
                    return true;

            return false;
        };
        NNFunctionNN<PathUtils.Step<IGameObject>, Double> costFunction = gameObjectStep -> 1d;
        NNFunctionNN<IGameObject, Stream<? extends IGameObject>> neighboursFunction = neighbour -> //
                neighbour.getLocation().get().neighbours().stream() //
                        .map( new Function<ITile, IGameObject>() {
                            @Override
                            public IGameObject apply(final ITile tile) {
                                return tile.getContents().orNull();
                            }
                        } ).filter( gameObject -> gameObject != null );

        // Find paths to containers and deposit mined resources.
        while (minedResources > 0) {
            Optional<PathUtils.Path<IGameObject>> path = PathUtils.find( getGameObject(), foundFunction, costFunction,
                                                                         Constants.MAX_DISTANCE_TO_CONTAINER, neighboursFunction );
            if (!path.isPresent())
                // No more containers with available capacity.
                break;

            for (final ContainerModule containerModule : path.get().getTarget().getModules( ModuleType.CONTAINER ))
                minedResources -= containerModule.addStock( minedResources );
        }

        // If we have minedResources left that we weren't able to stock, put them back in the tile (ie. don't extract them).
        newAvailableResources += minedResources;

        // Update the amount of resources left in the tile after this turn's extraction.
        location.setResourceQuantity( resourceType, newAvailableResources );
        logger.trc( "unstocked resources: %d %s, left in tile: %d", minedResources, resourceType, newAvailableResources );
    }

}

class ExtractorModuleTest extends Module {

    @Test
    public void testOnNewTurn()
            throws Exception {


        // Create an extractor unit on a tile with fuel.
        ExtractorModule extractorModule = ExtractorModule.createWithStandardResourceCost().resourceType( ResourceType.FUEL ).speed( 5 );
        ContainerModule unconnectedContainerModule = ContainerModule.createWithStandardResourceCost()
                                                                    .resourceType( ResourceType.FUEL )
                                                                    .capacity( 3 );
        createUnit( testUnitType( "Extractor", extractorModule ), 0, 0 );
        createUnit( testUnitType( "Unconnected Container", unconnectedContainerModule ), 3, 0 );
        extractorModule.getGameObject().getLocation().get().setResourceQuantity( ResourceType.FUEL, 10 );

        // There is no connected container yet, mining should fail.
        assertEquals( (int) extractorModule.getGameObject().getLocation().get().getResourceQuantity( ResourceType.FUEL ).get(), 10 );
        extractorModule.onNewTurn();
        assertEquals( (int) extractorModule.getGameObject().getLocation().get().getResourceQuantity( ResourceType.FUEL ).get(), 10 );

        // Create a connected container unit.
        ContainerModule smallConnectedContainerModule = ContainerModule.createWithStandardResourceCost()
                                                                       .resourceType( ResourceType.FUEL )
                                                                       .capacity( 3 );
        createUnit( testUnitType( "Small Connected Container", smallConnectedContainerModule ), 1, 0 );

        // Now we should be able to mine enough to fill the container.
        assertEquals( smallConnectedContainerModule.getAvailable(), 3 );
        extractorModule.onNewTurn();
        assertEquals( (int) extractorModule.getGameObject().getLocation().get().getResourceQuantity( ResourceType.FUEL ).get(), 7 );
        assertEquals( smallConnectedContainerModule.getAvailable(), 0 );
        extractorModule.onNewTurn();
        assertEquals( (int) extractorModule.getGameObject().getLocation().get().getResourceQuantity( ResourceType.FUEL ).get(), 7 );
        assertEquals( smallConnectedContainerModule.getAvailable(), 0 );

        // Create a bigger connected container unit.
        ContainerModule bigConnectedContainerModule = ContainerModule.createWithStandardResourceCost()
                                                                     .resourceType( ResourceType.FUEL )
                                                                     .capacity( 15 );
        createUnit( testUnitType( "Big Connected Container", bigConnectedContainerModule ), 0, 1 );

        // Now we should be able to mine enough to max out the extractor's speed and then empty the tile.
        assertEquals( smallConnectedContainerModule.getAvailable(), 0 );
        assertEquals( bigConnectedContainerModule.getAvailable(), 15 );
        extractorModule.onNewTurn();
        assertEquals( (int) extractorModule.getGameObject().getLocation().get().getResourceQuantity( ResourceType.FUEL ).get(), 2 );
        assertEquals( bigConnectedContainerModule.getAvailable(), 10 );
        extractorModule.onNewTurn();
        assertFalse( extractorModule.getGameObject().getLocation().get().getResourceQuantity( ResourceType.FUEL ).isPresent() );
        assertEquals( bigConnectedContainerModule.getAvailable(), 8 );
        assertEquals( bigConnectedContainerModule.getStock(), 7 );
    }

}