de.ailis.xadrian.data.Complex.java Source code

Java tutorial

Introduction

Here is the source code for de.ailis.xadrian.data.Complex.java

Source

/*
 * Copyright (C) 2010-2012 Klaus Reimer <k@ailis.de>
 * See LICENSE.TXT for licensing information.
 */

package de.ailis.xadrian.data;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.io.StringWriter;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedSet;

import javax.xml.bind.DatatypeConverter;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import de.ailis.xadrian.data.factories.FactoryFactory;
import de.ailis.xadrian.data.factories.GameFactory;
import de.ailis.xadrian.data.factories.RaceFactory;
import de.ailis.xadrian.data.factories.SectorFactory;
import de.ailis.xadrian.data.factories.SunFactory;
import de.ailis.xadrian.data.factories.WareFactory;
import de.ailis.xadrian.dialogs.SetYieldsDialog;
import de.ailis.xadrian.exceptions.DataException;
import de.ailis.xadrian.exceptions.GameNotFoundException;
import de.ailis.xadrian.exceptions.TemplateCodeException;
import de.ailis.xadrian.interfaces.GameProvider;
import de.ailis.xadrian.support.Config;
import de.ailis.xadrian.support.DynaByteInputStream;
import de.ailis.xadrian.support.DynaByteOutputStream;
import de.ailis.xadrian.support.I18N;
import de.ailis.xadrian.support.ModalDialog.Result;
import de.ailis.xadrian.support.MultiCollection;

/**
 * A complex
 *
 * @author Klaus Reimer (k@ailis.de)
 */
public class Complex implements Serializable, GameProvider {
    /** Serial version UID */
    private static final long serialVersionUID = 2128684141345704703L;

    /** The logger */
    private static final Log log = LogFactory.getLog(Complex.class);

    /** The single price of a complex construction kit */
    public static final int KIT_PRICE = 259696;

    /** The volume of a complex construction kit */
    public static final int KIT_VOLUME = 4250;

    /** The complex counter for the complex name generator */
    private static int complexCounter = 0;

    /** The game this complex belongs to. */
    private final Game game;

    /** The complex name */
    private String name;

    /** The factories in this complex */
    private final List<ComplexFactory> factories;

    /** Automatically added factories in this complex */
    private final List<ComplexFactory> autoFactories;

    /** The sun power in percent */
    private Sun suns;

    /** The sector where this complex is build */
    private Sector sector = null;

    /** If base complex should be calculated or not */
    private boolean addBaseComplex = false;

    /** Custom buy/sell prices in this complex */
    private final Map<Ware, Integer> customPrices;

    /** If complex setup should be displayed */
    private boolean showingComplexSetup = true;

    /** If production statistics should be displayed */
    private boolean showingProductionStats = false;

    /** If storage capacities should be displayed */
    private boolean showingStorageCapacities = false;

    /** If shopping list should be displayed */
    private boolean showingShoppingList = false;

    /** The built factories */
    private final Map<String, Integer> builtFactories;

    /** The number of built kits */
    private int builtKits;

    /** The cached shopping list */
    private ShoppingList shoppingList;

    /**
     * Constructor
     *
     * @param game
     *            The game this complex belongs to.
     */
    public Complex(final Game game) {
        this(game, createComplexName());
    }

    /**
     * Constructor
     *
     * @param game
     *            The game this complex belongs to.
     * @param name
     *            The complex name
     */
    public Complex(final Game game, final String name) {
        this.game = game;
        this.suns = game.getSunFactory().getDefaultSun();
        this.name = name;
        this.factories = new ArrayList<ComplexFactory>();
        this.autoFactories = new ArrayList<ComplexFactory>();
        this.customPrices = new HashMap<Ware, Integer>();
        this.builtFactories = new HashMap<String, Integer>();
    }

    /**
     * Returns a new name for a complex.
     *
     * @return A new complex name
     */
    private static String createComplexName() {
        complexCounter++;
        return I18N.getString("complex.nameTemplate", complexCounter);
    }

    /**
     * Returns the name.
     *
     * @return The name
     */
    public String getName() {
        return this.name;
    }

    /**
     * Sets the complex name.
     *
     * @param name
     *            The complex name to set
     */
    public void setName(final String name) {
        this.name = name;
    }

    /**
     * Returns the total number of factories in the complex
     *
     * @return The total number of factories in the complex
     */
    public int getTotalQuantity() {
        int quantity = 0;
        for (final ComplexFactory factory : this.factories)
            quantity += factory.getQuantity();
        for (final ComplexFactory factory : this.autoFactories)
            quantity += factory.getQuantity();
        return quantity;
    }

    /**
     * Returns the total complex price.
     *
     * @return The total complex price
     */
    public long getTotalPrice() {
        long price = 0;
        for (final ComplexFactory complexFactory : this.factories)
            price += ((long) complexFactory.getQuantity()) * complexFactory.getFactory().getPrice();
        for (final ComplexFactory complexFactory : this.autoFactories)
            price += ((long) complexFactory.getQuantity()) * complexFactory.getFactory().getPrice();
        return price + getTotalKitPrice();
    }

    /**
     * A immutable copy of the factories in this complex.
     *
     * @return The factories in this complex
     */
    public List<ComplexFactory> getFactories() {
        return Collections.unmodifiableList(this.factories);
    }

