/**
* Copyright (C) 2002-2012 The FreeCol Team
*
* This file is part of FreeCol.
*
* FreeCol is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* FreeCol is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with FreeCol. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sf.freecol.server.ai;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sf.freecol.common.model.Building;
import net.sf.freecol.common.model.BuildingType;
import net.sf.freecol.common.model.Colony;
import net.sf.freecol.common.model.ColonyTile;
import net.sf.freecol.common.model.GoodsType;
import net.sf.freecol.common.model.Market;
import net.sf.freecol.common.model.Tile;
import net.sf.freecol.common.model.TypeCountMap;
import net.sf.freecol.common.model.Unit;
import net.sf.freecol.common.model.UnitTypeChange;
import net.sf.freecol.common.model.WorkLocation;
import net.sf.freecol.common.model.UnitTypeChange.ChangeType;
/**
* The production cache is intended to record all possible
* combinations of units producing goods in a colony's work
* locations. These entries are sorted, allowing fast retrieval of the
* most efficient way to produce a given type of goods.
*/
public class ProductionCache {
private final Colony colony;
/**
* The units available in the colony.
*/
private final Set<Unit> units;
/**
* The available colony tiles.
*/
private final Set<ColonyTile> colonyTiles;
/**
* Sorted entries per goods type.
*/
private final Map<GoodsType, List<Entry>> entries;
/**
* The assigned entries.
*/
private final List<Entry> assigned = new ArrayList<Entry>();
/**
* The reserved entries.
*/
private final List<Entry> reserved = new ArrayList<Entry>();
/**
* Compares entries by production.
*/
private static final Comparator<Entry> defaultComparator =
new CacheEntryComparator();
/**
* Compares entries by market value of production.
*/
private static final Comparator<Entry> marketValueComparator =
new CacheEntryComparator() {
public int compareProduction(Entry entry1, Entry entry2) {
int production = entry2.getProduction() - entry1.getProduction();
Market market = entry1.getUnit().getOwner().getMarket();
if (market != null) {
production = market.getSalePrice(entry2.getGoodsType(), entry2.getProduction())
- market.getSalePrice(entry1.getGoodsType(), entry1.getProduction());
}
return production;
}
};
/**
* The number of units available.
*/
private int unitCount;
/**
* The number of Units in various buildings.
*/
private TypeCountMap<BuildingType> unitCounts = new TypeCountMap<BuildingType>();
public ProductionCache(Colony colony) {
this.colony = colony;
this.units = new HashSet<Unit>(colony.getUnitList());
this.unitCount = units.size();
this.colonyTiles = new HashSet<ColonyTile>();
// this assumes all colonists can be added to any tile
Unit someUnit = colony.getUnitList().get(0);
for (ColonyTile colonyTile : colony.getColonyTiles()) {
if (colonyTile.canAdd(someUnit)) {
colonyTiles.add(colonyTile);
}
}
this.entries = new HashMap<GoodsType, List<Entry>>();
}
private List<Entry> createEntries(GoodsType goodsType) {
List<Entry> result = new ArrayList<Entry>();
if (goodsType.isFarmed()) {
for (ColonyTile colonyTile : colonyTiles) {
Tile tile = colonyTile.getWorkTile();
if (tile.potential(goodsType, null) > 0
|| (tile.hasResource()
&& !tile.getTileItemContainer().getResource().getType()
.getModifierSet(goodsType.getId()).isEmpty())) {
for (Unit unit : units) {
result.add(new Entry(goodsType, colonyTile, unit));
}
}
}
} else {
for (Building building : colony.getBuildingsForProducing(goodsType)) {
if (building.getType().getWorkPlaces() > 0) {
for (Unit unit : units) {
result.add(new Entry(goodsType, building, unit));
}
}
}
}
Collections.sort(result, defaultComparator);
entries.put(goodsType, result);
return result;
}
public Set<Unit> getUnits() {
return units;
}
public int getUnitCount() {
return unitCount;
}
public int getUnitCount(BuildingType buildingType) {
return unitCounts.getCount(buildingType);
}
public int decrementUnitCount(BuildingType buildingType) {
Integer result = unitCounts.incrementCount(buildingType, -1);
return (result == null) ? 0 : result.intValue();
}
public List<Entry> getAssigned() {
return assigned;
}
public List<Entry> getReserved() {
return reserved;
}
public List<Entry> getEntries(GoodsType goodsType) {
List<Entry> result = entries.get(goodsType);
if (result == null) {
result = createEntries(goodsType);
}
return result;
}
public List<Entry> getEntries(List<GoodsType> goodsTypes) {
return getEntries(goodsTypes, false);
}
public List<Entry> getEntries(List<GoodsType> goodsTypes, boolean useMarketValues) {
List<Entry> result = new ArrayList<Entry>();
for (GoodsType goodsType : goodsTypes) {
result.addAll(getEntries(goodsType));
}
if (useMarketValues) {
Collections.sort(result, marketValueComparator);
} else {
Collections.sort(result, defaultComparator);
}
return result;
}
/**
* Assigns an entry. All conflicting entries, i.e. entries that
* refer to the same unit or colony tile, are removed from the
* cache.
*
* @param entry an <code>Entry</code> value
*/
public void assign(Entry entry) {
ColonyTile colonyTile = null;
Building building = null;
if (entry.getWorkLocation() instanceof ColonyTile) {
colonyTile = (ColonyTile) entry.getWorkLocation();
colonyTiles.remove(colonyTile);
} else if (entry.getWorkLocation() instanceof Building) {
building = (Building) entry.getWorkLocation();
unitCounts.incrementCount(building.getType(), 1);
}
Unit unit = null;
if (!entry.isOtherExpert()) {
unit = entry.getUnit();
units.remove(unit);
assigned.add(entry);
removeEntries(unit, colonyTile, reserved);
} else {
if (colonyTile == null) {
if (unitCounts.getCount(building.getType()) == 1) {
// only add building once
reserved.addAll(entries.get(entry.getGoodsType()));
}
} else {
reserved.addAll(removeEntries(null, colonyTile, entries.get(entry.getGoodsType())));
}
}
// if work location is a colony tile, remove it from all other
// lists, because it only supports a single unit
for (List<Entry> entryList : entries.values()) {
removeEntries(unit, colonyTile, entryList);
}
unitCount--;
}
/*
private void removeEntries(Unit unit, WorkLocation workLocation) {
units.remove(unit);
if (workLocation instanceof ColonyTile) {
colonyTiles.remove((ColonyTile) workLocation);
}
for (List<Entry> entryList : entries.values()) {
removeEntries(unit, workLocation, entryList);
}
removeEntries(unit, null, reserved);
}
*/
/**
* Removes all entries that refer to the unit or work location
* given from the given list of entries and returns them.
*
* @param unit a <code>Unit</code>
* @param workLocation a <code>WorkLocation</code>
* @param entryList a <code>List</code> of <code>Entry</code>s
* @return the <code>Entry</code>s removed
*/
public static List<Entry> removeEntries(Unit unit, WorkLocation workLocation, List<Entry> entryList) {
Iterator<Entry> entryIterator = entryList.iterator();
List<Entry> removedEntries = new ArrayList<Entry>();
while (entryIterator.hasNext()) {
Entry entry = entryIterator.next();
if (entry.getUnit() == unit
|| entry.getWorkLocation() == workLocation) {
removedEntries.add(entry);
entryIterator.remove();
}
}
return removedEntries;
}
/**
* An Entry in the production cache represents a single unit
* producing goods in a certain work location. It records
* information on the type and amount of goods produced, as well
* as on whether the unit is an expert for producing this type of
* goods, or can be upgraded to one.
*
*/
public class Entry {
private final GoodsType goodsType;
private final WorkLocation workLocation;
private final Unit unit;
private final int production;
private boolean isExpert = false;
private boolean isOtherExpert = false;
private boolean unitUpgrades = false;
private boolean unitUpgradesToExpert = false;
public Entry(GoodsType g, WorkLocation w, Unit u) {
goodsType = g;
workLocation = w;
unit = u;
if (workLocation instanceof ColonyTile) {
production = ((ColonyTile) workLocation).getWorkTile().potential(goodsType, unit.getType());
} else if (workLocation instanceof Building) {
production = ((Building) workLocation).getUnitProductivity(unit);
} else {
production = 0;
}
GoodsType expertProduction = unit.getType().getExpertProduction();
if (expertProduction != null) {
if (expertProduction == goodsType) {
isExpert = true;
} else {
isOtherExpert = true;
}
} else {
for (UnitTypeChange change : unit.getType().getTypeChanges()) {
if (change.asResultOf(ChangeType.EXPERIENCE)) {
unitUpgrades = true;
if (change.getNewUnitType().getExpertProduction() == goodsType) {
unitUpgradesToExpert = true;
break;
}
}
}
}
}
/**
* Returns the type of goods produced.
*
* @return a <code>GoodsType</code> value
*/
public GoodsType getGoodsType() {
return goodsType;
}
/**
* Returns the work location where goods are produced.
*
* @return a <code>WorkLocation</code> value
*/
public WorkLocation getWorkLocation() {
return workLocation;
}
/**
* Returns a unit producing goods in this work location.
*
* @return an <code>Unit</code> value
*/
public Unit getUnit() {
return unit;
}
/**
* Returns the amount of goods produced.
*
* @return an <code>int</code> value
*/
public int getProduction() {
return production;
}
/**
* Returns true if the unit is an expert for producing the
* type of goods selected.
*
* @return a <code>boolean</code> value
*/
public boolean isExpert() {
return isExpert;
}
/**
* Returns true if the unit is an expert for producing a type
* of goods other than the one selected.
*
* @return a <code>boolean</code> value
*/
public boolean isOtherExpert() {
return isOtherExpert;
}
/**
* Returns true if the unit can be upgraded through experience.
*
* @return a <code>boolean</code> value
*/
public boolean unitUpgrades() {
return unitUpgrades;
}
/**
* Returns true if the unit can be upgraded to an expert for
* producing the type of goods selected through experience.
*
* @return a <code>boolean</code> value
*/
public boolean unitUpgradesToExpert() {
return unitUpgradesToExpert;
}
/**
* Returns a string representation of this entry.
*
* @return a <code>String</code> value
*/
public String toString() {
String result = "Cache entry: " + unit.toString();
if (workLocation instanceof ColonyTile) {
return result
+ ((ColonyTile) workLocation).getTile().getNameKey()
+ "(" + workLocation.getId() + ") " + goodsType.getNameKey();
} else if (workLocation instanceof Building) {
return result
+ ((Building) workLocation).getNameKey() + "(" + workLocation.getId() + ") ";
} else {
return result;
}
}
}
}