package javakurs.uebung06;

import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * {@link FridgeEx2} extends the {@link FridgeEx} class with even more
 * functionality.
 *
 * @author Joerg Steffen, DFKI
 * @version $Id: FridgeEx2.java,v 1.2 2010/12/01 14:02:54 steffen Exp $
 */
public class FridgeEx2 extends FridgeEx {

  /**
   * This serves as a dictionary for looking up a product's gender and
   * plural word form.
   */
  private Map<String, Product> products = new HashMap<String, Product>();

  /**
   * This contains the document builder used for parsing the weather from Google
   * as XML.
   */
  private DocumentBuilder builder;


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

    super();

    try {
      // init document builder
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      this.builder = factory.newDocumentBuilder();
    } catch (ParserConfigurationException e) {
      this.message("Konnte den Parser nicht konfigurieren:");
      System.exit(-1);
    }

    // init products
    this.products.put(
      "Wurst", new Product("Wurst", "fem", "W\u00fcrste"));
    this.products.put(
      "K\u00e4se", new Product("K\u00e4se", "mask", "K\u00e4se"));
    this.products.put(
      "Butter", new Product("Butter", "fem", "Packungen Butter"));
    this.products.put(
      "Milch", new Product("Milch", "fem", "Flaschen Milch"));
    this.products.put(
      "Salat", new Product("Salat", "mask", "Salate"));
    this.products.put(
      "Joghurt", new Product("Joghurt", "mask", "Joghurts"));
    this.products.put(
      "Quark", new Product("Quark", "mask", "Portionen Quark"));
    this.products.put(
      "Bier", new Product("Bier", "neut", "Flaschen Bier"));
    this.products.put(
      "Cola", new Product("Cola", "fem", "Flaschen Cola"));
    this.products.put(
      "Ketchup", new Product("Ketchup", "mask", "Flaschen Ketchups"));
    this.products.put(
      "Senf", new Product("Senf", "mask", "Portionen Senf"));
    this.products.put(
      "Lachs", new Product("Lachs", "mask", "Lachse"));
    this.products.put(
      "Kaviar", new Product("Kaviar", "mask", "Packungen Kaviar"));
    this.products.put(
      "Ei", new Product("Ei", "neut", "Eier"));
  }


  /**
   * This is the extended version that prints the given message to stdout and
   * also uses Mary for TTS.
   *
   * @param message
   *          the message
   */
  @Override
  public void message(String message) {

    message = this.fixMessage(message);
    System.out.println(message);
    MaryTtsClient.say(message);
  }


  /**
   * This replaces the product numbers and names with a grammatically correct
   * version.
   *
   * @param message
   *          the message
   * @return the fixed message
   */
  private String fixMessage(String message) {

    // init result with the original message;
    // this is the default return value
    String result = message;

    // define patter to extract the product name and the amount
    Pattern prodPattern = Pattern.compile(
      "([0-9]+)\\s*([A-Z\u00c0-\u00dd][a-zA-Z\u00c0-\u00ff]+)");
    Matcher matcher = prodPattern.matcher(message);
    if (matcher.find()) {
      int amount = Integer.parseInt(matcher.group(1));
      String prodString = matcher.group(2);
      Product prod = this.products.get(prodString);
      if (null != prod) {
        if (amount > 1) {
          result = message.replace(prodString, prod.toString(amount));
        }
        else {
          result = message.replace(matcher.group(), prod.toString(amount));
        }
      }
    }

    // default if product is not in product list
    return result;
  }


  /**
   * This filters the stock using the given number and sign restriction.
   *
   * @param sign
   *          -1 = less than, 1 = equals, 0 = more than
   * @param amountString
   *          the number as string
   */
  public void filterStock(int sign, String amountString) {

    String product;
    double amount = this.parseNumber(amountString);
    boolean found = false;
    for (Map.Entry<Object, Object> e : this.getInventory().entrySet()) {
      double value = this.parseNumber((String)e.getValue());
      if ((sign == 1) && (value == amount)
          || (sign == -1) && (value < amount)
          || (sign == 0) && (value > amount)) {
        product = (String)e.getKey();
        this.askStock(product);
        found = true;
      }
    }
    if (!found) {
      this.message(
        "Ich haben keine zu der Suchanfrage passenden Eintr\u00e4ge gefunden.");
    }
  }


  /**
   * This retrieves the current weather from Google for the given city.
   *
   * @param city
   *          the city, may be {@code null}
   */
  public void printWeather(Token city) {

    // if no city is given, use Saarbruecken
    String cityString = null;
    if (null == city) {
      cityString = "Saarbruecken";
    }
    else {
      cityString = city.image;
    }

    // try to get weather from Google; the result is an XML String
    String url =
      "http://www.google.com/ig/api?weather=" + cityString + "&hl=de";
    String htmlContent = null;
    try {
      htmlContent = HtmlContent.getFromUrl(url, "ISO-8859-1");
    } catch (IOException e) {
      e.printStackTrace();
    }
    if (htmlContent == null) {
      this.message("Ich konnte keine Wetterdaten finden.");
    }
    else {
      // read HTML content (which is an XML string) into DOM
      Document doc = null;
      try {
        doc = this.builder.parse(
          new InputSource(new StringReader(htmlContent)));
      } catch (IOException e) {
        e.printStackTrace();
      } catch (SAXException e) {
        e.printStackTrace();
      }

      if (doc == null) {
        this.message("Ich konnte das Wetter von Google nicht lesen.");
      }
      else {
        try {
          // extract weather info using xpaths
          XPath xPath = XPathFactory.newInstance().newXPath();
          String condition =
            xPath.evaluate("//current_conditions/condition/@data", doc);
          String temp =
            xPath.evaluate("//current_conditions/temp_c/@data", doc);

          this.message(
            "Das Wetter in " + cityString + ": " + condition + " bei "
            + temp + " Grad");

        } catch (XPathExpressionException e) {
          e.printStackTrace();
        }
      }
    }
  }
}