    /**
     * A immutable copy of the automatically added factories in this complex.
     *
     * @return The automatically added factories in this complex
     */
    public List<ComplexFactory> getAutoFactories() {
        return Collections.unmodifiableList(this.autoFactories);
    }

    /**
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        return new HashCodeBuilder().append(this.name).append(this.factories).append(this.autoFactories).hashCode();
    }

    /**
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(final Object obj) {
        if (obj == null)
            return false;
        if (obj == this)
            return true;
        if (obj.getClass() != getClass())
            return false;
        final Complex other = (Complex) obj;
        return new EqualsBuilder().append(this.name, other.name).append(this.factories, other.factories)
                .append(this.suns, other.suns).append(this.sector, other.sector).isEquals();
    }

    /**
     * Removes the factory with the given index.
     *
     * @param index
     *            The factory index
     */
    public void removeFactory(final int index) {
        this.factories.remove(index);
        calculateBaseComplex();
        updateShoppingList();
    }

    /**
     * Disables the factory with the given index.
     *
     * @param index
     *            The factory index
     */
    public void disableFactory(final int index) {
        this.factories.get(index).disable();
        calculateBaseComplex();
    }

    /**
     * Disables the factory with the given index.
     *
     * @param index
     *            The factory index
     */
    public void enableFactory(final int index) {
        this.factories.get(index).enable();
        calculateBaseComplex();
    }

    /**
     * Accepts the automatically added factory with the given index.
     *
     * @param index
     *            The factory index
     */
    public void acceptFactory(final int index) {
        addFactory(this.autoFactories.get(index));
        calculateBaseComplex();
    }

    /**
     * Returns the quantity of the factory with the given index.
     *
     * @param index
     *            The factory index
     * @return The quantity
     */
    public int getQuantity(final int index) {
        return this.factories.get(index).getQuantity();
    }

    /**
     * Increases the quantity of the factory with the given index.
     *
     * @param index
     *            The factory index
     * @return True if quantity was changed, false if not.
     */
    public boolean increaseQuantity(final int index) {
        if (this.factories.get(index).increaseQuantity()) {
            calculateBaseComplex();
            updateShoppingList();
            return true;
        }
        return false;
    }

    /**
     * Decreases the quantity of the factory with the given index.
     *
     * @param index
     *            The factory index
     * @return True if quantity was changed, false if not.
     */
    public boolean decreaseQuantity(final int index) {
        if (this.factories.get(index).decreaseQuantity()) {
            calculateBaseComplex();
            updateShoppingList();
            return true;
        }
        return false;
    }

    /**
     * Sets the quantity of the factory with the given index to the specified
     * quantity.
     *
     * @param index
     *            The factory index
     * @param quantity
     *            The quantity to set
     */
    public void setQuantity(final int index, final int quantity) {
        final ComplexFactory factory = this.factories.get(index);
        if (factory.getQuantity() != quantity) {
            factory.setQuantity(quantity);
            calculateBaseComplex();
            updateShoppingList();
        }
    }

    /**
     * Returns the yield of the factory with the given index.
     *
     * @param index
     *            The factory index
     * @return The yield
     */
    public List<Integer> getYields(final int index) {
        return this.factories.get(index).getYields();
    }

    /**
     * Sets the yields of the factory with the given index.
     *
     * @param index
     *            The factory index
     * @param yields
     *            The yields to set
     */
    public void setYields(final int index, final List<Integer> yields) {
        final ComplexFactory factory = this.factories.get(index);
        factory.setYields(yields);
        calculateBaseComplex();
        updateShoppingList();
    }

    /**
     * Returns the factory type of factory with the given index.
     *
     * @param index
     *            The factory index
     * @return The factory
     */
    public Factory getFactory(final int index) {
        return this.factories.get(index).getFactory();
    }

    /**
     * Sets the suns in percent.
     *
     * @param suns
     *            The suns in percent to set
     */
    public void setSuns(final Sun suns) {
        this.suns = suns;
        calculateBaseComplex();
    }

    /**
     * Returns the suns in percent.
     *
     * @return The suns in percent
     */
    public Sun getSuns() {
        if (this.sector != null)
            return this.sector.getSuns();
        return this.suns;
    }

    /**
     * Adds a factory to the complex.
     *
     * @param factory
     *            The factory to add
     */
    public void addFactory(final Factory factory) {
        if (factory.isMine()) {
            final SetYieldsDialog dialog = new SetYieldsDialog(factory);
            dialog.setYields(null);
            dialog.setSector(getSector());
            if (dialog.open() == Result.OK) {
                setSector(dialog.getSector());
                addFactory(new ComplexFactory(this.game, factory, dialog.getYields()));
                calculateBaseComplex();
                updateShoppingList();
            }
        } else {
            addFactory(new ComplexFactory(this.game, factory, 1, 0));
            calculateBaseComplex();
            updateShoppingList();
        }
    }

    /**
     * Adds the specified factory/factories to the complex.
     *
     * @param complexFactory
     *            The factory/factories to add
     */
    private void addFactory(final ComplexFactory complexFactory) {
        if (!complexFactory.getFactory().isMine()) {
            for (final ComplexFactory current : this.factories) {
                if (current.getFactory().equals(complexFactory.getFactory())
                        && current.getYield() == complexFactory.getYield()) {
                    current.addQuantity(complexFactory.getQuantity());
                    return;
                }
            }
        }
        this.factories.add(complexFactory);
        Collections.sort(this.factories);
        updateShoppingList();
    }

