/**
 *  Copyright (C) 2002-2012   The FreeCol Team
 *
 *  This file is part of FreeCol.
 *
 *  FreeCol is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  FreeCol is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with FreeCol.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.sf.freecol.common.model;

import java.util.logging.Logger;

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;

import org.w3c.dom.Element;


/**
 * Objects of this class hold the market data for a particular type of
 * good.
 */
public class MarketData extends FreeColGameObject {

    private static final Logger logger = Logger.getLogger(MarketData.class.getName());

    /**
     * Bounds on price movements.
     */
    public static final int MINIMUM_PRICE = 1;
    public static final int MAXIMUM_PRICE = 19;

    /**
     * What type of goods is this.
     */
    private GoodsType goodsType;

    /**
     * Purchase price.
     */
    private int costToBuy;

    /**
     * Sale price.
     */
    private int paidForSale;

    /**
     * Amount of this goods in the market.
     */
    private int amountInMarket;

    /**
     * The initial price.
     */
    private int initialPrice;

    /**
     * Arrears owed to the crown.
     */
    private int arrears;

    /**
     * Total sales.
     */
    private int sales;

    /**
     * Total income before taxes.
     */
    private int incomeBeforeTaxes;

    /**
     * Total income after taxes.
     */
    private int incomeAfterTaxes;

    /**
     * Place to save to old price so as to be able to tell when a price change
     * message should be generated.  Not necessary to serialize.
     */
    private int oldPrice;

    /**
     * Has this good been traded?
     */
    private boolean traded;

    /**
     * Creates a new <code>MarketData</code> instance.
     *
     * @param goodsType a <code>GoodsType</code> value
     */
    public MarketData(Game game, GoodsType goodsType) {
        super(game);

        this.goodsType = goodsType;
        paidForSale = goodsType.getInitialSellPrice();
        costToBuy = goodsType.getInitialBuyPrice();
        amountInMarket = goodsType.getInitialAmount();
        initialPrice = goodsType.getInitialSellPrice();
        arrears = 0;
        sales = 0;
        incomeBeforeTaxes = 0;
        incomeAfterTaxes = 0;
        oldPrice = costToBuy;
        traded = false;
    }

    /**
     * Instantiate a new <code>MarketData</code> from an
     * XML representation.
     *
     * @param game The <code>Game</code> this object belongs to.
     * @param in The input stream containing the XML.
     * @throws XMLStreamException if an error occured during parsing.
     */
    public MarketData(Game game, XMLStreamReader in) throws XMLStreamException {
        super(game, in);

        readFromXML(in);
    }

    /**
     * Instantiates a new <code>MarketData</code> with the given
     * ID. The object should later be initialized by calling either
     * {@link #readFromXML(XMLStreamReader)} or
     * {@link #readFromXMLElement(Element)}.
     *
     * @param game The <code>Game</code> in which this object belong.
     * @param id The unique identifier for this object.
     */
    public MarketData(Game game, String id) {
        super(game, id);
    }

    /**
     * Adjust the prices.  Sets the costToBuy and paidForSale fields
     * from the amount in the market, initial price and goods-type
     * specific information.
     */
    public void price() {
        if (!goodsType.isStorable()) return;
        int diff = goodsType.getPriceDifference();
        float amountPrice = initialPrice * (goodsType.getInitialAmount()
            / (float) amountInMarket);
        int newSalePrice = Math.round(amountPrice);
        int newPrice = newSalePrice + diff;

        // Work-around to limit prices of new world goods
        // and related manufactured goods.
        if ((goodsType.isNewWorldGoodsType()
             || (goodsType.getRawMaterial() != null
                 && goodsType.getRawMaterial().isNewWorldGoodsType()))
            && newSalePrice > initialPrice + 2) {
            newSalePrice = initialPrice + 2;
            newPrice = newSalePrice + diff;
        }

        // Another hack to prevent price changing too fast in one hit.
        // Push the amount in market back as well to keep this stable.
        //
        // Prices that change by more than the buy/sell difference
        // allow big traders to exploit the market and extract free
        // money... not sure I want to be fighting economic reality
        // but game balance demands it here.
        if (costToBuy > 0) {
            if (newPrice > costToBuy + diff) {
                amountPrice -= newPrice - (costToBuy + diff);
                amountInMarket = Math.round(goodsType.getInitialAmount()
                    * (initialPrice / amountPrice));
                logger.info("Clamped price rise for " + getId()
                    + " from " + newPrice
                    + " to " + (costToBuy + diff));
                newPrice = costToBuy + diff;
            } else if (newPrice < costToBuy - diff) {
                amountPrice += (costToBuy - diff) - newPrice;
                amountInMarket = Math.round(goodsType.getInitialAmount()
                    * (initialPrice / amountPrice));
                logger.info("Clamped price fall for " + getId()
                    + " from " + newPrice
                    + " to " + (costToBuy - diff));
                newPrice = costToBuy - diff;
            }
            newSalePrice = newPrice - diff;
        }

        // Clamp extremes.
        if (newPrice > MAXIMUM_PRICE) {
            newPrice = MAXIMUM_PRICE;
            newSalePrice = newPrice - diff;
        } else if (newSalePrice < MINIMUM_PRICE) {
            newSalePrice = MINIMUM_PRICE;
            newPrice = newSalePrice + diff;
        }

        costToBuy = newPrice;
        paidForSale = newSalePrice;
    }

