package javakurs.uebung05;

import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;
import java.util.Map;

/**
 * {@link FridgeEx} extends the base {@link Fridge} class with more
 * functionality.
 *
 * @author Joerg Steffen, DFKI
 * @version $Id: FridgeEx.java,v 1.2 2010/11/29 17:19:24 steffen Exp $
 */
public class FridgeEx extends Fridge {

  /**
   * This contains the locale to use for printing and parsing numbers.
   */
  private static final Locale LOCALE = Locale.GERMAN;


  /**
   * This creates a new instance of {@link FridgeEx}.
   */
  public FridgeEx() {

    super();
  }


  /**
   * This parses the given number as a double using the GERMAN locale.
   *
   * @param number
   *          the number to parse
   * @return the parsed double or {@code 0} if string could not be parsed as
   * number
   */
  public double parseNumber(String number) {

    try {
      NumberFormat nf = NumberFormat.getInstance(LOCALE);
      Number parsedNum = nf.parse(number);
      return parsedNum.doubleValue();
    } catch (ParseException e) {
      System.err.println("could not parse " + number + ", returning 0.0");
      e.printStackTrace();
      return 0.0;
    }
  }


  /**
   * This converts floating point number into a string smartly, i.e., either in
   * integer format or with trailing decimal point followed by one digit.
   *
   * @param number
   *          to format
   * @return number as string
   *
   */
  public String formatNumber(double number) {

    String converted = "";
    if (number == (int)number) {
      converted = String.format(LOCALE, "%.0f", number);
    }
    else {
      converted = String.format(LOCALE, "%.1f", number);
    }

    return converted;
  }


  /**
   * This looks up the amount of the given product in the fridge and returns it
   * as a double.
   *
   * @param product
   *          name of product
   * @return current amount of the product; -1.0 if the product is not in the
   *         fridge inventory
   */
  public double getStock(String product) {

    double currentAmount = -1.0;
    String currentAmountString = super.getInventory().getProperty(product);
    if (currentAmountString != null) {
      currentAmount = this.parseNumber(currentAmountString);
    }

    return currentAmount;
  }


  /**
   * This checks if the given amount of the given product is in the fridge.
   *
   * @param checkAmount
   *          the amount to check
   * @param product
   *          the name of the product
   */
  public void checkStock(double checkAmount, String product) {

    // compare check amount with current stock
    double realAmount = this.getStock(product);
    if (realAmount == -1.0) {
      // unknown product
      this.message(product + " ist nicht im K\u00fchlschrank.");
    }
    else if (realAmount == 0.0) {
      // known product, but out of stock
      this.message(
        "Nein, " + product + " ist nicht mehr im K\u00fchlschrank.");
    }
    else if (realAmount < checkAmount) {
      // known product, not out of stock, but not enough left
      this.message(
        "Nein. Es " + (realAmount < 2.0 ? "ist" : "sind") + " nur noch "
        + this.formatNumber(realAmount) + " " + product
        + " im K\u00fchlschrank.");
    }
    else {
      // know product, enough left
      this.message(
        "Ja. Es " + (realAmount < 2.0 ? "ist" : "sind") + " "
        + this.formatNumber(realAmount) + " " + product
        + " im K\u00fchlschrank.");
    }
  }


  /**
   * This asks the amount of the given product.
   *
   * @param product
   *          the name of the product
   */
  public void askStock(String product) {

    double amount = this.getStock(product);
    if (amount == -1.0) {
      this.message(product + " ist nicht im K\u00fchlschrank.");
    }
    else {
      if (amount == 0.0) {
        this.message(product + " ist nicht mehr im K\u00fchlschrank.");
      }
      else {
        this.message("Es " + (amount < 2.0 ? "ist" : "sind") + " "
            + this.formatNumber(amount) + " " + product
            + " im K\u00fchlschrank.");
      }
    }
  }


  /**
   * This adds or removes the given amount of the given product to the fridge.
   *
   * @param amount
   *          the amount; may be positive or negative
   * @param product
   *          the name of the product
   */
  public void modifyStock(double amount, String product) {

    // get current amount of the product
    double currentAmount = 0.0;
    String currentAmountString = super.getInventory().getProperty(product);
    if (currentAmountString == null) {
      this.message(product + " war noch nicht im K\u00fchlschrank.");
    }
    else {
      currentAmount = this.parseNumber(currentAmountString);
    }

    // check whether amount to remove is larger than available amount
    if (currentAmount + amount < 0) {
      this.message("Soviel ist nicht im K\u00fchlschrank!");
    }
    else {

      // inform about adding/removing
      if (amount > 0) {
        this.message("F\u00fcge " + this.formatNumber(amount) + " " + product
          + " hinzu.");
      }
      else {
        this.message("Entnehme " + this.formatNumber(-amount) + " " + product
          + ".");
      }

      // update amount
      currentAmount = currentAmount + amount;
      super.getInventory().setProperty(
        product, this.formatNumber(currentAmount));
      this.message(
        "Jetzt sind " + this.formatNumber(currentAmount) + " "
        + product + " vorhanden.");
    }
  }


  /**
   * This lists all products in fridge; preceded by the amount of the product.
   */
  public void listStock() {

    this.message("Im K\u00fchlschrank sind:");
    for (Map.Entry<Object, Object> e : super.getInventory().entrySet()) {
      double amount = this.parseNumber((String)e.getValue());
      this.message(this.formatNumber(amount) + " " + (String)e.getKey());
    }
  }


  /**
   * This lists all products in the fridge with the given amount.
   *
   * @param amount
   *          product amount to match
   */
  public void listStock(double amount) {

    for (Map.Entry<Object, Object> e : super.getInventory().entrySet()) {
      double value = this.parseNumber((String)e.getValue());
      if (value == amount) {
        this.message((String)e.getKey());
      }
    }
  }


  /**
   * This prints the list of products to buy, i.e. items with amount = 0.0.
   */
  public void buyList() {

    this.message("Du solltest folgendes einkaufen:");
    this.listStock(0.0);
  }


  /*
   * Following are auxiliary methods that take Token parameters from the
   * JavaCC-generated parser.
   */


  /**
   * This checks if the given amount of the given product is in the fridge;
   * formulates an answer using {@link #message(String)}.
   *
   * @param checkAmountTok
   *          the amount to check as Token (may be null, defaults to 1.0 then)
   * @param productTok
   *          the product as Token
   */
  public void checkStock(Token checkAmountTok, Token productTok) {

    // extract values from tokens
    double checkAmount = 1.0;
    if (null != checkAmountTok) {
      checkAmount = this.parseNumber(checkAmountTok.image);
    }
    String product = productTok.image;

    // forward to non-Token version of the method
    this.checkStock(checkAmount, product);
  }


  /**
   * This adds or removes the given amount of the given product to the fridge.
   *
   * @param amountTok
   *          the amount as Token (may be null, defaults to 1.0 then)
   * @param productTok
   *          the product as Token
   * @param sign
   *          1 = add, -1 = remove
   */
  public void modifyStock(Token amountTok, Token productTok, int sign) {

    // extract values from tokens
    double amount = 1.0;
    if (amountTok != null) {
      amount = this.parseNumber(amountTok.image);
    }
    String product = productTok.image;

    // forward to non-Token version of the method
    this.modifyStock(amount * sign, product);
  }


  /**
   * {@inheritDoc}
   */
  @Override
  public void listStock(Token product) {

    if (product == null) {
      this.listStock();
    }
    else {
      this.askStock(product.image);
    }
  }
}