    /**
     * Converts the complex into XML and returns it.
     *
     * @return The complex as XML
     */
    public Document toXML() {
        final Document document = DocumentHelper.createDocument();
        final Element root = document.addElement("complex");
        root.addAttribute("version", "4");
        root.addAttribute("game", this.game.getId());
        root.addAttribute("suns", Integer.toString(getSuns().getPercent()));
        if (this.sector != null)
            root.addAttribute("sector", this.sector.getId());
        root.addAttribute("addBaseComplex", Boolean.toString(this.addBaseComplex));
        root.addAttribute("showingProductionStats", Boolean.toString(this.showingProductionStats));
        root.addAttribute("showingShoppingList", Boolean.toString(this.showingShoppingList));
        root.addAttribute("showingStorageCapacities", Boolean.toString(this.showingStorageCapacities));
        root.addAttribute("showingComplexSetup", Boolean.toString(this.showingComplexSetup));
        if (!this.factories.isEmpty()) {
            final Element factoriesE = root.addElement("complexFactories");
            for (final ComplexFactory factory : this.factories) {
                final Element factoryE = factoriesE.addElement("complexFactory");
                factoryE.addAttribute("factory", factory.getFactory().getId());
                factoryE.addAttribute("disabled", Boolean.toString(factory.isDisabled()));
                if (factory.getFactory().isMine()) {
                    final Element yieldsE = factoryE.addElement("yields");
                    for (final Integer yield : factory.getYields()) {
                        final Element yieldE = yieldsE.addElement("yield");
                        yieldE.setText(Integer.toString(yield));
                    }
                } else {
                    factoryE.addAttribute("quantity", Integer.toString(factory.getQuantity()));
                }
            }
        }
        if (!this.customPrices.isEmpty()) {
            final Element waresE = root.addElement("complexWares");
            for (final Map.Entry<Ware, Integer> entry : this.customPrices.entrySet()) {
                final Ware ware = entry.getKey();
                final int price = entry.getValue();
                final Element wareE = waresE.addElement("complexWare");
                wareE.addAttribute("ware", ware.getId());
                wareE.addAttribute("use", Boolean.valueOf(price > 0).toString());
                wareE.addAttribute("price", Integer.toString(Math.abs(price)));
            }
        }

        final Element shoppingListE = root.addElement("built");
        shoppingListE.addAttribute("kits", Integer.toString(this.builtKits));
        for (final Entry<String, Integer> entry : this.builtFactories.entrySet()) {
            final String id = entry.getKey();
            final int quantity = entry.getValue();
            final Element factoryE = shoppingListE.addElement("factory");
            factoryE.addAttribute("id", id);
            factoryE.addAttribute("quantity", Integer.toString(quantity));
        }

        return document;
    }

    /**
     * Loads a complex from the specified XML document and returns it.
     *
     * @param document
     *            The XML document
     * @return The complex
     * @throws DocumentException
     *             If XML file could not be read
     */
    public static Complex fromXML(final Document document) throws DocumentException {
        final Element root = document.getRootElement();

        // Check the version
        final String versionStr = root.attributeValue("version");
        int version = 1;
        if (versionStr != null)
            version = Integer.parseInt(versionStr);
        if (version > 4)
            throw new DocumentException(I18N.getString("error.fileFormatTooNew"));

        // Determine the game for this complex.
        String gameId = "x3tc";
        if (version == 4)
            gameId = root.attributeValue("game");
        final Game game = GameFactory.getInstance().getGame(gameId);

        final Complex complex = new Complex(game);
        final FactoryFactory factoryFactory = game.getFactoryFactory();
        final SectorFactory sectorFactory = game.getSectorFactory();
        final WareFactory wareFactory = game.getWareFactory();
        final SunFactory sunsFactory = game.getSunFactory();

        complex.setSuns(sunsFactory.getSun(Integer.parseInt(root.attributeValue("suns"))));
        complex.setSector(sectorFactory.getSector(root.attributeValue("sector")));
        complex.setAddBaseComplex(Boolean.parseBoolean(root.attributeValue("addBaseComplex", "false")));
        complex.showingProductionStats = Boolean
                .parseBoolean(root.attributeValue("showingProductionStats", "false"));
        complex.showingShoppingList = Boolean.parseBoolean(root.attributeValue("showingShoppingList", "false"));
        complex.showingStorageCapacities = Boolean
                .parseBoolean(root.attributeValue("showingStorageCapacities", "false"));
        complex.showingComplexSetup = Boolean.parseBoolean(root.attributeValue("showingComplexSetup", "true"));

        // Get the factories parent element (In older version this was the root
        // node)
        Element factoriesE = root.element("complexFactories");
        if (factoriesE == null)
            factoriesE = root;

        // Read the complex factories
        for (final Object item : factoriesE.elements("complexFactory")) {
            final Element element = (Element) item;
            final Factory factory = factoryFactory.getFactory(element.attributeValue("factory"));
            final ComplexFactory complexFactory;
            final Element yieldsE = element.element("yields");
            if (yieldsE == null) {
                final int yield = Integer.parseInt(element.attributeValue("yield", "0"));
                final int quantity = Integer.parseInt(element.attributeValue("quantity"));
                complexFactory = new ComplexFactory(game, factory, quantity, yield);
            } else {
                final List<Integer> yields = new ArrayList<Integer>();
                for (final Object yieldItem : yieldsE.elements("yield")) {
                    final Element yieldE = (Element) yieldItem;
                    yields.add(Integer.parseInt(yieldE.getText()));
                }
                complexFactory = new ComplexFactory(game, factory, yields);
            }
            if (Boolean.parseBoolean(element.attributeValue("disabled", "false")))
                complexFactory.disable();
            complex.addFactory(complexFactory);
        }

        // Read the complex wares
        final Element waresE = root.element("complexWares");
        if (waresE != null) {
            complex.customPrices.clear();
            for (final Object item : waresE.elements("complexWare")) {
                final Element element = (Element) item;
                final Ware ware = wareFactory.getWare(element.attributeValue("ware"));
                final boolean use = Boolean.parseBoolean(element.attributeValue("use"));
                final int price = Integer.parseInt(element.attributeValue("price"));
                complex.customPrices.put(ware, use ? price : -price);
            }
        }

        final Element builtE = root.element("built");
        if (builtE != null) {
            complex.builtKits = Integer.parseInt(builtE.attributeValue("kits", "0"));
            for (final Object item : builtE.elements("factory")) {
                final Element element = (Element) item;
                final String id = element.attributeValue("id");
                final int quantity = Integer.parseInt(element.attributeValue("quantity"));
                complex.builtFactories.put(id, quantity);
            }
        }

        complex.calculateBaseComplex();
        return complex;
    }

