Package de.ailis.xadrian.data

Source Code of de.ailis.xadrian.data.Complex

/*
* 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();
    }
}
TOP

Related Classes of de.ailis.xadrian.data.Complex

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.