    /**
     * Get the type of goods of this <code>MarketData</code>.
     *
     * @return The goods type for this data.
     */
    public final GoodsType getGoodsType() {
        return goodsType;
    }

    /**
     * Get the <code>CostToBuy</code> value.
     *
     * @return an <code>int</code> value
     */
    public final int getCostToBuy() {
        return costToBuy;
    }

    /**
     * Set the <code>CostToBuy</code> value.
     *
     * @param newCostToBuy The new CostToBuy value.
     */
    public final void setCostToBuy(final int newCostToBuy) {
        this.costToBuy = newCostToBuy;
    }

    /**
     * Get the <code>PaidForSale</code> value.
     *
     * @return an <code>int</code> value
     */
    public final int getPaidForSale() {
        return paidForSale;
    }

    /**
     * Set the <code>PaidForSale</code> value.
     *
     * @param newPaidForSale The new PaidForSale value.
     */
    public final void setPaidForSale(final int newPaidForSale) {
        this.paidForSale = newPaidForSale;
    }

    /**
     * Get the <code>AmountInMarket</code> value.
     *
     * @return an <code>int</code> value
     */
    public final int getAmountInMarket() {
        return amountInMarket;
    }

    /**
     * Set the <code>AmountInMarket</code> value.
     *
     * @param newAmountInMarket The new AmountInMarket value.
     */
    public final void setAmountInMarket(final int newAmountInMarket) {
        this.amountInMarket = newAmountInMarket;
    }

    /**
     * Get the <code>InitialPrice</code> value.
     *
     * @return an <code>int</code> value
     */
    public final int getInitialPrice() {
        return initialPrice;
    }

    /**
     * Set the <code>InitialPrice</code> value.
     *
     * @param newInitialPrice The new InitialPrice value.
     */
    public final void setInitialPrice(final int newInitialPrice) {
        this.initialPrice = newInitialPrice;
    }

    /**
     * Get the <code>Arrears</code> value.
     *
     * @return an <code>int</code> value
     */
    public final int getArrears() {
        return arrears;
    }

    /**
     * Set the <code>Arrears</code> value.
     *
     * @param newArrears The new Arrears value.
     */
    public final void setArrears(final int newArrears) {
        this.arrears = newArrears;
    }

    /**
     * Get the <code>Sales</code> value.
     *
     * @return an <code>int</code> value
     */
    public final int getSales() {
        return sales;
    }

    /**
     * Set the <code>Sales</code> value.
     *
     * @param newSales The new Sales value.
     */
    public final void setSales(final int newSales) {
        this.sales = newSales;
    }

    /**
     * Get the <code>IncomeBeforeTaxes</code> value.
     *
     * @return an <code>int</code> value
     */
    public final int getIncomeBeforeTaxes() {
        return incomeBeforeTaxes;
    }

    /**
     * Set the <code>IncomeBeforeTaxes</code> value.
     *
     * @param newIncomeBeforeTaxes The new IncomeBeforeTaxes value.
     */
    public final void setIncomeBeforeTaxes(final int newIncomeBeforeTaxes) {
        this.incomeBeforeTaxes = newIncomeBeforeTaxes;
    }