    /**
     * Returns all factories (Manually and automatically added ones):
     *
     * @return All factories
     */
    @SuppressWarnings("unchecked")
    private Collection<ComplexFactory> getAllFactories() {
        return new MultiCollection<ComplexFactory>(this.factories, this.autoFactories);
    }

    /**
     * Returns the products this complex produces in one hour.
     *
     * @return The products per hour.
     */
    public Collection<Product> getProductsPerHour() {
        final Map<String, Product> products = new HashMap<String, Product>();
        for (final ComplexFactory factory : getAllFactories()) {
            final Product product = factory.getProductPerHour(getSuns());
            final Ware ware = product.getWare();
            final Product mapProduct = products.get(ware.getId());
            if (mapProduct == null)
                products.put(ware.getId(), product);
            else
                products.put(ware.getId(), new Product(ware, mapProduct.getQuantity() + product.getQuantity()));
        }
        return products.values();
    }

    /**
     * Returns the resources this complex needs in one hour.
     *
     * @return The needed resources per hour.
     */
    public Collection<Product> getResourcesPerHour() {
        final Map<String, Product> resources = new HashMap<String, Product>();
        for (final ComplexFactory factory : getAllFactories()) {
            for (final Product resource : factory.getResourcesPerHour(getSuns())) {
                final Ware ware = resource.getWare();
                final Product mapResource = resources.get(ware.getId());
                if (mapResource == null)
                    resources.put(ware.getId(), resource);
                else
                    resources.put(ware.getId(),
                            new Product(ware, mapResource.getQuantity() + resource.getQuantity()));
            }
        }
        return resources.values();
    }

    /**
     * Returns the list of complex wares (Produced and needed).
     *
     * @return The list of complex wares
     */
    public Collection<ComplexWare> getWares() {
        final Map<String, ComplexWare> wares = new HashMap<String, ComplexWare>();

        // Add the products
        for (final Product product : getProductsPerHour()) {
            final Ware ware = product.getWare();
            final String wareId = ware.getId();
            wares.put(wareId, new ComplexWare(ware, product.getQuantity(), 0, getWarePrice(ware)));
        }

        // Add the resources
        for (final Product resource : getResourcesPerHour()) {
            final Ware ware = resource.getWare();
            final String wareId = ware.getId();
            ComplexWare complexWare = wares.get(wareId);
            if (complexWare == null)
                complexWare = new ComplexWare(ware, 0, resource.getQuantity(), getWarePrice(ware));
            else
                complexWare = new ComplexWare(ware, complexWare.getProduced(), resource.getQuantity(),
                        getWarePrice(ware));
            wares.put(wareId, complexWare);
        }

        final List<ComplexWare> result = new ArrayList<ComplexWare>(wares.values());
        Collections.sort(result);
        return result;
    }

    /**
     * Returns the profit of this complex.
     *
     * @return The profit
     */
    public double getProfit() {
        double profit;

        profit = 0;
        for (final ComplexWare complexWare : getWares()) {
            profit += complexWare.getProfit();
        }
        return profit;
    }

    /**
     * Returns the number of needed complex construction kits in this complex.
     *
     * @return The number of needed complex construction kits.
     */
    public int getKitQuantity() {
        return Math.max(0, getTotalQuantity() - 1);
    }

    /**
     * Returns the price of a single complex construction kit.
     *
     * @return The price of a single complex construction kit
     */
    public int getKitPrice() {
        return KIT_PRICE;
    }

    /**
     * Returns the total price of all needed complex construction kits.
     *
     * @return The total price of all needed complex construction kits
     */
    public int getTotalKitPrice() {
        return getKitQuantity() * getKitPrice();
    }

    /**
     * Calculates and adds the factories needed to keep the factories of this
     * complex running stable.
     */
    private void calculateBaseComplex() {
        final FactoryFactory factoryFactory = this.game.getFactoryFactory();
        final RaceFactory raceFactory = this.game.getRaceFactory();
        final Ware crystals = this.game.getWareFactory().getWare("crystals");
        final Config config = Config.getInstance();
        long currentPrice;
        long price;
        final List<ComplexFactory> backup = new ArrayList<ComplexFactory>();

        // First of all remove all automatically added factories
        this.autoFactories.clear();
        updateShoppingList();

        if (!this.addBaseComplex)
            return;

        // First of all we build a base complex without specific crystal fab
        // race and remember the price
        while (true)
            if (!addBaseComplex(null))
                break;
        currentPrice = getTotalPrice();

        // Now cycle over all races and check if the complex gets cheaper if
        // the crystal fabs are bought from them
        for (final Race race : raceFactory.getRaces()) {
            // If race is ignored then don't use it
            if (config.isRaceIgnored(race))
                continue;

            // If race has no crystal fabs then don't use it
            if (!factoryFactory.hasFactories(race, crystals))
                continue;

            // Backup current automatically added factories, clear the
            // calculated factories and then calculate the complex again with
            // a specific "crystal race"
            backup.addAll(this.autoFactories);
            this.autoFactories.clear();
            while (true)
                if (!addBaseComplex(race))
                    break;

            // Check if new price is cheaper then the old one. If cheaper
            // then the new complex is used (and checked against the next
            // available race). If not cheaper then the old complex is restored
            price = getTotalPrice();
            if (price < currentPrice) {
                currentPrice = price;
            } else {
                this.autoFactories.clear();
                this.autoFactories.addAll(backup);
            }
            backup.clear();
        }
        updateShoppingList();
    }

    /**
     * Updates the base complex.
     */
    public void updateBaseComplex() {
        calculateBaseComplex();
    }

    /**
     * Updates the shopping list.
     */
    public void updateShoppingList() {
        this.shoppingList = null;
        this.builtKits = Math.max(0, Math.min(this.builtKits, getTotalQuantity() - 1));
        for (final Map.Entry<String, Integer> entry : this.builtFactories.entrySet()) {
            final String id = entry.getKey();
            final int max = getMaxFactories(id);
            final int quantity = Math.min(entry.getValue(), max);
            this.builtFactories.put(id, quantity);
        }
        this.shoppingList = null;
    }

    /**
     * Searches for the first unfulfilled resource need (which is not a mineral)
     * and adds the necessary factories for this. If a need was found (and
     * fixed) then this method returns true. If all needs are already fulfilled
     * then it returns false.
     *
     * @param crystalRace
     *            Optional race from which crystal fabs should be bought. If
     *            null then the cheapest fab is searched.
     * @return True if a need was found and fixed, false if everything is
     *         finished
     */
    private boolean addBaseComplex(final Race crystalRace) {
        for (final ComplexWare ware : getWares()) {
            // We are not going to add mines
            if (ware.getWare().isMineral())
                continue;

            // If the current ware has missing units then add the necessary
            // factories for this ware and then restart the adding of factories
            if (ware.getMissing() > 0) {
                final Race race = ware.getWare().getId().equals("crystals") ? crystalRace : null;
                if (!addBaseComplexForWare(ware, race))
                    continue;
                return true;
            }

        }
        return false;
    }

    /**
     * Adds the factories needed to fulfill the need of the specified complex
     * ware.
     *
     * @param complexWare
     *            The complex ware for which factories must be added
     * @param race
     *            The race from which factories should be bought. If null then
     *            the cheapest factory is used.
     * @return True if a new factories were added, false if this was not
     *         possible
     */
    private boolean addBaseComplexForWare(final ComplexWare complexWare, final Race race) {
        final Ware ware = complexWare.getWare();
        final FactoryFactory factoryFactory = this.game.getFactoryFactory();

        // Remove all automatically added factories which produces the
        // specified ware and calculate the real need which must be
        // fulfilled.
        double need = complexWare.getMissing();
        final double oldNeed = need;
        for (final ComplexFactory complexFactory : new ArrayList<ComplexFactory>(this.autoFactories)) {
            if (complexFactory.getFactory().getProduct().getWare().equals(ware)) {
                need += complexFactory.getProductPerHour(getSuns()).getQuantity();
                this.autoFactories.remove(complexFactory);
            }
        }

        // Determine the available factory sizes
        final SortedSet<FactorySize> sizesSet = factoryFactory.getFactorySizes(ware, race);
        final FactorySize[] sizes = sizesSet.toArray(new FactorySize[sizesSet.size()]);

        // Abort if no factories were found
        if (sizes.length == 0)
            return false;

        // Get the cheapest factories for the sizes
        final Map<FactorySize, Factory> factories = new HashMap<FactorySize, Factory>();
        for (final FactorySize size : sizes) {
            if (race == null)
                factories.put(size, factoryFactory.getCheapestFactory(ware, size));
            else
                factories.put(size, factoryFactory.getFactory(ware, size, race));
        }

        // Get the smallest possible production quantity
        final double minProduction = factories.get(sizes[0]).getProductPerHour(getSuns(), 0).getQuantity();

        // Iterate the available sizes (from largest to smallest) and add
        // the factories producing an adequate number of products
        for (int i = sizes.length - 1; i >= 0; i--) {
            final FactorySize size = sizes[i];
            final Factory factory = factories.get(size);
            final double product = factory.getProductPerHour(getSuns(), 0).getQuantity();

            // Calculate the number of factories of the current size needed
            log.debug("Need " + need + " units of " + ware + ". Considering " + factory + " which produces "
                    + product + " units");
            final int quantity = (int) Math.floor((need + minProduction - 0.1) / product);

            // Add the number of factories and decrease the need
            if (quantity > 0) {
                log.debug("Adding " + quantity + "x " + factory);
                this.autoFactories.add(new ComplexFactory(this.game, factory, quantity, 0));
                need -= quantity * product;
            } else
                log.debug("Not adding any " + factory);
        }
        if (Math.abs(need - oldNeed) < .0000001) {
            log.debug("Unable to calculate best matching factory. Aborting");
            return false;
        }
        return true;
    }