    /**
     * Get the <code>IncomeAfterTaxes</code> value.
     *
     * @return an <code>int</code> value
     */
    public final int getIncomeAfterTaxes() {
        return incomeAfterTaxes;
    }

    /**
     * Set the <code>IncomeAfterTaxes</code> value.
     *
     * @param newIncomeAfterTaxes The new IncomeAfterTaxes value.
     */
    public final void setIncomeAfterTaxes(final int newIncomeAfterTaxes) {
        this.incomeAfterTaxes = newIncomeAfterTaxes;
    }

    /**
     * Get the old price in this <code>MarketData</code>.
     *
     * @return The old price.
     */
    public final int getOldPrice() {
        return oldPrice;
    }

    /**
     * Set the old price in this <code>MarketData</code>.
     *
     * @param oldPrice A `new' old price.
     */
    public void setOldPrice(int oldPrice) {
        this.oldPrice = oldPrice;
    }

    /**
     * Has there been trading in this <code>MarketData</code>?
     *
     * @return Whether trading has occurred.
     **/
    public final boolean getTraded() {
        return traded;
    }

    /**
     * Set the trade status of this <code>MarketData</code>.
     *
     * @param traded The trade status to set.
     **/
    public void setTraded(boolean traded) {
        this.traded = traded;
    }


    /**
     * This method writes an XML-representation of this object to
     * the given stream.
     *
     * Only attributes visible to the given <code>Player</code> will
     * be added to that representation if <code>showAll</code> is
     * set to <code>false</code>.
     *
     * @param out The target stream.
     * @param player The <code>Player</code> this XML-representation
     *      should be made for, or <code>null</code> if
     *      <code>showAll == true</code>.
     * @param showAll Only attributes visible to <code>player</code>
     *      will be added to the representation if <code>showAll</code>
     *      is set to <i>false</i>.
     * @param toSavedGame If <code>true</code> then information that
     *      is only needed when saving a game is added.
     * @throws XMLStreamException if there are any problems writing
     *      to the stream.
     */
    protected void toXMLImpl(XMLStreamWriter out, Player player,
                             boolean showAll, boolean toSavedGame)
        throws XMLStreamException {
        // Start element:
        out.writeStartElement(getXMLElementTagName());

        out.writeAttribute(ID_ATTRIBUTE, getId());
        out.writeAttribute("goods-type", goodsType.getId());
        out.writeAttribute("amount", Integer.toString(amountInMarket));
        out.writeAttribute("initialPrice", Integer.toString(initialPrice));
        out.writeAttribute("arrears", Integer.toString(arrears));
        out.writeAttribute("sales", Integer.toString(sales));
        out.writeAttribute("incomeBeforeTaxes",
            Integer.toString(incomeBeforeTaxes));
        out.writeAttribute("incomeAfterTaxes",
            Integer.toString(incomeAfterTaxes));
        out.writeAttribute("traded", Boolean.toString(traded));
        out.writeEndElement();
    }

    /**
     * Initialize this object from an XML-representation of this object.
     *
     * @param in The input stream with the XML.
     */
    public void readAttributes(XMLStreamReader in) throws XMLStreamException {
        String goodsTypeStr = in.getAttributeValue(null, "goods-type");
        if (goodsTypeStr == null) { // @compat 0.9.x
            goodsTypeStr = in.getAttributeValue(null, ID_ATTRIBUTE);
            setDefaultId(getGame());
            // end compatibility code
        } else {
            setId(in.getAttributeValue(null, ID_ATTRIBUTE));
        }
        if (goodsTypeStr == null) {
            throw new XMLStreamException("Missing goods-type");
        }
        goodsType = getSpecification().getGoodsType(goodsTypeStr);
        amountInMarket = getAttribute(in, "amount", 0);
        initialPrice = getAttribute(in, "initialPrice", -1);
        arrears = getAttribute(in, "arrears", 0);
        sales = getAttribute(in, "sales", 0);
        incomeBeforeTaxes = getAttribute(in, "incomeBeforeTaxes", 0);
        incomeAfterTaxes = getAttribute(in, "incomeAfterTaxes", 0);
        traded = getAttribute(in, "traded", sales != 0);
        costToBuy = -1; // Disable price change clamping
        price();
        oldPrice = costToBuy;
    }

    /**
     * Returns the tag name of the root element representing this object.
     *
     * @return "marketData"
     */
    public static String getXMLElementTagName() {
        return "marketData";
    }
}