    /**
     * Toggles the addition of automatically calculated base complex.
     */
    public void toggleAddBaseComplex() {
        this.addBaseComplex = !this.addBaseComplex;
        calculateBaseComplex();
    }

    /**
     * Enables or disabled base complex addition.
     *
     * @param addBaseComplex
     *            True if base complex should be added, false if not
     */
    public void setAddBaseComplex(final boolean addBaseComplex) {
        this.addBaseComplex = addBaseComplex;
    }

    /**
     * Checks whether a base complex was added or not.
     *
     * @return True if a base complex was added. False if not.
     */
    public boolean isAddBaseComplex() {
        return this.addBaseComplex;
    }

    /**
     * Returns the storage capacities.
     *
     * @return The storage capacities.
     */
    public Collection<Capacity> getCapacities() {
        final Map<String, Capacity> capacities = new HashMap<String, Capacity>();
        for (final ComplexFactory factory : getAllFactories()) {
            for (final Capacity capacity : factory.getCapacities()) {
                final Ware ware = capacity.getWare();
                final Capacity mapCapacity = capacities.get(ware.getId());
                if (mapCapacity == null)
                    capacities.put(ware.getId(), capacity);
                else
                    capacities.put(ware.getId(),
                            new Capacity(ware, mapCapacity.getQuantity() + capacity.getQuantity()));
            }
        }
        final List<Capacity> result = new ArrayList<Capacity>(capacities.values());
        Collections.sort(result);
        return result;
    }

    /**
     * Returns the total storage capacity
     *
     * @return The total storage capacity;
     */
    public long getTotalCapacity() {
        long total = 0;
        for (final Capacity capacity : getCapacities())
            total += capacity.getQuantity();
        return total;
    }

    /**
     * Returns the total storage volume
     *
     * @return The total storage volume;
     */
    public long getTotalStorageVolume() {
        long total = 0;
        for (final Capacity capacity : getCapacities())
            total += capacity.getVolume();
        return total;
    }

    /**
     * Sets the sector in which to build this complex
     *
     * @param sector
     *            The sector to set
     */
    public void setSector(final Sector sector) {
        if ((sector != null && !sector.equals(this.sector)) || (sector == null && this.sector != null)) {
            this.sector = sector;
            calculateBaseComplex();
            updateShoppingList();
        }
    }

    /**
     * Returns the sector in which this complex is build.
     *
     * @return The sector
     */
    public Sector getSector() {
        return this.sector;
    }

    /**
     * Returns the factory shopping list.
     *
     * @return The factory shopping list
     */
    public ShoppingList getShoppingList() {
        // Return cached shopping list if present
        if (this.shoppingList != null)
            return this.shoppingList;

        final ShoppingList list = new ShoppingList(
                this.sector == null ? null : this.sector.getNearestKitSellingSector(), this.builtKits);
        for (final ComplexFactory factory : this.factories) {
            list.addItem(new ShoppingListItem(factory.getFactory(), factory.getQuantity(),
                    this.sector == null ? null : factory.getFactory().getNearestManufacturer(this.sector),
                    getFactoriesBuilt(factory.getFactory())));
        }
        for (final ComplexFactory factory : this.autoFactories) {
            list.addItem(new ShoppingListItem(factory.getFactory(), factory.getQuantity(),
                    this.sector == null ? null : factory.getFactory().getNearestManufacturer(this.sector),
                    getFactoriesBuilt(factory.getFactory())));
        }

        this.shoppingList = list;
        return list;
    }

    /**
     * Returns the price for the specified ware. If the price has a custom price
     * then this one is returned. If not then the standard average price of the
     * ware is returned.
     *
     * @param ware
     *            The ware
     * @return The price
     */
    public int getWarePrice(final Ware ware) {
        final Integer price = this.customPrices.get(ware);
        if (price == null)
            return ware.getAvgPrice();
        if (price < 0)
            return 0;
        return price;
    }

    /**
     * Returns the map with custom prices.
     *
     * @return The map with custom prices
     */
    public Map<Ware, Integer> getCustomPrices() {
        return Collections.unmodifiableMap(this.customPrices);
    }

    /**
     * Sets a new map with custom prices.
     *
     * @param customPrices
     *            The new map with custom prices
     */
    public void setCustomPrices(final Map<Ware, Integer> customPrices) {
        this.customPrices.clear();
        this.customPrices.putAll(customPrices);
    }

    /**
     * Checks if complex setup should be displayed.
     *
     * @return True if complex setup should be displayed, false if not
     */
    public boolean isShowingComplexSetup() {
        return this.showingComplexSetup;
    }

    /**
     * Toggles the display of the complex setup.
     */
    public void toggleShowingComplexSetup() {
        this.showingComplexSetup = !this.showingComplexSetup;
    }

    /**
     * Checks if production stats should be displayed.
     *
     * @return True if production stats should be displayed, false if not
     */
    public boolean isShowingProductionStats() {
        return this.showingProductionStats;
    }

    /**
     * Toggles the display of production statistics.
     */
    public void toggleShowingProductionStats() {
        this.showingProductionStats = !this.showingProductionStats;
    }

    /**
     * Checks if storage capacities should be displayed.
     *
     * @return True if storage capacities should be displayed, false if not
     */
    public boolean isShowingStorageCapacities() {
        return this.showingStorageCapacities;
    }

    /**
     * Toggles the display of storage capacities.
     */
    public void toggleShowingStorageCapacities() {
        this.showingStorageCapacities = !this.showingStorageCapacities;
    }

    /**
     * Checks if the shopping list should be displayed.
     *
     * @return True if the shopping list should be displayed, false if not
     */
    public boolean isShowingShoppingList() {
        return this.showingShoppingList;
    }

    /**
     * Toggles the display of the shopping list.
     */
    public void toggleShowingShoppingList() {
        this.showingShoppingList = !this.showingShoppingList;
    }

    /**
     * Returns the maximum number of factories in the shopping list for the
     * specified factory id.
     *
     * @param id
     *            The id of the factory
     * @return The number of factories in the shopping list
     */
    private int getMaxFactories(final String id) {
        final ShoppingList list = getShoppingList();
        for (final ShoppingListItem item : list.getItems()) {
            if (item.getFactory().getId().equals(id))
                return item.getQuantity();
        }
        return 0;
    }

    /**
     * Builds a factory with the specified id
     *
     * @param id
     *            The id of the factory to build
     */
    public void buildFactory(final String id) {
        Integer oldCount = this.builtFactories.get(id);
        if (oldCount == null)
            oldCount = 0;
        if (oldCount == getMaxFactories(id))
            return;
        this.builtFactories.put(id, oldCount + 1);
        updateShoppingList();
    }

    /**
     * Destroys a factory with the specified id.
     *
     * @param id
     *            The id of the factory to destroy
     */
    public void destroyFactory(final String id) {
        final Integer oldCount = this.builtFactories.get(id);
        if (oldCount == null)
            return;
        if (oldCount == 0)
            return;
        this.builtFactories.put(id, oldCount - 1);
        updateShoppingList();
    }

    /**
     * Build a kit.
     */
    public void buildKit() {
        if (this.builtKits >= getTotalQuantity() - 1)
            return;
        this.builtKits++;
        updateShoppingList();
    }

    /**
     * Destroys a kit.
     */
    public void destroyKit() {
        if (this.builtKits == 0)
            return;
        this.builtKits--;
        updateShoppingList();
    }

    /**
     * Returns the number of built factories of the specified type.
     *
     * @param factory
     *            The factory type
     * @return The number of built factores of the specified type
     */
    private int getFactoriesBuilt(final Factory factory) {
        final Integer count = this.builtFactories.get(factory.getId());
        return count == null ? 0 : count;
    }

    /**
     * @see de.ailis.xadrian.interfaces.GameProvider#getGame()
     */
    @Override
    public Game getGame() {
        return this.game;
    }

    /**
     * Checks if the complex uses the specified ware as product or resource.
     *
     * @param ware
     *            The ware to check.
     * @return True if ware is used, false if not.
     */
    public boolean usesWare(final Ware ware) {
        for (final ComplexFactory complexFactory : getAllFactories()) {
            final Factory factory = complexFactory.getFactory();
            if (factory.getProduct().getWare().equals(ware))
                return true;
            for (final Product product : factory.getResources())
                if (product.getWare().equals(ware))
                    return true;
        }
        return false;
    }

    /**
     * Checks if the complex is empty.
     *
     * @return True when the complex is empty, false if not.
     */
    public boolean isEmpty() {
        return this.factories.isEmpty();
    }

    /**
     * Checks if the specified code is a valid template code.
     *
     * @param templateCode The template code to check.
     * @return True if code is valid, false if not.
     */
    public static boolean isValidTemplateCode(final String templateCode) {
        try {
            // Decode base 64
            final byte[] data = DatatypeConverter.parseBase64Binary(templateCode.trim());

            final InputStream stream = new DynaByteInputStream(new ByteArrayInputStream(data));
            try {
                // Read complex settings
                final int settings = stream.read();
                final boolean hasSector = (settings & 1) == 1;
                final int gameNid = (settings >> 1) & 7;

                final Game game;
                try {
                    game = GameFactory.getInstance().getGame(gameNid);
                } catch (final GameNotFoundException e) {
                    return false;
                }

                // Read sector coordinates or sun power
                if (hasSector) {
                    final int x = stream.read();
                    final int y = stream.read();
                    if (game.getSectorFactory().getSector(x, y) == null)
                        return false;
                } else {
                    final int percent = stream.read();
                    try {
                        game.getSunFactory().getSun(percent);
                    } catch (final DataException e) {
                        return false;
                    }
                }

                int factoryId;
                while ((factoryId = stream.read()) != 0) {
                    final Factory factory = game.getFactoryFactory().getFactory(factoryId);
                    if (factory == null)
                        return false;
                    if (factory.isMine()) {
                        int yield;
                        while ((yield = stream.read()) != 0)
                            if (yield > 256)
                                return false;
                    } else {
                        stream.read();
                    }
                }

                return true;
            } finally {
                stream.close();
            }
        } catch (final Exception e) {
            return false;
        }
    }

    /**
     * Creates a complex from the specified template code.
     *
     * @param templateCode
     *            The template code
     * @return The complex.
     */
    public static Complex fromTemplateCode(final String templateCode) {
        try {
            // Decode base 64
            final byte[] data = DatatypeConverter.parseBase64Binary(templateCode.trim());

            final InputStream stream = new DynaByteInputStream(new ByteArrayInputStream(data));
            try {
                // Read complex settings
                final int settings = stream.read();
                final boolean hasSector = (settings & 1) == 1;
                final int gameNid = (settings >> 1) & 7;

                final Game game = GameFactory.getInstance().getGame(gameNid);
                final Complex complex = new Complex(game);

                // Read sector coordinates or sun power
                if (hasSector) {
                    final int x = stream.read();
                    final int y = stream.read();
                    complex.setSector(game.getSectorFactory().getSector(x, y));
                } else {
                    final int percent = stream.read();
                    complex.setSuns(game.getSunFactory().getSun(percent));
                }

                int factoryId;
                while ((factoryId = stream.read()) != 0) {
                    final Factory factory = game.getFactoryFactory().getFactory(factoryId);
                    if (factory.isMine()) {
                        final List<Integer> yields = new ArrayList<Integer>();
                        int yield;
                        while ((yield = stream.read()) != 0)
                            yields.add(yield - 1);
                        complex.addFactory(new ComplexFactory(game, factory, yields));
                    } else {
                        final int quantity = stream.read();
                        complex.addFactory(new ComplexFactory(game, factory, quantity, 0));
                    }
                }

                return complex;
            } finally {
                stream.close();
            }
        } catch (final IOException e) {
            throw new TemplateCodeException(e.toString(), e);
        }
    }

    /**
     * Returns the template code.
     *
     * @return The template code.
     */
    public String getTemplateCode() {
        try {
            final ByteArrayOutputStream arrayStream = new ByteArrayOutputStream();
            final OutputStream stream = new DynaByteOutputStream(arrayStream);

            // Write the template settings bit mask.
            int settings = this.sector == null ? 0 : 1;
            settings |= this.game.getNid() << 1;
            stream.write(settings);

            // Write the sector coordinates
            if (this.sector != null) {
                stream.write(this.sector.getX());
                stream.write(this.sector.getY());
            }

            // Or else write the sun power
            else {
                stream.write(this.suns.getPercent());
            }

            // Write the factories
            for (final ComplexFactory complexFactory : getAllFactories()) {
                if (complexFactory.isDisabled())
                    continue;
                final Factory factory = complexFactory.getFactory();
                stream.write(factory.getNid());
                if (factory.isMine()) {
                    for (final int yield : complexFactory.getYields())
                        stream.write(yield + 1);
                    stream.write(0);
                } else
                    stream.write(complexFactory.getQuantity());
            }

            // Write end marker
            stream.write(0);
            stream.close();

            // Get byte array from stream
            final byte[] data = arrayStream.toByteArray();

            // Return base 64 encoded bytes
            return DatatypeConverter.printBase64Binary(data);
        } catch (final IOException e) {
            throw new TemplateCodeException(e.toString(), e);
        }
    }

    /**
     * Checks if this complex has mines.
     *
     * @return True if complex has mines, false if not.
     */
    public boolean hasMines() {
        for (final ComplexFactory factory : this.factories)
            if (factory.getFactory().isMine())
                return true;
        return false;
    }

    /**
     * Returns the complex data as ASCII.
     *
     * @return The complex data as ASCII.
     */
    public String getASCII() {
        final StringWriter writer = new StringWriter();
        final PrintWriter out = new PrintWriter(writer);

        // Print the game name.
        out.print(I18N.getString("complex.game"));
        out.print(": ");
        out.println(getGame().getName());

        // Print the sector name if chosen
        final Sector sector = getSector();
        if (sector != null) {
            out.print(I18N.getString("complex.sector"));
            out.print(": ");
            out.println(sector.getName());
        }

        // Print the sun power
        out.print(I18N.getString("complex.suns"));
        out.print(": ");
        out.println(getSuns().toString());

        // Print the template code
        out.print(I18N.getString("complex.templateCode"));
        out.print(": ");
        out.println(getTemplateCode());

        out.println();

        // Print factories
        out.print(I18N.getString("complex.factories"));
        out.println(":");
        for (final ComplexFactory complexFactory : getAllFactories()) {
            if (complexFactory.isDisabled())
                continue;
            final Factory factory = complexFactory.getFactory();

            out.print(complexFactory.getQuantity());
            out.print("x ");
            out.print(factory.getName());
            out.print(" (");
            out.print(factory.getRace().getName());
            if (factory.isMine()) {
                out.print(", ");
                out.print(I18N.getString("complex.yield"));
                out.print(": ");
                boolean first = true;
                for (final int yield : complexFactory.getYields()) {
                    if (!first)
                        out.print(", ");
                    out.print(yield);
                    first = false;
                }
            }
            out.println(")");
        }

        out.println();

        // Print total complex price
        out.print(I18N.getString("complex.totalPrice"));
        out.print(": ");
        out.print(new DecimalFormat().format(getTotalPrice()));
        out.println(" Cr");

        // Print total complex price
        out.print(I18N.getString("complex.profitPerHour"));
        out.print(": ");
        out.print(new DecimalFormat().format(Math.round(getProfit())));
        out.println(" Cr");
        return writer.toString();
    }
}